Skip to content

8355726: LinkedBlockingDeque fixes and improvements#24925

Closed
kabutz wants to merge 15 commits into
openjdk:masterfrom
kabutz:JDK-8355726-lbd-improvements-and-fixes
Closed

8355726: LinkedBlockingDeque fixes and improvements#24925
kabutz wants to merge 15 commits into
openjdk:masterfrom
kabutz:JDK-8355726-lbd-improvements-and-fixes

Conversation

@kabutz

@kabutz kabutz commented Apr 28, 2025

Copy link
Copy Markdown
Contributor

We logged several bugs on the LinkedBlockingDeque. This aggregates them into a single bug report and PR.

  1. LinkedBlockingDeque does not immediately throw InterruptedException in put/take

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.

  1. LinkedBlockingDeque.clear() should preserve weakly-consistent iterators

LinkedBlockingDeque.clear() should preserve weakly-consistent iterators by linking f.prev and f.next back to f, allowing the iterators to continue from the first or last respectively. This would be consistent with how the other node-based weakly consistent queues LinkedBlockingQueue LinkedTransferQueue, ConcurrentLinkedQueue/Deque work.

The LBD already supports self-linking, since that is done by the unlinkFirst() and unlinkLast() methods, and the iterators and spliterator thus all support self-linking.

This can be fixed very easily by linking both f.prev and f.next back to f.

  1. LinkedBlockingDeque offer() creates nodes even if capacity has been reached

In the JavaDoc of LinkedBlockingDeque, it states: "Linked nodes are dynamically created upon each insertion unless this would bring the deque above capacity." However, in the current implementation, nodes are always created, even if the deque is full. This is because count is non-volatile, and we only check inside the linkFirst/Last() methods whether the queue is full. At this point we have already locked and have created the Node. Instead, the count could be volatile, and we could check before locking.

In the current version, calling offer() on a full LinkedBlockingDeque creates unnecessary objects and contention. Similarly for poll() and peek(), we could exit prior to locking by checking the count field.

  1. LinkedBlockingDeque allows us to overflow size with addAll()

In LinkedBlockingDeque.addAll() we first build up the chain of nodes and then add that chain in bulk to the existing nodes. We count the nodes in "int n" and then whilst holding the lock, we check that we haven't exceeded the capacity with "if (count + n <= capacity)". However, if we pass in a collection that has more than Integer.MAX_VALUE items in it, then we can overflow n, making it negative. Since "count + n" is also negative, we can add the chain to our last item, and thus we end up with a LinkedBlockingDeque with more than Integer.MAX_VALUE of items and a negative size(). stream().count() gives the correct number of items.

This happens both via the bulk add constructor LinkedBlockingDeque(Collection) and when we call addAll(Collection) directly.


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

Issue

  • JDK-8355726: LinkedBlockingDeque fixes and improvements (Bug - P4)

Reviewers

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 24925

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

Using diff file

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

Using Webrev

Link to Webrev Comment

@bridgekeeper

bridgekeeper Bot commented Apr 28, 2025

Copy link
Copy Markdown

👋 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

openjdk Bot commented Apr 28, 2025

Copy link
Copy Markdown

@kabutz This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8355726: LinkedBlockingDeque fixes and improvements

Reviewed-by: vklang, dl

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 783 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@DougLea, @viktorklang-ora) but any other Committer may sponsor as well.

➡️ To flag this PR as ready for integration with the above commit message, type /integrate in a new comment. (Afterwards, your sponsor types /sponsor in a new comment to perform the integration).

@openjdk openjdk Bot added the rfr Pull request is ready for review label Apr 28, 2025
@openjdk

openjdk Bot commented Apr 28, 2025

Copy link
Copy Markdown

@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 Apr 28, 2025
@mlbridge

mlbridge Bot commented Apr 28, 2025

Copy link
Copy Markdown

@kabutz

kabutz commented Apr 28, 2025

Copy link
Copy Markdown
Contributor Author

@viktorklang-ora

Copy link
Copy Markdown
Contributor

/reviewers 2

@openjdk

openjdk Bot commented Apr 28, 2025

Copy link
Copy Markdown

@viktorklang-ora
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@viktorklang-ora

Copy link
Copy Markdown
Contributor

@kabutz Thanks for opening this PR—just confirming that it's on my to-review queue.

@DougLea DougLea left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I agree that these changes make behavior more consistent with LinkedBlockingQueue, which is worth doing. Thanks for finding straightforward ways to do these that don't impact anything else.
Changing count field to volatile and sometimes read outside of lock introduces more potential non-sequential-consistency (for example checking size while adding) but doesn't impact observable guarantees. It may have small performance impact in either direction that could vary across platforms, but I have not detected any.
There are possible (not likely, and still legal according to spec) observable consequences of using lockInterruptibly, but better to have them now not surprisingly different than other blocking queues.

@kabutz

kabutz commented May 2, 2025

Copy link
Copy Markdown
Contributor Author

Thanks @DougLea !

@kabutz

kabutz commented May 7, 2025

Copy link
Copy Markdown
Contributor Author

@viktorklang-ora any idea whom else we can ask to approve this PR?

@viktorklang-ora

Copy link
Copy Markdown
Contributor

@kabutz I think @AlanBateman might be able to have a look as well.

As for timing, it seems to me most reasonable if this PR (if it is to be integrated) to go in after JDK25 has been forked, to give enough time for JDK26 early access feedback and giving the changes time to harden. I hope that makes sense.

@kabutz

kabutz commented May 7, 2025

Copy link
Copy Markdown
Contributor Author

@kabutz I think @AlanBateman might be able to have a look as well.

As for timing, it seems to me most reasonable if this PR (if it is to be integrated) to go in after JDK25 has been forked, to give enough time for JDK26 early access feedback and giving the changes time to harden. I hope that makes sense.

That sound reasonable. When would this be?

Comment thread src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java Outdated
@@ -858,7 +860,7 @@ public boolean addAll(Collection<? extends E> c) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We could likely check if there's any remaining capacity up front, and immediately return false?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We could likely check if there's any remaining capacity up front, and immediately return false?

We could if you like. I wanted to make as few changes as possible, to not introduce unexpected changes. This particular bug was to stop a size overflow.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@DougLea What do you think about checking if the target collection is likely going to fit into the queue early?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Because of races, prechecks can't be exactly accurate, but I think Heinz's current approach is a good choice, and is similar to how it's done elsewhere.

q.clear();
q.add(four);
q.add(five);
q.add(six);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Out of curiosity, how does it.remove() work under these conditions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Out of curiosity, how does it.remove() work under these conditions?

If we call it.remove() on the first element, it delegates to unlinkFirst() (if we are using an ascending iterator), and unlinkLast (if we are using a descending iterator). Similarly, if we call it.remove() on the last element it will call unlinkLast() or unlinkFirst(). With unlinkFirst(), it will make f.next = f (thus linking back to itself) and with unlinkLast(), it will make l.prev = l.

If we call it.remove() on a middle element, then we simply link the p.next = n; n.prev = p; and does not do self-linking. Thus if we have an LBD with 1,2,3,4,5 with two iterators pointing onto 3, if one of them removes it, then the other will continue with 3 (cached), 4, 5, and it won't go back to the beginning and see duplicate elements.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've added a unit test for this.

int n = 0;
long n = 0;
for (E e : c) {
Objects.requireNonNull(e);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This makes me wonder: Does it make sense to create new nodes if we don't track if they will still fit into the capacity?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We could if you like, but that would subtly change the current behaviour. I tried to make as few changes as possible.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

On the other hand, there could be intermediate operations modifying the underlying collection before the lock is obtained, so checking wouldn't help.

@viktorklang-ora

Copy link
Copy Markdown
Contributor

@kabutz I think @AlanBateman might be able to have a look as well.
As for timing, it seems to me most reasonable if this PR (if it is to be integrated) to go in after JDK25 has been forked, to give enough time for JDK26 early access feedback and giving the changes time to harden. I hope that makes sense.

That sound reasonable. When would this be?

About a month or so.

kabutz and others added 2 commits June 11, 2025 12:45
Co-authored-by: Viktor Klang <viktor.klang@oracle.com>
Co-authored-by: Viktor Klang <viktor.klang@oracle.com>
Comment thread src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java Outdated
Comment thread src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java Outdated

@viktorklang-ora viktorklang-ora left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just made some final suggestions, then I think this is good to go.
Great job on this, @kabutz—I really appreciate you taking this on!

I'll have a look once you've commented/addressed the comments, and I'll move this forward after that.

kabutz and others added 2 commits June 11, 2025 22:39
…gDeque.java

Co-authored-by: Viktor Klang <viktor.klang@oracle.com>
…gDeque.java

Co-authored-by: Viktor Klang <viktor.klang@oracle.com>

@viktorklang-ora viktorklang-ora left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great work, @kabutz — this is now ready to integrate.

@kabutz

kabutz commented Jun 12, 2025

Copy link
Copy Markdown
Contributor Author

/integrate

@openjdk

openjdk Bot commented Jun 12, 2025

Copy link
Copy Markdown

@kabutz This pull request has not yet been marked as ready for integration.

@kabutz

kabutz commented Jun 12, 2025

Copy link
Copy Markdown
Contributor Author

Great work, @kabutz — this is now ready to integrate.

What are the next steps for me to do ?

@viktorklang-ora

Copy link
Copy Markdown
Contributor

@kabutz Apologies, I'll let you know as soon as you can /integrate this.

@viktorklang-ora

Copy link
Copy Markdown
Contributor

Changing to 1 reviewer after discussing with @AlanBateman

@viktorklang-ora

Copy link
Copy Markdown
Contributor

/reviewers 1

@openjdk

openjdk Bot commented Jun 12, 2025

Copy link
Copy Markdown

@viktorklang-ora
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 1 (with at least 1 Reviewer).

@openjdk openjdk Bot added the ready Pull request is ready to be integrated label Jun 12, 2025
@viktorklang-ora

Copy link
Copy Markdown
Contributor

@kabutz This is now ready for integration. After you've commented with "/integrate", I'll sponsor the integration.

@kabutz

kabutz commented Jun 12, 2025

Copy link
Copy Markdown
Contributor Author

/integrate

@openjdk openjdk Bot added the sponsor Pull request is ready to be sponsored label Jun 12, 2025
@openjdk

openjdk Bot commented Jun 12, 2025

Copy link
Copy Markdown

@kabutz
Your change (at version 6bdd22f) is now ready to be sponsored by a Committer.

@viktorklang-ora

Copy link
Copy Markdown
Contributor

/sponsor

@openjdk

openjdk Bot commented Jun 12, 2025

Copy link
Copy Markdown

Going to push as commit 91fdd72.
Since your change was applied there have been 783 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk Bot added the integrated Pull request has been integrated label Jun 12, 2025
@openjdk openjdk Bot closed this Jun 12, 2025
@openjdk openjdk Bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review sponsor Pull request is ready to be sponsored labels Jun 12, 2025
@viktorklang-ora

Copy link
Copy Markdown
Contributor

Thanks @kabutz!

@openjdk

openjdk Bot commented Jun 12, 2025

Copy link
Copy Markdown

@viktorklang-ora @kabutz Pushed as commit 91fdd72.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@kabutz kabutz deleted the JDK-8355726-lbd-improvements-and-fixes branch June 12, 2025 12:13
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 integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

3 participants