|
34 | 34 | import java.util.NoSuchElementException;
|
35 | 35 | import java.util.concurrent.ArrayBlockingQueue;
|
36 | 36 | import java.util.concurrent.BlockingQueue;
|
| 37 | +import java.util.concurrent.ConcurrentLinkedQueue; |
37 | 38 | import java.util.concurrent.ExecutionException;
|
38 | 39 | import java.util.concurrent.Executor;
|
39 | 40 | import java.util.concurrent.Future;
|
40 |
| -import java.util.concurrent.LinkedBlockingQueue; |
| 41 | +import java.util.concurrent.locks.LockSupport; |
41 | 42 | import java.util.logging.Level;
|
42 | 43 | import java.util.logging.Logger;
|
43 | 44 | import javax.annotation.Nullable;
|
@@ -627,32 +628,54 @@ public void onClose(Status status, Metadata trailers) {
|
627 | 628 | }
|
628 | 629 | }
|
629 | 630 |
|
630 |
| - private static final class ThreadlessExecutor implements Executor { |
| 631 | + @SuppressWarnings("serial") |
| 632 | + private static final class ThreadlessExecutor extends ConcurrentLinkedQueue<Runnable> |
| 633 | + implements Executor { |
631 | 634 | private static final Logger log = Logger.getLogger(ThreadlessExecutor.class.getName());
|
632 | 635 |
|
633 |
| - private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); |
| 636 | + private volatile Thread waiter; |
634 | 637 |
|
635 | 638 | // Non private to avoid synthetic class
|
636 | 639 | ThreadlessExecutor() {}
|
637 | 640 |
|
638 | 641 | /**
|
639 | 642 | * Waits until there is a Runnable, then executes it and all queued Runnables after it.
|
| 643 | + * Must only be called by one thread at a time. |
640 | 644 | */
|
641 | 645 | public void waitAndDrain() throws InterruptedException {
|
642 |
| - Runnable runnable = queue.take(); |
643 |
| - while (runnable != null) { |
| 646 | + final Thread currentThread = Thread.currentThread(); |
| 647 | + throwIfInterrupted(currentThread); |
| 648 | + Runnable runnable = poll(); |
| 649 | + if (runnable == null) { |
| 650 | + waiter = currentThread; |
| 651 | + try { |
| 652 | + while ((runnable = poll()) == null) { |
| 653 | + LockSupport.park(this); |
| 654 | + throwIfInterrupted(currentThread); |
| 655 | + } |
| 656 | + } finally { |
| 657 | + waiter = null; |
| 658 | + } |
| 659 | + } |
| 660 | + do { |
644 | 661 | try {
|
645 | 662 | runnable.run();
|
646 | 663 | } catch (Throwable t) {
|
647 | 664 | log.log(Level.WARNING, "Runnable threw exception", t);
|
648 | 665 | }
|
649 |
| - runnable = queue.poll(); |
| 666 | + } while ((runnable = poll()) != null); |
| 667 | + } |
| 668 | + |
| 669 | + private static void throwIfInterrupted(Thread currentThread) throws InterruptedException { |
| 670 | + if (currentThread.isInterrupted()) { |
| 671 | + throw new InterruptedException(); |
650 | 672 | }
|
651 | 673 | }
|
652 | 674 |
|
653 | 675 | @Override
|
654 | 676 | public void execute(Runnable runnable) {
|
655 |
| - queue.add(runnable); |
| 677 | + add(runnable); |
| 678 | + LockSupport.unpark(waiter); // no-op if null |
656 | 679 | }
|
657 | 680 | }
|
658 | 681 | }
|
0 commit comments