StreamingConfigurer.java

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.time.Duration;
19
import java.util.List;
20
import java.util.concurrent.Executor;
21
import java.util.concurrent.TimeUnit;
22
import java.util.function.UnaryOperator;
23
24
/**
25
 * Fluent configuration builder for collectors that <em>stream</em> results.
26
 * <p>
27
 * Instances of this class are used internally to accumulate configuration options that are later
28
 * interpreted by {@link ConfigProcessor}.
29
 */
30
public final class StreamingConfigurer {
31
32
    private final ConfigurerState state = new ConfigurerState();
33
34
    StreamingConfigurer() {
35
    }
36
37
    /**
38
     * Preserves encounter order of the input when emitting results.
39
     * <p>
40
     * Enabling this option may reduce throughput compared to unordered streaming, since it can
41
     * require additional coordination and buffering to emit results in encounter order.
42
     *
43
     * @return this configurer instance for fluent chaining
44
     */
45
    public StreamingConfigurer ordered() {
46 1 1. ordered : removed call to com/pivovarit/collectors/ConfigurerState::ordered → KILLED
        state.ordered();
47 1 1. ordered : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::ordered → KILLED
        return this;
48
    }
49
50
    /**
51
     * Enables batching of work submitted to workers.
52
     * <p>
53
     * When enabled, each worker thread receives a batch of input items and processes them in one go,
54
     * instead of scheduling one task per item. This reduces the number of tasks created and typically
55
     * decreases contention on the underlying worker queue.
56
     *
57
     * <p><b>Note:</b> Depending on batch sizing and workload skew, batching may reduce load balancing and
58
     * can lead to thread starvation (some workers become idle while others remain overloaded).
59
     *
60
     * @return this configurer instance for fluent chaining
61
     */
62
    public StreamingConfigurer batching() {
63 1 1. batching : removed call to com/pivovarit/collectors/ConfigurerState::batching → KILLED
        state.batching();
64 1 1. batching : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::batching → KILLED
        return this;
65
    }
66
67
    /**
68
     * Sets the maximum level of parallelism.
69
     * <p>
70
     * This limits the number of tasks submitted to the worker queue at once, effectively bounding
71
     * the amount of in-flight work and the maximum concurrency used by the collector.
72
     *
73
     * <p><b>Note:</b> {@code parallelism(1)} streaming runs the mapper <em>synchronously on the calling
74
     * thread</em> as elements are accumulated, rather than dispatching work to an executor. As a
75
     * consequence, any {@link #executor(Executor)}, {@link #executorDecorator(UnaryOperator)} or
76
     * {@link #taskDecorator(UnaryOperator)} configured alongside {@code parallelism(1)} is ignored.
77
     * This is asymmetric with the collecting collectors (e.g. {@code parallel(...)}), where
78
     * {@code parallelism(1)} still executes on the configured executor — keep this in mind for
79
     * thread-isolation or context-propagation (MDC, OpenTelemetry, {@code SecurityContext}) use cases,
80
     * which require {@code parallelism(2)} or higher when streaming.
81
     *
82
     * @param parallelism the desired parallelism level (must be positive)
83
     *
84
     * @return this configurer instance for fluent chaining
85
     */
86
    public StreamingConfigurer parallelism(int parallelism) {
87 1 1. parallelism : removed call to com/pivovarit/collectors/ConfigurerState::parallelism → KILLED
        state.parallelism(parallelism);
88 1 1. parallelism : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::parallelism → KILLED
        return this;
89
    }
90
91
    /**
92
     * Sets a total-operation timeout for streaming results.
93
     * <p>
94
     * The timeout budget starts on the first pull from the returned stream. If the next result is not
95
     * available within the remaining budget, stream traversal throws a
96
     * {@link java.util.concurrent.CompletionException} wrapping a
97
     * {@link java.util.concurrent.TimeoutException}.
98
     *
99
     * <p><b>Note:</b> When the timeout trips, stream traversal stops with an exception, but any
100
     * in-flight tasks are <em>not</em> cancelled and keep running to completion in the background.
101
     *
102
     * <p><b>Note:</b> The timeout is ignored at {@code parallelism(1)}, which runs the mapper
103
     * synchronously on the calling thread and therefore cannot be interrupted by a timeout — see
104
     * {@link #parallelism(int)}.
105
     *
106
     * @param duration the timeout duration (must be positive)
107
     * @param unit     the time unit of the {@code duration} argument
108
     *
109
     * @return this configurer instance for fluent chaining
110
     */
111
    public StreamingConfigurer timeout(long duration, TimeUnit unit) {
112 1 1. timeout : removed call to com/pivovarit/collectors/ConfigurerState::timeout → KILLED
        state.timeout(duration, unit);
113 1 1. timeout : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::timeout → SURVIVED
        return this;
114
    }
115
116
    /**
117
     * Sets a total-operation timeout for streaming results.
118
     * <p>
119
     * The timeout budget starts on the first pull from the returned stream. If the next result is not
120
     * available within the remaining budget, stream traversal throws a
121
     * {@link java.util.concurrent.CompletionException} wrapping a
122
     * {@link java.util.concurrent.TimeoutException}.
123
     *
124
     * <p><b>Note:</b> When the timeout trips, stream traversal stops with an exception, but any
125
     * in-flight tasks are <em>not</em> cancelled and keep running to completion in the background.
126
     *
127
     * <p><b>Note:</b> The timeout is ignored at {@code parallelism(1)}, which runs the mapper
128
     * synchronously on the calling thread and therefore cannot be interrupted by a timeout — see
129
     * {@link #parallelism(int)}.
130
     *
131
     * @param duration the timeout duration (must be positive)
132
     *
133
     * @return this configurer instance for fluent chaining
134
     */
135
    public StreamingConfigurer timeout(Duration duration) {
136 1 1. timeout : removed call to com/pivovarit/collectors/ConfigurerState::timeout → KILLED
        state.timeout(duration);
137 1 1. timeout : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::timeout → SURVIVED
        return this;
138
    }
139
140
    /**
141
     * Sets the {@link Executor} used for running tasks.
142
     * <p>
143
     * Use this to supply a custom execution strategy (for example, a dedicated thread pool).
144
     *
145
     * <p><b>Note:</b> The provided executor must not <em>drop</em> tasks on rejection (e.g. using a
146
     * {@code RejectedExecutionHandler} that discards submitted work). Dropping tasks will cause the
147
     * stream to wait for results that will never be produced, which can lead to deadlocks.
148
     *
149
     * <p><b>Note:</b> The executor is ignored at {@code parallelism(1)}, which runs synchronously on the
150
     * calling thread — see {@link #parallelism(int)}.
151
     *
152
     * @param executor the executor to use
153
     *
154
     * @return this configurer instance for fluent chaining
155
     */
156
    public StreamingConfigurer executor(Executor executor) {
157 1 1. executor : removed call to com/pivovarit/collectors/ConfigurerState::executor → KILLED
        state.executor(executor);
158 1 1. executor : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::executor → KILLED
        return this;
159
    }
160
161
    /**
162
     * Decorates the executor used for running tasks.
163
     * <p>
164
     * The decorator receives the resolved executor (either the default virtual-thread executor or
165
     * the one provided via {@link #executor(Executor)}) and returns a wrapped replacement.
166
     * This is useful for augmenting the executor with cross-cutting concerns such as context
167
     * propagation (MDC, OpenTelemetry spans, etc.) or monitoring, without replacing the executor entirely.
168
     *
169
     * <p><b>Note:</b> The executor returned by the decorator must not <em>drop</em> tasks on rejection.
170
     * Dropping tasks will cause the stream to wait for results that will never be produced,
171
     * which can lead to deadlocks.
172
     *
173
     * <p><b>Note:</b> The decorator is ignored at {@code parallelism(1)}, which runs synchronously on the
174
     * calling thread — see {@link #parallelism(int)}.
175
     *
176
     * @param decorator a function that wraps the resolved executor
177
     *
178
     * @return this configurer instance for fluent chaining
179
     */
180
    public StreamingConfigurer executorDecorator(UnaryOperator<Executor> decorator) {
181 1 1. executorDecorator : removed call to com/pivovarit/collectors/ConfigurerState::executorDecorator → KILLED
        state.executorDecorator(decorator);
182 1 1. executorDecorator : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::executorDecorator → SURVIVED
        return this;
183
    }
184
185
    /**
186
     * Decorates each individual task before it is submitted to the executor.
187
     * <p>
188
     * The decorator receives the {@link Runnable} representing a single unit of work and returns a
189
     * wrapped replacement that runs in its place. This is useful for propagating thread-local context
190
     * (e.g. MDC entries, OpenTelemetry spans, {@code SecurityContext}) into worker threads, or for
191
     * per-task instrumentation, without replacing the executor entirely.
192
     *
193
     * <p>Unlike {@link #executorDecorator(UnaryOperator)}, which wraps the executor as a whole,
194
     * this decorator is applied to each task individually and runs on the worker thread.
195
     *
196
     * <p><b>Note:</b> The decorator is ignored at {@code parallelism(1)}, which runs synchronously on the
197
     * calling thread — see {@link #parallelism(int)}.
198
     *
199
     * @param decorator a function that wraps each submitted task
200
     *
201
     * @return this configurer instance for fluent chaining
202
     */
203
    public StreamingConfigurer taskDecorator(UnaryOperator<Runnable> decorator) {
204 1 1. taskDecorator : removed call to com/pivovarit/collectors/ConfigurerState::taskDecorator → KILLED
        state.taskDecorator(decorator);
205 1 1. taskDecorator : replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::taskDecorator → SURVIVED
        return this;
206
    }
207
208
    List<ConfigProcessor.Option> getConfig() {
209 1 1. getConfig : replaced return value with Collections.emptyList for com/pivovarit/collectors/StreamingConfigurer::getConfig → KILLED
        return state.getConfig();
210
    }
211
212
    void validate() {
213 1 1. validate : removed call to com/pivovarit/collectors/ConfigurerState::validate → KILLED
        state.validate();
214
    }
215
}

Mutations

46

1.1
Location : ordered
Killed by : com.pivovarit.collectors.OptionTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.OptionTest]/[method:shouldThrowOnDuplicateOrdered()]
removed call to com/pivovarit/collectors/ConfigurerState::ordered → KILLED

47

1.1
Location : ordered
Killed by : com.pivovarit.collectors.test.BasicParallelismTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.test.BasicParallelismTest]/[test-factory:shouldRejectInvalidParallelism()]/[dynamic-test:#30]
replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::ordered → KILLED

63

1.1
Location : batching
Killed by : com.pivovarit.collectors.OptionTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.OptionTest]/[method:streamingConfigurerShouldRejectBatchingWithoutParallelism()]
removed call to com/pivovarit/collectors/ConfigurerState::batching → KILLED

64

1.1
Location : batching
Killed by : com.pivovarit.collectors.OptionTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.OptionTest]/[method:streamingConfigurerValidateShouldAcceptBatchingWithParallelism()]
replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::batching → KILLED

87

1.1
Location : parallelism
Killed by : com.pivovarit.collectors.test.BasicParallelismTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.test.BasicParallelismTest]/[test-factory:shouldRejectInvalidParallelism()]/[dynamic-test:#36]
removed call to com/pivovarit/collectors/ConfigurerState::parallelism → KILLED

88

1.1
Location : parallelism
Killed by : com.pivovarit.collectors.test.BasicParallelismTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.test.BasicParallelismTest]/[test-factory:shouldProcessEmptyWithMaxParallelism()]/[dynamic-test:#18]
replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::parallelism → KILLED

112

1.1
Location : timeout
Killed by : com.pivovarit.collectors.OptionTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.OptionTest]/[method:shouldThrowOnInvalidTimeoutStreaming()]
removed call to com/pivovarit/collectors/ConfigurerState::timeout → KILLED

113

1.1
Location : timeout
Killed by : none
replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::timeout → SURVIVED
Covering tests

136

1.1
Location : timeout
Killed by : com.pivovarit.collectors.OptionTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.OptionTest]/[method:shouldThrowOnNullTimeoutDurationStreaming()]
removed call to com/pivovarit/collectors/ConfigurerState::timeout → KILLED

137

1.1
Location : timeout
Killed by : none
replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::timeout → SURVIVED
Covering tests

157

1.1
Location : executor
Killed by : com.pivovarit.collectors.test.TaskRejectionTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.test.TaskRejectionTest]/[test-factory:shouldRejectInvalidRejectedExecutionHandlerFactory()]/[dynamic-test:#16]
removed call to com/pivovarit/collectors/ConfigurerState::executor → KILLED

158

1.1
Location : executor
Killed by : com.pivovarit.collectors.test.BasicParallelismTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.test.BasicParallelismTest]/[test-factory:shouldRejectInvalidParallelism()]/[dynamic-test:#24]
replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::executor → KILLED

181

1.1
Location : executorDecorator
Killed by : com.pivovarit.collectors.OptionTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.OptionTest]/[method:shouldThrowOnNullExecutorDecoratorStreaming()]
removed call to com/pivovarit/collectors/ConfigurerState::executorDecorator → KILLED

182

1.1
Location : executorDecorator
Killed by : none
replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::executorDecorator → SURVIVED
Covering tests

204

1.1
Location : taskDecorator
Killed by : com.pivovarit.collectors.test.TaskDecoratorTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.test.TaskDecoratorTest]/[method:shouldInterceptTasksViaDecoratorOnStreaming()]
removed call to com/pivovarit/collectors/ConfigurerState::taskDecorator → KILLED

205

1.1
Location : taskDecorator
Killed by : none
replaced return value with null for com/pivovarit/collectors/StreamingConfigurer::taskDecorator → SURVIVED
Covering tests

209

1.1
Location : getConfig
Killed by : com.pivovarit.collectors.OptionTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.OptionTest]/[method:shouldConfigureTimeoutFromDuration()]
replaced return value with Collections.emptyList for com/pivovarit/collectors/StreamingConfigurer::getConfig → KILLED

213

1.1
Location : validate
Killed by : com.pivovarit.collectors.OptionTest.[engine:junit-jupiter]/[class:com.pivovarit.collectors.OptionTest]/[method:streamingConfigurerShouldRejectBatchingWithoutParallelism()]
removed call to com/pivovarit/collectors/ConfigurerState::validate → KILLED

Active mutators

Tests examined


Report generated by PIT 1.30.0 support