-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Implement parallel executor service without ForkJoinPool
#5060
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
* More precise clock * Fixed width thread names * Abbreviated package names
To avoid race conditions with other workers that lead to stalling.
…entation" This reverts commit 9d77839.
|
I changed how to opt-in to the feature and have updated the docs. @mpkorstanje I'd appreciate your feedback! 🙂 |
| + PARALLEL_CONFIG_EXECUTOR_SERVICE_PROPERTY_NAME + "' configuration parameter to '" | ||
| + WORKER_THREAD_POOL + "' and report any issues to the JUnit team. " | ||
| + "Alternatively, set the configuration parameter to '" + FORK_JOIN_POOL | ||
| + "' to hide this message."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| + "' to hide this message."); | |
| + "' to hide this message and use the original implementation."); |
|
|
||
| * Support for creating a `ModuleSelector` from a `java.lang.Module` and using | ||
| its classloader for test discovery. | ||
| * New `WorkerThreadPoolHierarchicalTestExecutorService` implementation of parallel test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * New `WorkerThreadPoolHierarchicalTestExecutorService` implementation of parallel test | |
| * New `WorkerThreadPoolHierarchicalTestExecutorService` implementation used for parallel test |
| itself prior to waiting for its children to finish. In case it does need to block, it temporarily gives up its | ||
| worker lease and starts another worker thread to compensate for the reduced `parallelism`. If the max pool size | ||
| does not permit starting another thread, that is ignored in case there are still other active worker threads. | ||
| The same happens in case a resource lock needs to be acquired. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This reads like a flow of consciousness.
I'd suggest rewriting it to <objective> <action> format.
This implementation is based on a regular thread pool and a work queue shared among all worker threads.
Each worker thread scans the shared work queue for tasks to run. Since the tasks represent hierarchically
structured tests, container tasks will call `submit(TestTask)` or `invokeAll(List<TestTask>)` for their
children, recursively.
To maintain the desired parallelism -- regardless whether the user code performs any blocking operations --
a fixed number of worker leases is configured. Whenever a task is submitted to the work queue to be executed
concurrently, an attempt is made to acquire a worker lease. If a worker lease was acquired, a worker thread is
started. Each worker thread attempts to "steal" queue entries for its children and execute them itself prior to waiting
for its children to finish.
To optimize CPU utilization, whenever a worker thread does need to block, it temporarily gives up its worker
lease and attempts to start another worker thread to compensate for the reduced `parallelism`. If the max pool size does
not permit starting another thread, the attempt is ignored in case there are still other active worker threads.
The same happens in case a resource lock needs to be acquired.
To minimize the number of idle workers, worker threads will prefer to steal top level tasks, while working
through their own task hierarchy in a depth first fashion. Furthermore, child tasks with execution mode
`CONCURRENT` are submitted to the shared queue be prior to executing those with execution mode `SAME_THREAD`
directly.
| } | ||
|
|
||
| private static class WorkQueue implements Iterable<WorkQueue.Entry> { | ||
| private final AtomicLong index = new AtomicLong(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having some second thoughts about this. This is an exhaustible resource, especially compared to ConcurrentSkipListSet. Not that I think any one could reasonably exhaust it, but those are famous last words.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've had a look at replacing the ConcurrentSkipListSet with a PriorityBlockingQueue. Because we depend on both queue and iteration order, the PriorityBlockingQueue with its "messy" iteration order can't be used because it is not guaranteed that the executing thread and work stealer will approach each other from opposite directions..
Resolved the potential exhaustion in 1f9ffde by using the UniqueId to create an arbitrary ordering. This however meant that invokeAll order wasn't used any more. 592a05e solves that.
This avoids an exhaustable index and potentially famous last words.
…on-custom-implementation
This PR introduces a new implementation of
HierarchicalTestExecutorServicethat runs rests in parallel and has limited work stealing capabilities but is not based onForkJoinPool. It avoids its pitfalls (such as #3945) and #3108 but may require additional threads because its work stealing is limited to direct children. Contrary to theForkJoinPoolimplementation, the new executor service guarantees that no more thanparallelismtest nodes are executed in parallel.My intention is to initially ship this implementation as an opt-in feature (via the new
junit.jupiter.execution.parallel.executorconfiguration parameter) in 6.1, make it an opt-out feature in 6.2, and drop support for theForkJoinPool-based implementation in a later to-be-determined release.The PR is not yet finished but feedback is already welcome! If you use parallel test execution in your projects (or other test engines), it would be great if you could try out the new implementation and report your observations.
Resolves #3108.
I hereby agree to the terms of the JUnit Contributor License Agreement.
Definition of Done
@APIannotations