LinkedTransferQueue.poll() returns null even though queue is not empty

XMLWordPrintable

    • Type: Bug
    • Resolution: Fixed
    • Priority: P3
    • 26
    • Affects Version/s: 21, 22, 23, 24, 25
    • Component/s: core-libs
    • Environment:

      Happens on both Mac OS X ARM-64 and Linux. Did not try Windows.

      When multiple threads offer() and poll() items into a LinkedTransferQueue, it can happen that poll() returns null, even though the queue is not actually empty.

      Sample code:

      import java.util.*;
      import java.util.concurrent.*;

      public class LinkedTransferQueueTest {
          public static void main(String... args) throws Throwable {
              test(new LinkedTransferQueue<>());
              test(new LinkedBlockingQueue<>());
              test(new LinkedBlockingDeque<>());
              test(new ArrayBlockingQueue<>(10));
              var q = new LinkedTransferQueue<Integer>();
          }

          private static void test(BlockingQueue<Integer> q) throws ExecutionException, InterruptedException {
              System.out.println(q.getClass());
              try (var pool = Executors.newCachedThreadPool()) {
                  var futures = new ArrayList<Future<List<Integer>>>();
                  var phaser = new Phaser(4);
                  for (var i = 0; i < 4; i++) {
                      futures.add(pool.submit(() -> {
                          var failedPolls = new ArrayList<Integer>();
                          for (int k = 0; k < 10; k++) {
                              for (int j = 0; j < 1000; j++) {
                                  if (!q.offer(j))
                                      throw new AssertionError("offer() failed for j=" + j);
                                  if (q.peek() == null)
                                      throw new AssertionError("peek() failed for j=" + j);
                                  if (q.peek() == null)
                                      throw new AssertionError("peek() failed for j=" + j);
                                  while (q.poll() == null)
                                      failedPolls.add(j);
                              }
                              phaser.arriveAndAwaitAdvance();
                              q.clear();
                              phaser.arriveAndAwaitAdvance();
                          }
                          return failedPolls;
                      }));
                  }
                  var futureNumber = 0;
                  for (var future : futures) {
                      var failedPolls = future.get();
                      if (failedPolls.size() > 0) {
                          System.out.println(futureNumber +
                                             ": failedPolls = " + failedPolls);
                      }
                      futureNumber++;
                  }
              }
          }
      }

      Expected output:

      class java.util.concurrent.LinkedTransferQueue
      class java.util.concurrent.LinkedBlockingQueue
      class java.util.concurrent.LinkedBlockingDeque
      class java.util.concurrent.ArrayBlockingQueue

      Actual output:

      class java.util.concurrent.LinkedTransferQueue
      0: failedPolls = [590, 971, 581, 629, 652, 632, 439, 651, 820, 918, 555]
      1: failedPolls = [682, 516, 14, 426, 510, 607, 658, 679, 699, 811, 812, 813, 828, 862, 876]
      2: failedPolls = [151, 497, 524, 547, 609, 617, 759, 795, 826, 843, 845, 866, 988]
      3: failedPolls = [43, 7, 20, 929, 963, 976, 875, 900, 982, 200]
      class java.util.concurrent.LinkedBlockingQueue
      class java.util.concurrent.LinkedBlockingDeque
      class java.util.concurrent.ArrayBlockingQueue

            Assignee:
            Viktor Klang
            Reporter:
            Heinz Kabutz
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: