RFR: 8238761: Asynchronous handshakes [v8]

David Holmes dholmes at openjdk.java.net
Mon Sep 28 06:32:48 UTC 2020


On Thu, 24 Sep 2020 08:18:12 GMT, Robbin Ehn <rehn at openjdk.org> wrote:

>> This patch implements asynchronous handshake, which changes how handshakes works by default. Asynchronous handshakes
>> are target only executed, which they may never be executed. (target may block on socket for the rest of VM lifetime)
>> Since we have several use-cases for them we can have many handshake pending. (should be very rare) To be able handle an
>> arbitrary amount of handshakes this patch adds a per JavaThread queue and heap allocated HandshakeOperations. It's a
>> singly linked list where you push/insert to the end and pop/get from the front. Inserts are done via CAS on first
>> pointer, no lock needed. Pops are done while holding the per handshake state lock, and when working on the first
>> pointer also CAS.  The thread grabbing the handshake state lock for a JavaThread will pop and execute all handshake
>> operations matching the filter. The JavaThread itself uses no filter and any other thread uses the filter of everything
>> except asynchronous handshakes. In this initial change-set there is no need to do any other filtering. If needed
>> filtering can easily be exposed as a virtual method on the HandshakeClosure, but note that filtering causes handshake
>> operation to be done out-order. Since the filter determins who execute the operation and not the invoked method, there
>> is now only one method to call when handshaking one thread.  Some comments about the changes:
>> - HandshakeClosure uses ThreadClosure, since it neat to use the same closure for both alla JavThreads do and Handshake
>>   all threads. With heap allocating it cannot extends StackObj. I tested several ways to fix this, but those very much
>>   worse then this.
>> 
>> - I added a is_handshake_safe_for for checking if it's current thread is operating on itself or the handshaker of that
>>   thread.
>> 
>> - Simplified JVM TI with a JvmtiHandshakeClosure and also made them not needing a JavaThread when executing as a
>>   handshaker on a JavaThread, e.g. VM Thread can execute the handshake operation.
>> 
>> - Added WB testing method.
>> 
>> - Removed VM_HandshakeOneThread, the VM thread uses the same call path as direct handshakes did.
>> 
>> - Changed the handshake semaphores to mutex to be able to handle deadlocks with lock ranking.
>> 
>> - VM_HandshakeAllThreadsis still a VM operation, since we do support half of the threads being handshaked before a
>>   safepoint and half of them after, in many handshake all operations.
>> 
>> - ThreadInVMForHandshake do not need to do a fenced transistion since this is always a transistion from unsafe to unsafe.
>> 
>> - Added NoSafepointVerifyer, we are thinking about supporting safepoints inside handshake, but it's not needed at the
>>   moment. To make sure that gets well tested if added the NoSafepointVerifyer will raise eyebrows.
>> 
>> - Added ttyLocker::break_tty_lock_for_safepoint(os::current_thread_id()); due to lock rank.
>> 
>> - Added filtered queue and gtest for it.
>> 
>> Passes multiple t1-8 runs.
>> Been through some pre-reviwing.
>
> Robbin Ehn has updated the pull request incrementally with one additional commit since the last revision:
> 
>   Add constructor and comment. Previous renames caused confusing, improved names once more and moved non-public to private

Still some naming issues to resolve and an editing pass on various new comments.

Thanks,
David

src/hotspot/share/runtime/handshake.cpp line 51:

> 49:  private:
> 50:   // Must use AsyncHandshakeOperation when using AsyncHandshakeClosure.
> 51:   HandshakeOperation(AsyncHandshakeClosure* cl, JavaThread* target, jlong start_ns) {};

I'm really not understanding the way you have implemented the guard I requested. How does declaring the private
constructor prevent a call to HandShakeOperation(someAsync, target) ?

src/hotspot/share/runtime/handshake.cpp line 202:

> 200: }
> 201:
> 202: static void log_handshake_info(jlong start_time_ns, const char* name, int targets, int non_self_executed, const
> char* extra = NULL) {

The name "non_self_executed" is much clearer - thanks. But this now highlights that the log message itself doesn't make
complete sense as it refers to "requesting thread" when it could be a range of possible threads not just a single
requesting thread.

src/hotspot/share/runtime/handshake.cpp line 239:

> 237:     // Keeps count on how many of own emitted handshakes
> 238:     // this thread execute.
> 239:     int emitted_handshakes_executed = 0;

I suggest:
// Keeps count of how many handshakes were actually executed
// by this thread.
int handshakes_executed = 0;

src/hotspot/share/runtime/handshake.cpp line 326:

> 324:   // Keeps count on how many of own emitted handshakes
> 325:   // this thread execute.
> 326:   int emitted_handshakes_executed = 0;

See previous comment.

src/hotspot/share/runtime/handshake.hpp line 79:

> 77:   // Provides mutual exclusion to this state and queue.
> 78:   Mutex   _lock;
> 79:   // Set to the thread executing the handshake operation during the execution.

s/the execution/its execution/

Or just delete "during the execution"

src/hotspot/share/runtime/handshake.hpp line 87:

> 85:   void process_self_inner();
> 86:
> 87:   bool have_non_self_executable_operation();

API naming consistency is still a problem here. We have:
- pop_for_self()
- pop()

but
- has_operation()  // for use by self/handshakee
- have_non_self_executable_operation()

why not:
- has_operation_for_self()
- has_operation()

?

src/hotspot/share/runtime/handshake.hpp line 100:

> 98:   }
> 99:
> 100:   // Both _queue and _lock must be check. If a thread have seen this _handshakee

s/check/checked/
s/have/has/

src/hotspot/share/runtime/handshake.hpp line 103:

> 101:   // as safe it will execute all possible handshake operations in a loop while
> 102:   // holding _lock. We use lock free addition to the queue, which means it is
> 103:   // possible to the queue to be seen as empty by _handshakee but as non-empty

s/to/for/

src/hotspot/share/runtime/handshake.hpp line 105:

> 103:   // possible to the queue to be seen as empty by _handshakee but as non-empty
> 104:   // by the thread executing in the loop. To avoid the _handshakee eliding
> 105:   // stopping while handshake operations are being executed, the _handshakee

s/eliding stopping/continuing/ ?

src/hotspot/share/runtime/handshake.hpp line 106:

> 104:   // by the thread executing in the loop. To avoid the _handshakee eliding
> 105:   // stopping while handshake operations are being executed, the _handshakee
> 106:   // must take slow if _lock is held and make sure the queue is empty otherwise

Unclear what "slow" refers to - "take the slow path"? But what path is that?

src/hotspot/share/runtime/handshake.hpp line 107:

> 105:   // stopping while handshake operations are being executed, the _handshakee
> 106:   // must take slow if _lock is held and make sure the queue is empty otherwise
> 107:   // try process it.

s/try/try to/

but I'm not sure how this follows the "otherwise" in this sentence.

src/hotspot/share/utilities/filterQueue.hpp line 32:

> 30:
> 31: // The FilterQueue is FIFO with the ability to skip over queued items.
> 32: // The skipping is controlled by using a filter when poping.

s/poping/popping/

src/hotspot/share/utilities/filterQueue.hpp line 33:

> 31: // The FilterQueue is FIFO with the ability to skip over queued items.
> 32: // The skipping is controlled by using a filter when poping.
> 33: // It also supports lock free pushes, while poping (including contain())

s/poping/popping/
s/contain()/contains()/

src/hotspot/share/utilities/filterQueue.hpp line 63:

> 61:
> 62:   // Applies the match_func to the items in the queue until match_func returns
> 63:   // true and then return false, or there is no more items and then returns

Surely "true and then returns true"?
s/there is/there are/

src/hotspot/share/utilities/filterQueue.hpp line 64:

> 62:   // Applies the match_func to the items in the queue until match_func returns
> 63:   // true and then return false, or there is no more items and then returns
> 64:   // false. Any pushed item while executing may or may not have match_func

s/pushed item/item pushed/

I know what you are trying to say about concurrent pushes but it sounds too non-deterministic - any concurrent push
that happens before contains() reaches the end of the queue will have the match_func applied. So in that sense "while
executing" only applies to pushes that will be seen; any push not seen had to have happened after execution was
complete.

src/hotspot/share/utilities/filterQueue.hpp line 66:

> 64:   // false. Any pushed item while executing may or may not have match_func
> 65:   // applied. The method is not re-entrant and must be executed mutually
> 66:   // exclusive other contains and pops calls.

// exclusive to other contains() and pop() calls.

src/hotspot/share/utilities/filterQueue.hpp line 77:

> 75:
> 76:   // Applies the match_func to all items in the queue returns the item which
> 77:   // match_func return true for and was inserted first. Any pushed item while

// Applies the match_func to each item in the queue, in order of insertion, and
// returns the first item for which match_func returns true. Returns false if there are
// no matches or the queue is empty.

src/hotspot/share/utilities/filterQueue.hpp line 80:

> 78:   // executing may or may not have be popped, if popped it was the first
> 79:   // inserted match. The method is not re-entrant and must be executed mutual
> 80:   // exclusive with other contains and pops calls.

See comments on contains() to ensure pop() and contains() use consistent terminology. Thanks.

src/hotspot/share/runtime/handshake.cpp line 389:

> 387: };
> 388:
> 389: static bool non_self_queue_filter(HandshakeOperation* op) {

The name "non_self_queue_filter" is really awkward - sorry. But I think we're going to have to revisit the way
filtering is named and done once we try to generalise it anyway, so I'll let this pass.

-------------

Changes requested by dholmes (Reviewer).

PR: https://git.openjdk.java.net/jdk/pull/151


More information about the serviceability-dev mailing list