1
1
/*
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.
3
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
4
*
5
5
* 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 {
94
94
// push/append operations, each may introduce another such segment. But they
95
95
// all eventually get resolved by their respective updates of their old tail's
96
96
// "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.
98
98
//
99
99
// A push operation is just a degenerate append, where the object being pushed
100
100
// is both the head and the tail of the list being appended.
101
101
template <typename T, T* volatile * (*next_ptr)(T&)>
102
102
void NonblockingQueue<T, next_ptr>::append(T& first, T& last) {
103
103
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.
104
107
set_next (last, end_marker ());
105
108
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.
117
125
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.
124
128
}
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);
125
132
}
126
133
127
134
template <typename T, T* volatile * (*next_ptr)(T&)>
@@ -161,7 +168,9 @@ bool NonblockingQueue<T, next_ptr>::try_pop(T** node_ptr) {
161
168
// _head to NULL, "helping" the competing try_pop. _head will remain
162
169
// NULL until a subsequent push/append. This is a lost race, and we
163
170
// 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.
165
174
return false ;
166
175
} else {
167
176
// [Clause 1c]
@@ -181,17 +190,21 @@ bool NonblockingQueue<T, next_ptr>::try_pop(T** node_ptr) {
181
190
// Any further try_pops will consider the queue empty until a
182
191
// push/append completes by installing a new head.
183
192
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].
189
196
190
197
// Attempt to change the queue head from result to NULL. Failure of the
191
198
// 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].
193
200
Atomic::cmpxchg (&_head, result, (T*)NULL );
194
201
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
+
195
208
// The queue has been restored to order, and we can return the result.
196
209
*node_ptr = result;
197
210
return true ;
0 commit comments