Skip to content

8349543: LinkedBlockingDeque does not immediately throw InterrruptedException in put/take#23464

Closed
kabutz wants to merge 2 commits into
openjdk:masterfrom
kabutz:juc-lbd-interruptedexception
Closed

8349543: LinkedBlockingDeque does not immediately throw InterrruptedException in put/take#23464
kabutz wants to merge 2 commits into
openjdk:masterfrom
kabutz:juc-lbd-interruptedexception

Conversation

@kabutz
Copy link
Copy Markdown
Contributor

@kabutz kabutz commented Feb 5, 2025

The LinkedBlockingDeque does not behave consistently with other concurrency components. If we call putFirst(), putLast(), takeFirst(), or takeLast() with a thread that is interrupted, it does not immediately throw an InterruptedException, the way that ArrayBlockingQueue and LInkedBlockingQueue does, because instead of lockInterruptibly(), we call lock(). It will only throw an InterruptedException if the queue is full (on put) or empty (on take). Since interruptions are frequently used as a shutdown mechanism, this might prevent code from ever shutting down.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Integration blocker

 ⚠️ Title mismatch between PR and JBS for issue JDK-8349543

Issue

  • JDK-8349543: LinkedBlockingDeque does not immediately throw InterruptedException in put/take (Bug - P4) ⚠️ Title mismatch between PR and JBS.

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/23464/head:pull/23464
$ git checkout pull/23464

Update a local copy of the PR:
$ git checkout pull/23464
$ git pull https://git.openjdk.org/jdk.git pull/23464/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 23464

View PR using the GUI difftool:
$ git pr show -t 23464

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/23464.diff

Using Webrev

Link to Webrev Comment

…lready interrupted when putXXX() and takeXXX() are called
…ast() all throw InterruptedException immediately if the thread is interrupted.
@bridgekeeper
Copy link
Copy Markdown

bridgekeeper Bot commented Feb 5, 2025

👋 Welcome back kabutz! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link
Copy Markdown

openjdk Bot commented Feb 5, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk
Copy link
Copy Markdown

openjdk Bot commented Feb 5, 2025

@kabutz The following label will be automatically applied to this pull request:

  • core-libs

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk Bot added the core-libs core-libs-dev@openjdk.org label Feb 5, 2025
@DougLea
Copy link
Copy Markdown
Contributor

DougLea commented Feb 5, 2025

Thanks for finding this. The only question is whether we believe that any existing usage might depend on current behavior, and if so should it be done anyway?

@kabutz kabutz changed the title LinkedBlockingDeque does not immediately throw InterrruptedException in put/take 8349543: LinkedBlockingDeque does not immediately throw InterrruptedException in put/take Feb 6, 2025
@openjdk openjdk Bot added the rfr Pull request is ready for review label Feb 6, 2025
@mlbridge
Copy link
Copy Markdown

mlbridge Bot commented Feb 6, 2025

Webrevs

@kabutz
Copy link
Copy Markdown
Contributor Author

kabutz commented Feb 6, 2025

Thanks for finding this. The only question is whether we believe that any existing usage might depend on current behavior, and if so should it be done anyway?

Good question - your call Doug.

LinkedBlockingDeque's comment is the same as LInkedBlockingQueue's (inherited from BlockingQueue): "@throws InterruptedException if interrupted while waiting" - however all the other classes that do throw this InterruptedException would do so on entry, even if we are not technically waiting.

Other examples where this behaviour is consistent is LinkedTransferQueue.take(), PriorityBlockingQueue.take(), etc.:

import java.util.concurrent.*;

public class InterruptionsIgnoredOnTakeOtherQueues {
    public static void main(String... args) throws InterruptedException {
        // Ensure that take() immediately throws an InterruptedException
        // if the thread is interrupted for other BlockingQueues
        try (var pool = Executors.newSingleThreadExecutor()) {
            var success = pool.submit(() -> {
                test(new LinkedTransferQueue<>());
                test(new PriorityBlockingQueue<>());
                test(new ArrayBlockingQueue<>(10));
                test(new LinkedBlockingQueue<>());
                return null;
            });
            try {
                success.get();
            } catch (ExecutionException e) {
                try {
                    throw e.getCause();
                } catch (Error | RuntimeException unchecked) {
                    throw unchecked;
                } catch (Throwable cause) {
                    throw new AssertionError(cause);
                }
            }
        }
    }

    private static void test(BlockingQueue<Integer> queue) {
        queue.add(42);
        Thread.currentThread().interrupt();
        try {
            queue.take();
            fail("Expected InterruptedException in " + queue.getClass().getSimpleName() + ".take()");
        } catch (InterruptedException expected) {
            // good that's what we want
        }
    }

    private static void fail(String message) {
        throw new AssertionError(message);
    }
}

Copy link
Copy Markdown
Member

@Martin-Buchholz Martin-Buchholz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will give this review a bit of my 1% openjdk time

@AlanBateman
Copy link
Copy Markdown
Contributor

I think this is okay. Anything calling these methods already has to deal with InterruptedException. I see that the implementation offerFirst/offerLast/pollFirst/pollLast is using lockInterruptibly so if usage extends to these methods then there is already the possibility of the exception when calling them with the interrupt status set.

Given the behavior change then a CSR + release notes would be good but I think it's overall.

@bridgekeeper
Copy link
Copy Markdown

bridgekeeper Bot commented Mar 10, 2025

@kabutz This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@kabutz
Copy link
Copy Markdown
Contributor Author

kabutz commented Mar 10, 2025

Should we drop this PR or continue with it?

I noticed that the timed offer() and poll() methods are consistent with the way that LinkedBlockingQueue works, in other words, they throw the InterruptedException as soon as we enter the methods. They use lock.lockInterruptibly(), which I was suggesting we use for put() and take().

import java.util.concurrent.*;

public class OfferPollLBD {
    public static void main(String... args) {
        var lbd = new LinkedBlockingDeque<Integer>();
        Thread.currentThread().interrupt();
        System.out.println(lbd.offer(1)); // should work
        System.out.println(lbd.add(2)); // should work
        try {
            System.out.println(lbd.offer(3, 10, TimeUnit.SECONDS)); // throws IE
        } catch (InterruptedException e) {
            System.out.println("Interrupted");
            Thread.currentThread().interrupt();
        }
        try {
            lbd.put(4); // IMHO should throw IE, but doesn't
            System.out.println("put(4) succeeded");
        } catch (InterruptedException e) {
            System.out.println("Interrupted");
            Thread.currentThread().interrupt();
        }

        System.out.println("lbd = " + lbd);

        System.out.println(lbd.poll()); // should work
        System.out.println(lbd.remove()); // should work
        try {
            System.out.println(lbd.poll(10, TimeUnit.SECONDS)); // throws IE
        } catch (InterruptedException e) {
            System.out.println("Interrupted");
            Thread.currentThread().interrupt();
        }
        try {
            System.out.println(lbd.take());
        } catch (InterruptedException e) {
            System.out.println("Interrupted");
            Thread.currentThread().interrupt();
        }
        System.out.println("lbd = " + lbd);
    }
}

@bridgekeeper
Copy link
Copy Markdown

bridgekeeper Bot commented Apr 7, 2025

@kabutz This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@bridgekeeper
Copy link
Copy Markdown

bridgekeeper Bot commented May 6, 2025

@kabutz This pull request has been inactive for more than 8 weeks and will now be automatically closed. If you would like to continue working on this pull request in the future, feel free to reopen it! This can be done using the /open pull request command.

@bridgekeeper bridgekeeper Bot closed this May 6, 2025
@kabutz
Copy link
Copy Markdown
Contributor Author

kabutz commented May 13, 2025

These changes have been included in #24925

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core-libs core-libs-dev@openjdk.org rfr Pull request is ready for review

Development

Successfully merging this pull request may close these issues.

4 participants