(aio) AsynchronousFileChannel writes wrong content using heap ByteBuffer when position != 0

XMLWordPrintable

    • x86_64
    • windows

      ADDITIONAL SYSTEM INFORMATION :
      JDK 26-ea 28
      Windows 11 25H2 OS Build 26200.7392

      A DESCRIPTION OF THE PROBLEM :
      When using heap ByteBuffers to write content to an WindowsAsynchronousFileChannelImpl incorrect data can be written.

      REGRESSION : Last worked in version 25.0.1

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Create a temporary file with an initial set of text written.
      2. Use AsynchronousFileChannel.open to write ByteBuffers to a certain range of the file to replace content.
      3. For each buffer written, ensure the entire ByteBuffer is written and use the results of the Future to update the write position.

      Repeat these steps using both direct and heap ByteBuffers as the update source.

      Additional information, adding a delay between file writes doesn't alleviate the issue either, nor does moving the validation outside of the try-with-resources block for the AsynchronousFileChannel.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Both heap and direct buffers should result in the same final file being written.
      ACTUAL -
      The heap ByteBuffer case wrote the second of the two ByteBuffers being written as zeros. Instead of writing bytes [115, 116] it wrote [0, 0].

      ---------- BEGIN SOURCE ----------
      public class Main {
          private static final String REPLACEMENT = "test";
          private static final String ORIGINAL = "hello there";
          private static final String EXPECTED = "testo there";

          public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
              usingDirectBuffers();
              usingHeapBuffers();
          }

          private static void usingDirectBuffers() throws IOException, ExecutionException, InterruptedException {
              File file = createFileIfNotExist();
              byte[] bytes = REPLACEMENT.getBytes(StandardCharsets.UTF_8);
              ByteBuffer buffer1 = ByteBuffer.allocateDirect(2);
              buffer1.put(0, bytes, 0, 2);
              buffer1.position(0);

              ByteBuffer buffer2 = ByteBuffer.allocateDirect(2);
              buffer2.put(0, bytes, 2, 2);
              buffer2.position(0);

              writeBuffersAndValidate(file, List.of(buffer1, buffer2), "direct");
          }

          private static void usingHeapBuffers() throws IOException, ExecutionException, InterruptedException {
              File file = createFileIfNotExist();
              byte[] bytes = REPLACEMENT.getBytes(StandardCharsets.UTF_8);
              ByteBuffer buffer1 = ByteBuffer.wrap(bytes, 0, 2);
              ByteBuffer buffer2 = ByteBuffer.wrap(bytes, 2, 2);

              writeBuffersAndValidate(file, List.of(buffer1, buffer2), "heap");
          }

          private static void writeBuffersAndValidate(File file, List<ByteBuffer> buffers, String bufferType)
              throws IOException, ExecutionException, InterruptedException {
              try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.WRITE)) {
                  long position = 0;
                  for (ByteBuffer buffer : buffers) {
                      while (buffer.hasRemaining()) {
                          position += channel.write(buffer, position).get();
                      }
                  }

                  String writtenResult = Files.readString(file.toPath());
                  if (!EXPECTED.equals(writtenResult)) {
                      System.out.printf(
                          "Written file doesn't match expected output using %s ByteBuffers. Actual: %s, expected: %s%n",
                          bufferType, writtenResult, EXPECTED);
                  }
              }
          }

          private static File createFileIfNotExist() {
              String fileName = UUID.randomUUID().toString();
              File file = new File("target");
              if (!file.exists()) {
                  if (!file.getParentFile().mkdirs()) {
                      throw new RuntimeException("Unable to create directories: " + file.getAbsolutePath());
                  }
              }

              try {
                  File tempFile = Files.createTempFile(file.toPath(), fileName, "").toFile();
                  Files.writeString(tempFile.toPath(), ORIGINAL);
                  return tempFile;
              } catch (IOException ex) {
                  throw new UncheckedIOException(ex);
              }
          }
      }
      ---------- END SOURCE ----------

            Assignee:
            Alan Bateman
            Reporter:
            Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated: