Skip to content

Commit d658d94

Browse files
author
Kim Barrett
committedFeb 8, 2022
8280828: Improve invariants in NonblockingQueue::append
Reviewed-by: iwalulya, tschatzl
1 parent 5fb56db commit d658d94

File tree

1 file changed

+39
-26
lines changed

1 file changed

+39
-26
lines changed
 

‎src/hotspot/share/utilities/nonblockingQueue.inline.hpp

+39-26
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -94,34 +94,41 @@ size_t NonblockingQueue<T, next_ptr>::length() const {
9494
// push/append operations, each may introduce another such segment. But they
9595
// all eventually get resolved by their respective updates of their old tail's
9696
// "next" value. This also means that try_pop operation must handle an object
97-
// with a NULL "next" value specially.
97+
// differently depending on its "next" value.
9898
//
9999
// A push operation is just a degenerate append, where the object being pushed
100100
// is both the head and the tail of the list being appended.
101101
template<typename T, T* volatile* (*next_ptr)(T&)>
102102
void NonblockingQueue<T, next_ptr>::append(T& first, T& last) {
103103
assert(next(last) == NULL, "precondition");
104+
// Make last the new end of the queue. Any further push/appends will
105+
// extend after last. We will try to extend from the previous end of
106+
// queue.
104107
set_next(last, end_marker());
105108
T* old_tail = Atomic::xchg(&_tail, &last);
106-
bool is_old_tail_null = (old_tail == NULL);
107-
if (is_old_tail_null ||
108-
// Try to install first as old_tail's next.
109-
!is_end(Atomic::cmpxchg(next_ptr(*old_tail), end_marker(), &first))) {
110-
// Install first as the new head if either
111-
// (1) the list was empty, or
112-
// (2) a concurrent try_pop claimed old_tail, so it is no longer in the list.
113-
// Note that multiple concurrent push/append operations cannot modify
114-
// _head simultaneously, because the Atomic::xchg() above orders these
115-
// push/append operations so they perform Atomic::cmpxchg() on different
116-
// old_tail. Thus, the cmpxchg can only fail because of a concurrent try_pop.
109+
if (old_tail == NULL) {
110+
// If old_tail is NULL then the queue was empty, and _head must also be
111+
// NULL. The correctness of this assertion depends on try_pop clearing
112+
// first _head then _tail when taking the last entry.
113+
assert(Atomic::load(&_head) == NULL, "invariant");
114+
// Fall through to common update of _head.
115+
} else if (is_end(Atomic::cmpxchg(next_ptr(*old_tail), end_marker(), &first))) {
116+
// Successfully extended the queue list from old_tail to first. No
117+
// other push/append could have competed with us, because we claimed
118+
// old_tail for extension. We won any races with try_pop by changing
119+
// away from end-marker. So we're done.
120+
return;
121+
} else {
122+
// A concurrent try_pop has claimed old_tail, so it is no longer in the
123+
// list. The queue was logically empty. _head is either NULL or
124+
// old_tail, depending on how far try_pop operations have progressed.
117125
DEBUG_ONLY(T* old_head = Atomic::load(&_head);)
118-
// If old_tail is NULL, old_head could be NULL, or an unseen object
119-
// that is being popped. Otherwise, old_head must be either NULL
120-
// or the same as old_tail.
121-
assert(is_old_tail_null ||
122-
old_head == NULL || old_head == old_tail, "invariant");
123-
Atomic::store(&_head, &first);
126+
assert((old_head == NULL) || (old_head == old_tail), "invariant");
127+
// Fall through to common update of _head.
124128
}
129+
// The queue was empty, and first should become the new _head. The queue
130+
// will appear to be empty to any further try_pops until done.
131+
Atomic::store(&_head, &first);
125132
}
126133

127134
template<typename T, T* volatile* (*next_ptr)(T&)>
@@ -161,7 +168,9 @@ bool NonblockingQueue<T, next_ptr>::try_pop(T** node_ptr) {
161168
// _head to NULL, "helping" the competing try_pop. _head will remain
162169
// NULL until a subsequent push/append. This is a lost race, and we
163170
// report it as such for consistency, though we could report the queue
164-
// was empty.
171+
// was empty. We don't attempt to further help [Clause 2] by also
172+
// trying to set _tail to NULL, as that would just ensure that one or
173+
// the other cmpxchg is a wasted failure.
165174
return false;
166175
} else {
167176
// [Clause 1c]
@@ -181,17 +190,21 @@ bool NonblockingQueue<T, next_ptr>::try_pop(T** node_ptr) {
181190
// Any further try_pops will consider the queue empty until a
182191
// push/append completes by installing a new head.
183192

184-
// Attempt to change the queue tail from result to NULL. Failure of the
185-
// cmpxchg indicates that a concurrent push/append updated the tail first.
186-
// That operation will eventually recognize the old tail (our result) is
187-
// no longer in the list and update head from the list being appended.
188-
Atomic::cmpxchg(&_tail, result, (T*)NULL);
193+
// The order of the two cmpxchgs doesn't matter algorithmically, but
194+
// dealing with _head first gives a stronger invariant in append, and is
195+
// also consistent with [Clause 1b].
189196

190197
// Attempt to change the queue head from result to NULL. Failure of the
191198
// cmpxchg indicates a concurrent operation updated _head first. That
192-
// could be either a push/extend or a try_pop in [Clause 1b].
199+
// could be either a push/append or a try_pop in [Clause 1b].
193200
Atomic::cmpxchg(&_head, result, (T*)NULL);
194201

202+
// Attempt to change the queue tail from result to NULL. Failure of the
203+
// cmpxchg indicates that a concurrent push/append updated _tail first.
204+
// That operation will eventually recognize the old tail (our result) is
205+
// no longer in the list and update _head from the list being appended.
206+
Atomic::cmpxchg(&_tail, result, (T*)NULL);
207+
195208
// The queue has been restored to order, and we can return the result.
196209
*node_ptr = result;
197210
return true;

0 commit comments

Comments
 (0)
Please sign in to comment.