| 1 | /* | |
| 2 | * Copyright 2014-2026 Grzegorz Piwowarek, https://4comprehension.com/ | |
| 3 | * | |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | * you may not use this file except in compliance with the License. | |
| 6 | * You may obtain a copy of the License at | |
| 7 | * | |
| 8 | * https://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | * | |
| 10 | * Unless required by applicable law or agreed to in writing, software | |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | * See the License for the specific language governing permissions and | |
| 14 | * limitations under the License. | |
| 15 | */ | |
| 16 | package com.pivovarit.collectors; | |
| 17 | ||
| 18 | import java.lang.ref.Cleaner; | |
| 19 | import java.util.Objects; | |
| 20 | import java.util.Set; | |
| 21 | import java.util.concurrent.BlockingQueue; | |
| 22 | import java.util.concurrent.CompletableFuture; | |
| 23 | import java.util.concurrent.ConcurrentHashMap; | |
| 24 | import java.util.concurrent.Executor; | |
| 25 | import java.util.concurrent.FutureTask; | |
| 26 | import java.util.concurrent.LinkedBlockingQueue; | |
| 27 | import java.util.concurrent.RejectedExecutionException; | |
| 28 | import java.util.concurrent.Semaphore; | |
| 29 | import java.util.concurrent.ThreadFactory; | |
| 30 | import java.util.concurrent.atomic.AtomicReference; | |
| 31 | import java.util.function.Consumer; | |
| 32 | import java.util.function.Supplier; | |
| 33 | ||
| 34 | import static com.pivovarit.collectors.Preconditions.requireValidExecutor; | |
| 35 | ||
| 36 | /** | |
| 37 | * @author Grzegorz Piwowarek | |
| 38 | */ | |
| 39 | final class Dispatcher<T> { | |
| 40 | ||
| 41 | private static final Cleaner CLEANER = Cleaner.create(); | |
| 42 | ||
| 43 | private final AtomicReference<Throwable> failure = new AtomicReference<>(); | |
| 44 | private final Set<InterruptibleCompletableFuture<T>> pendingFutures = ConcurrentHashMap.newKeySet(); | |
| 45 | private final BlockingQueue<DispatchItem> workingQueue = new LinkedBlockingQueue<>(); | |
| 46 | ||
| 47 | private final ThreadFactory dispatcherThreadFactory = Thread.ofVirtual() | |
| 48 | .name("parallel-collectors-dispatcher-", 0) | |
| 49 | .factory(); | |
| 50 | ||
| 51 | private final Consumer<Thread> dispatcherThreadHook; | |
| 52 | ||
| 53 | private final Executor executor; | |
| 54 | private final Semaphore limiter; | |
| 55 | ||
| 56 | // UNSTARTED -> RUNNING -> SHUTTING_DOWN | |
| 57 | enum State { | |
| 58 | UNSTARTED, RUNNING, SHUTTING_DOWN | |
| 59 | } | |
| 60 | ||
| 61 | private final AtomicReference<State> state = new AtomicReference<>(State.UNSTARTED); | |
| 62 | ||
| 63 | Dispatcher(Executor executor, int permits, Consumer<Thread> dispatcherThreadHook) { | |
| 64 |
1
1. <init> : removed call to com/pivovarit/collectors/Preconditions::requireValidExecutor → SURVIVED |
requireValidExecutor(executor); |
| 65 | this.executor = executor; | |
| 66 | this.dispatcherThreadHook = dispatcherThreadHook; | |
| 67 | this.limiter = new Semaphore(permits); | |
| 68 | } | |
| 69 | ||
| 70 | Dispatcher(Executor executor, int permits) { | |
| 71 | this(executor, permits, c -> {}); | |
| 72 | } | |
| 73 | ||
| 74 | Dispatcher(Executor executor) { | |
| 75 | this(executor, c -> {}); | |
| 76 | } | |
| 77 | ||
| 78 | Dispatcher(Executor executor, Consumer<Thread> dispatcherThreadHook) { | |
| 79 |
1
1. <init> : removed call to com/pivovarit/collectors/Preconditions::requireValidExecutor → SURVIVED |
requireValidExecutor(executor); |
| 80 | this.executor = executor; | |
| 81 | this.dispatcherThreadHook = dispatcherThreadHook; | |
| 82 | this.limiter = null; | |
| 83 | } | |
| 84 | ||
| 85 | void start() { | |
| 86 |
1
1. start : negated conditional → TIMED_OUT |
if (state.compareAndSet(State.UNSTARTED, State.RUNNING)) { |
| 87 | var thread = dispatcherThreadFactory.newThread(() -> { | |
| 88 | try { | |
| 89 | while (true) { | |
| 90 | switch (workingQueue.take()) { | |
| 91 | case DispatchItem.Task(FutureTask<?> task) -> { | |
| 92 | try { | |
| 93 |
1
1. lambda$start$0 : negated conditional → KILLED |
if (limiter != null) { |
| 94 |
1
1. lambda$start$0 : removed call to java/util/concurrent/Semaphore::acquire → KILLED |
limiter.acquire(); |
| 95 | } | |
| 96 | } catch (InterruptedException e) { | |
| 97 |
1
1. lambda$start$0 : removed call to com/pivovarit/collectors/Dispatcher::abort → NO_COVERAGE |
abort(e); |
| 98 | return; | |
| 99 | } | |
| 100 | try { | |
| 101 |
2
1. lambda$start$0 : removed call to com/pivovarit/collectors/Dispatcher::retry → TIMED_OUT 2. lambda$start$1 : removed call to java/util/concurrent/Executor::execute → TIMED_OUT |
retry(() -> executor.execute(() -> { |
| 102 | try { | |
| 103 |
1
1. lambda$start$2 : removed call to java/util/concurrent/FutureTask::run → TIMED_OUT |
task.run(); |
| 104 | } finally { | |
| 105 |
1
1. lambda$start$2 : negated conditional → KILLED |
if (task.isCancelled()) { |
| 106 | Thread.interrupted(); | |
| 107 | } | |
| 108 |
1
1. lambda$start$2 : negated conditional → TIMED_OUT |
if (limiter != null) { |
| 109 |
1
1. lambda$start$2 : removed call to java/util/concurrent/Semaphore::release → TIMED_OUT |
limiter.release(); |
| 110 | } | |
| 111 | } | |
| 112 | })); | |
| 113 | } catch (RejectedExecutionException e) { | |
| 114 |
1
1. lambda$start$0 : negated conditional → SURVIVED |
if (limiter != null) { |
| 115 |
1
1. lambda$start$0 : removed call to java/util/concurrent/Semaphore::release → SURVIVED |
limiter.release(); |
| 116 | } | |
| 117 | ||
| 118 | throw e; | |
| 119 | } | |
| 120 | } | |
| 121 | case DispatchItem.Stop ignored -> { | |
| 122 | return; | |
| 123 | } | |
| 124 | } | |
| 125 | } | |
| 126 | } catch (Throwable e) { | |
| 127 |
1
1. lambda$start$0 : removed call to com/pivovarit/collectors/Dispatcher::abort → KILLED |
abort(e); |
| 128 | } | |
| 129 | }); | |
| 130 |
1
1. start : removed call to java/util/function/Consumer::accept → KILLED |
dispatcherThreadHook.accept(thread); |
| 131 |
1
1. start : removed call to java/lang/Thread::start → TIMED_OUT |
thread.start(); |
| 132 | } | |
| 133 | } | |
| 134 | ||
| 135 | void stop() { | |
| 136 |
1
1. stop : negated conditional → TIMED_OUT |
if (state.compareAndSet(State.RUNNING, State.SHUTTING_DOWN)) { |
| 137 | workingQueue.add(DispatchItem.Stop.POISON_PILL); | |
| 138 | } | |
| 139 | } | |
| 140 | ||
| 141 | Cleaner.Cleanable registerTerminationGuard(Object guard) { | |
| 142 | var queue = workingQueue; | |
| 143 | var currentState = state; | |
| 144 | ||
| 145 |
1
1. registerTerminationGuard : replaced return value with null for com/pivovarit/collectors/Dispatcher::registerTerminationGuard → KILLED |
return CLEANER.register(guard, () -> { |
| 146 |
1
1. lambda$registerTerminationGuard$0 : negated conditional → TIMED_OUT |
if (currentState.compareAndSet(State.RUNNING, State.SHUTTING_DOWN)) { |
| 147 | queue.add(DispatchItem.Stop.POISON_PILL); | |
| 148 | } | |
| 149 | }); | |
| 150 | } | |
| 151 | ||
| 152 | boolean wasStarted() { | |
| 153 |
1
1. wasStarted : replaced boolean return with false for com/pivovarit/collectors/Dispatcher::wasStarted → SURVIVED |
return switch (state.get()) { |
| 154 | case UNSTARTED -> false; | |
| 155 | case RUNNING, SHUTTING_DOWN -> true; | |
| 156 | }; | |
| 157 | } | |
| 158 | ||
| 159 | boolean wasShutdown() { | |
| 160 |
1
1. wasShutdown : replaced boolean return with false for com/pivovarit/collectors/Dispatcher::wasShutdown → TIMED_OUT |
return switch (state.get()) { |
| 161 | case UNSTARTED, RUNNING -> false; | |
| 162 | case SHUTTING_DOWN -> true; | |
| 163 | }; | |
| 164 | } | |
| 165 | ||
| 166 | CompletableFuture<T> submit(Supplier<T> supplier) { | |
| 167 |
1
1. submit : negated conditional → KILLED |
if (state.get() == State.SHUTTING_DOWN) { |
| 168 | throw new IllegalStateException("collector was already used and cannot be reused"); | |
| 169 | } | |
| 170 | var future = new InterruptibleCompletableFuture<T>(); | |
| 171 | var task = new FutureTask<>(() -> { | |
| 172 | try { | |
| 173 | future.complete(supplier.get()); | |
| 174 | } catch (Throwable e) { | |
| 175 |
1
1. lambda$submit$0 : removed call to com/pivovarit/collectors/Dispatcher::abort → TIMED_OUT |
abort(e); |
| 176 | Thread.interrupted(); | |
| 177 | } | |
| 178 | }, null); | |
| 179 |
1
1. submit : removed call to com/pivovarit/collectors/InterruptibleCompletableFuture::completedBy → TIMED_OUT |
future.completedBy(task); |
| 180 | pendingFutures.add(future); | |
| 181 | future.whenComplete((result, ex) -> pendingFutures.remove(future)); | |
| 182 | ||
| 183 | var e = failure.get(); | |
| 184 |
1
1. submit : negated conditional → KILLED |
if (e != null) { |
| 185 |
1
1. submit : removed call to com/pivovarit/collectors/Dispatcher::abort → KILLED |
abort(future, e); |
| 186 |
1
1. submit : replaced return value with null for com/pivovarit/collectors/Dispatcher::submit → KILLED |
return future; |
| 187 | } | |
| 188 | workingQueue.add(new DispatchItem.Task(task)); | |
| 189 |
1
1. submit : replaced return value with null for com/pivovarit/collectors/Dispatcher::submit → KILLED |
return future; |
| 190 | } | |
| 191 | ||
| 192 | private void abort(Throwable e) { | |
| 193 |
1
1. abort : negated conditional → TIMED_OUT |
if (failure.compareAndSet(null, e)) { |
| 194 | for (var future : pendingFutures) { | |
| 195 |
1
1. abort : removed call to com/pivovarit/collectors/Dispatcher::abort → TIMED_OUT |
abort(future, e); |
| 196 | } | |
| 197 | } | |
| 198 | } | |
| 199 | ||
| 200 | private static void abort(InterruptibleCompletableFuture<?> future, Throwable e) { | |
| 201 | future.completeExceptionally(e); | |
| 202 | future.cancel(true); | |
| 203 | } | |
| 204 | ||
| 205 | private static void retry(Runnable runnable) { | |
| 206 | try { | |
| 207 |
1
1. retry : removed call to java/lang/Runnable::run → TIMED_OUT |
runnable.run(); |
| 208 | } catch (RejectedExecutionException e) { | |
| 209 |
1
1. retry : removed call to java/lang/Thread::onSpinWait → SURVIVED |
Thread.onSpinWait(); |
| 210 |
1
1. retry : removed call to java/lang/Runnable::run → KILLED |
runnable.run(); |
| 211 | } | |
| 212 | } | |
| 213 | ||
| 214 | sealed interface DispatchItem permits DispatchItem.Task, DispatchItem.Stop { | |
| 215 | record Task(FutureTask<?> task) implements DispatchItem { | |
| 216 | public Task { | |
| 217 | Objects.requireNonNull(task); | |
| 218 | } | |
| 219 | } | |
| 220 | ||
| 221 | enum Stop implements DispatchItem { | |
| 222 | POISON_PILL | |
| 223 | } | |
| 224 | } | |
| 225 | } | |
Mutations | ||
| 64 |
1.1 |
|
| 79 |
1.1 |
|
| 86 |
1.1 |
|
| 93 |
1.1 |
|
| 94 |
1.1 |
|
| 97 |
1.1 |
|
| 101 |
1.1 2.2 |
|
| 103 |
1.1 |
|
| 105 |
1.1 |
|
| 108 |
1.1 |
|
| 109 |
1.1 |
|
| 114 |
1.1 |
|
| 115 |
1.1 |
|
| 127 |
1.1 |
|
| 130 |
1.1 |
|
| 131 |
1.1 |
|
| 136 |
1.1 |
|
| 145 |
1.1 |
|
| 146 |
1.1 |
|
| 153 |
1.1 |
|
| 160 |
1.1 |
|
| 167 |
1.1 |
|
| 175 |
1.1 |
|
| 179 |
1.1 |
|
| 184 |
1.1 |
|
| 185 |
1.1 |
|
| 186 |
1.1 |
|
| 189 |
1.1 |
|
| 193 |
1.1 |
|
| 195 |
1.1 |
|
| 207 |
1.1 |
|
| 209 |
1.1 |
|
| 210 |
1.1 |