Java создает HUGE файл при записи через Windows Remote Desktop (tsclient)

Наш клиент сообщил о очень странной проблеме, когда наше приложение Swing записывает файл на локальную машину пользователя через Windows Remote Desktop (приложение размещается на сервере терминалов, на котором подключаются пользователи).

Поток:

  • Пользователи регистрируются и запускают приложение через удаленный рабочий стол (с их C:\, включенным в качестве "Локального ресурса" )
  • Во время работы они экспортируют данные из базы данных в файлы
  • Пользователь выбирает, какие данные экспортировать
  • Пользователь выбирает файл назначения на своем локальном компьютере, например \\tsclient\C\Temp\TestFile.txt
  • Файлы могут быть большими, поэтому 1000 строк извлекаются из базы данных и записываются в файл за пакет
  • Во второй партии, когда Java открывает файл и записывает его снова, начинает что-то действительно странное!
    • Файл быстро увеличивается по размеру и останавливается примерно на 2 ГБ.
    • Затем данные продолжают записываться в файл

Я не уверен, что это проблема в основных библиотеках Java, реализации удаленного рабочего стола или комбинации. Наше приложение также размещено через Citrix, который отлично работает, и запись на локальный диск или сетевые пути UNC также отлично работает.

Я создал SSCCE, демонстрирующий проблему, подключился к компьютеру с помощью Remote Desktop (убедитесь, что C:\ является "локальный ресурс" ) и запустить программу, чтобы увидеть какое-то действительно странное поведение! Я использую 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;
    }
}

Ответы

Ответ 1

У меня нет настройки (нет Windows), чтобы проверить этот эффект:( так просто мысли:

2 ГБ звучит, как максимальный размер файла, связанный с файловой системой. 32-разрядная операционная система Windows со стороны клиента?

Поведение звучит как умное кэширование файловой системы на плохом блоке FS: быстрый доступ к файлам большого размера удаленно пытается ухитрять захват файла, пытаясь закрепить будущие записи в файле, имеющем блоки вместе. Попробуйте другой FS для проверки? Пробовал FreeRDP?

Сохраните файл открытым. Повторное открытие для записи огромных блоков может намекать на умные системы для кеширования.

Update:

FileChannelImpl.java:248

// in append-mode then position is advanced to end before writing
p = (append) ? nd.size(fd) : position0(fd, -1);

приводит, наконец, к FileDispatcherImpl: 136

static native long More ...size0(FileDescriptor fd) throws IOException;

то, что как native может содержать любую ошибку. Когда дело доходит до протоколов между ними. Я бы сделал это скорее как ошибку в nio/Windows, так как они, возможно, не предвидели каких-либо смешных вещей с RDP внизу.

Похоже, что возвращаемый размер Integer.MAX_VALUE, и указатель файла перемещается туда...

Альтернативная реализация java.io.FileWriter и отсутствие кодировки для сокращения строк кода:

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);
   }

}