txt - Java crea un archivo ENORME cuando escribe a través de Windows Remote Desktop(tsclient)
escribir fichero java sin borrar contenido (1)
Un cliente nuestro informó un problema muy extraño cuando nuestra aplicación Swing está escribiendo un archivo en la máquina local de los usuarios a través de Windows Remote Desktop (la aplicación está alojada en un servidor de terminal donde los usuarios se conectan).
El flujo es:
- Los usuarios inician sesión y ejecutan la aplicación a través del escritorio remoto (con su
C:/
incluido como un "recurso local") - Mientras trabajan exportan datos de la base de datos a archivos
- El usuario elige qué datos exportar
- El usuario selecciona un archivo de destino en su computadora local como
//tsclient/C/Temp/TestFile.txt
- Los archivos pueden ser grandes, por lo que se obtienen 1000 filas de la base de datos y se escriben en el archivo por lote
- En el segundo lote, cuando Java abre el archivo y vuelve a escribir, ¡algo realmente extraño comienza a suceder!
- El archivo aumenta rápidamente de tamaño y se detiene en alrededor de 2 GB
- Luego, los datos continúan escribiéndose en el archivo
No estoy seguro de si esto es un problema en las bibliotecas centrales de Java, la implementación de Escritorio remoto o una combinación. Nuestra aplicación también se aloja a través de Citrix, que funciona bien, y escribir en el disco local o en las rutas de red de UNC funciona bien también.
Creé un SSCCE que demuestra el problema, me conecté a una computadora con Escritorio remoto (me aseguro de que C:/
un "recurso local") y ejecuté el programa para ver un comportamiento realmente extraño. Estoy usando JDK-7u45.
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.util.Collections;
/**
* Demonstrates weird issue when writing (appending) to a file over TsClient (Microsoft Remote Desktop).
*
* @author Martin
*/
public class WriteOverTsClientDemo
{
private static final File FILE_TO_WRITE = new File("////tsclient//C//Temp//TestFile.txt");
//private static final File FILE_TO_WRITE = new File("C://Temp//TestFile.txt");
private static final String ROW_DATA = "111111111122222222223333333333444444444555555555566666666667777777777888888888899999999990000000000";
public static void main(String[] args) throws IOException
{
if (!FILE_TO_WRITE.getParentFile().exists())
{
throw new RuntimeException("/nPlease create directory C://Temp// on your local machine and run this application via RemoteDesktop with C:// as a ''Local resource''.");
}
FILE_TO_WRITE.delete();
new WriteOverTsClientDemo().execute();
}
private void execute() throws IOException
{
System.out.println("Writing to file: " + FILE_TO_WRITE);
System.out.println();
for (int i = 1; i <= 10; i++)
{
System.out.println("Writing batch " + i + "...");
writeDataToFile(i);
System.out.println("Size of file after batch " + i + ": " + FILE_TO_WRITE.length());
System.out.println();
}
System.out.println("Done!");
}
private void writeDataToFile(int batch) throws IOException
{
Charset charset = Charset.forName("UTF-8");
CharsetEncoder encoder = charset.newEncoder();
try(OutputStream out = Files.newOutputStream(FILE_TO_WRITE.toPath(), CREATE, WRITE, getTruncateOrAppendOption(batch));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, encoder)))
{
writeData(batch, writer);
}
}
private void writeData(int batch, BufferedWriter writer) throws IOException
{
for (String data : createData())
{
writer.append(Integer.toString(batch));
writer.append(" ");
writer.append(data);
writer.append("/n");
}
}
private Iterable<String> createData()
{
return Collections.nCopies(100, ROW_DATA);
}
/**
* @return option to write from the beginning or from the end of the file
*/
private OpenOption getTruncateOrAppendOption(int batch)
{
return batch == 1 ? TRUNCATE_EXISTING : APPEND;
}
}
No tengo una configuración (sin Windows) para verificar este efecto :( solo pensamientos:
2GB suena como el tamaño de archivo máximo relacionado con el sistema de archivos. Sistema operativo Windows de 32 bits en su lado del cliente?
El comportamiento suena como el almacenamiento en caché inteligente del sistema de archivos en el bloque malo FS: el acceso rápido a la escritura de archivos de los bloques grandes intenta de forma inteligente preocuparse por el archivo en un intento de ajustar futuras escrituras en el archivo que tiene bloques juntos. Pruebe con un FS diferente para verificar? ¿Has probado FreeRDP?
Mantenga el archivo abierto. La reapertura para escritura de bloques grandes podría indicar sistemas inteligentes para almacenar en caché.
Actualizar:
// in append-mode then position is advanced to end before writing
p = (append) ? nd.size(fd) : position0(fd, -1);
conduce finalmente a FileDispatcherImpl: 136
static native long More ...size0(FileDescriptor fd) throws IOException;
como nativo puede contener cualquier error. Cuando se trata de protocolos en medio. Me gustaría presentar esto como un error en nio / Windows, ya que podrían no haber previsto algo divertido con RDP debajo.
Parece que el tamaño devuelto es Integer.MAX_VALUE
y el puntero del archivo se mueve allí ...
Implementación alternativa java.io.FileWriter
y sin codificación para reducir las líneas de código:
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
/**
* Demonstrates weird issue when writing (appending) to a file over TsClient (Microsoft Remote Desktop).
*
* @author Martin
*/
public class WriteOverTsClientDemo
{
// private static final File FILE_TO_WRITE = new File("////tsclient//C//Temp//TestFile.txt");
private static final File FILE_TO_WRITE = new File("/tmp/TestFile.txt");
private static final String ROW_DATA = "111111111122222222223333333333444444444555555555566666666667777777777888888888899999999990000000000";
public static void main(final String[] args) throws IOException
{
if (!FILE_TO_WRITE.getParentFile().exists())
{
throw new RuntimeException("/nPlease create directory C://Temp// on your local machine and run this application via RemoteDesktop with C:// as a ''Local resource''.");
}
FILE_TO_WRITE.delete();
new WriteOverTsClientDemo().execute();
}
private void execute() throws IOException
{
System.out.println("Writing to file: " + FILE_TO_WRITE);
System.out.println();
for (int i = 1; i <= 20; i++)
{
System.out.println("Writing batch " + i + "...");
writeDataToFile(i);
System.out.println("Size of file after batch " + i + ": " + FILE_TO_WRITE.length());
System.out.println();
}
System.out.println("Done!");
}
private void writeDataToFile(final int batch) throws IOException
{
try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_TO_WRITE, batch > 1)))
{
writeData(batch, writer);
}
}
private void writeData(final int batch, final BufferedWriter writer) throws IOException
{
for (final String data : createData())
{
writer.append(Integer.toString(batch));
writer.append(" ");
writer.append(data);
writer.append("/n");
}
}
private Iterable<String> createData()
{
return Collections.nCopies(100, ROW_DATA);
}
}