@@ -68,6 +68,15 @@ public CompletableFuture<Void> shutdownExecutorUntimed(
68
68
return untimedWait (executorToShutdown , executorName );
69
69
}
70
70
71
+ public CompletableFuture <Void > waitForSemaphorePermitsReleaseUntimed (
72
+ Semaphore semaphore , int initialSemaphorePermits , String semaphoreName ) {
73
+ CompletableFuture <Void > future = new CompletableFuture <>();
74
+ scheduledExecutorService .submit (
75
+ new SemaphoreReportingDelayShutdown (
76
+ semaphore , initialSemaphorePermits , semaphoreName , future ));
77
+ return future ;
78
+ }
79
+
71
80
/**
72
81
* Wait for {@code executorToShutdown} to terminate. Only completes the returned CompletableFuture
73
82
* when the executor is terminated.
@@ -76,7 +85,7 @@ private CompletableFuture<Void> untimedWait(
76
85
ExecutorService executorToShutdown , String executorName ) {
77
86
CompletableFuture <Void > future = new CompletableFuture <>();
78
87
scheduledExecutorService .submit (
79
- new ReportingDelayShutdown (executorToShutdown , executorName , future ));
88
+ new ExecutorReportingDelayShutdown (executorToShutdown , executorName , future ));
80
89
return future ;
81
90
}
82
91
@@ -91,7 +100,7 @@ private CompletableFuture<Void> limitedWait(
91
100
92
101
CompletableFuture <Void > future = new CompletableFuture <>();
93
102
scheduledExecutorService .submit (
94
- new LimitedWaitShutdown (executorToShutdown , attempts , executorName , future ));
103
+ new ExecutorLimitedWaitShutdown (executorToShutdown , attempts , executorName , future ));
95
104
return future ;
96
105
}
97
106
@@ -100,67 +109,90 @@ public void close() {
100
109
scheduledExecutorService .shutdownNow ();
101
110
}
102
111
103
- private class LimitedWaitShutdown implements Runnable {
104
- private final ExecutorService executorToShutdown ;
112
+ private abstract class LimitedWaitShutdown implements Runnable {
105
113
private final CompletableFuture <Void > promise ;
106
114
private final int maxAttempts ;
107
- private final String executorName ;
108
115
private int attempt ;
109
116
110
- public LimitedWaitShutdown (
111
- ExecutorService executorToShutdown ,
112
- int maxAttempts ,
113
- String executorName ,
114
- CompletableFuture <Void > promise ) {
115
- this .executorToShutdown = executorToShutdown ;
117
+ public LimitedWaitShutdown (int maxAttempts , CompletableFuture <Void > promise ) {
116
118
this .promise = promise ;
117
119
this .maxAttempts = maxAttempts ;
118
- this .executorName = executorName ;
119
120
}
120
121
121
122
@ Override
122
123
public void run () {
123
- if (executorToShutdown .isTerminated ()) {
124
+ if (isTerminated ()) {
125
+ onSuccessfulTermination ();
124
126
promise .complete (null );
125
127
return ;
126
128
}
127
129
attempt ++;
128
130
if (attempt > maxAttempts ) {
129
- log .warn (
130
- "Wait for a graceful shutdown of {} timed out, fallback to shutdownNow()" ,
131
- executorName );
132
- executorToShutdown .shutdownNow ();
131
+ onAttemptExhaustion ();
133
132
// we don't want to complicate shutdown with dealing of exceptions and errors of all sorts,
134
133
// so just log and complete the promise
135
134
promise .complete (null );
136
135
return ;
137
136
}
138
137
scheduledExecutorService .schedule (this , CHECK_PERIOD_MS , TimeUnit .MILLISECONDS );
139
138
}
139
+
140
+ abstract boolean isTerminated ();
141
+
142
+ abstract void onAttemptExhaustion ();
143
+
144
+ abstract void onSuccessfulTermination ();
140
145
}
141
146
142
- private class ReportingDelayShutdown implements Runnable {
147
+ private class ExecutorLimitedWaitShutdown extends LimitedWaitShutdown {
148
+ private final ExecutorService executorToShutdown ;
149
+ private final String executorName ;
150
+
151
+ public ExecutorLimitedWaitShutdown (
152
+ ExecutorService executorToShutdown ,
153
+ int maxAttempts ,
154
+ String executorName ,
155
+ CompletableFuture <Void > promise ) {
156
+ super (maxAttempts , promise );
157
+ this .executorToShutdown = executorToShutdown ;
158
+ this .executorName = executorName ;
159
+ }
160
+
161
+ @ Override
162
+ boolean isTerminated () {
163
+ return executorToShutdown .isTerminated ();
164
+ }
165
+
166
+ @ Override
167
+ void onAttemptExhaustion () {
168
+ log .warn (
169
+ "Wait for a graceful shutdown of {} timed out, fallback to shutdownNow()" , executorName );
170
+ executorToShutdown .shutdownNow ();
171
+ }
172
+
173
+ @ Override
174
+ void onSuccessfulTermination () {}
175
+ }
176
+
177
+ private abstract class ReportingDelayShutdown implements Runnable {
178
+ // measures in attempts count, not in ms
143
179
private static final int BLOCKED_REPORTING_THRESHOLD = 60 ;
144
180
private static final int BLOCKED_REPORTING_PERIOD = 20 ;
145
181
146
- private final ExecutorService executorToShutdown ;
147
182
private final CompletableFuture <Void > promise ;
148
- private final String executorName ;
149
183
private int attempt ;
150
184
151
- public ReportingDelayShutdown (
152
- ExecutorService executorToShutdown , String executorName , CompletableFuture <Void > promise ) {
153
- this .executorToShutdown = executorToShutdown ;
185
+ public ReportingDelayShutdown (CompletableFuture <Void > promise ) {
154
186
this .promise = promise ;
155
- this .executorName = executorName ;
156
187
}
157
188
158
189
@ Override
159
190
public void run () {
160
- if (executorToShutdown . isTerminated ()) {
191
+ if (isTerminated ()) {
161
192
if (attempt > BLOCKED_REPORTING_THRESHOLD ) {
162
- // log warn only if we already logged a shutdown being delayed
163
- log .warn ("{} successfully terminated" , executorName );
193
+ onSlowSuccessfulTermination ();
194
+ } else {
195
+ onSuccessfulTermination ();
164
196
}
165
197
promise .complete (null );
166
198
return ;
@@ -170,13 +202,87 @@ public void run() {
170
202
if (attempt >= BLOCKED_REPORTING_THRESHOLD ) {
171
203
// and repeat every BLOCKED_REPORTING_PERIOD attempts
172
204
if (((float ) (attempt - BLOCKED_REPORTING_THRESHOLD ) % BLOCKED_REPORTING_PERIOD ) < 0.001 ) {
173
- log .warn (
174
- "Graceful shutdown of {} is blocked by one of the long currently processing tasks" ,
175
- executorName );
205
+ onSlowTermination ();
176
206
}
177
207
}
178
208
scheduledExecutorService .schedule (this , CHECK_PERIOD_MS , TimeUnit .MILLISECONDS );
179
209
}
210
+
211
+ abstract boolean isTerminated ();
212
+
213
+ abstract void onSlowTermination ();
214
+
215
+ abstract void onSuccessfulTermination ();
216
+
217
+ /** Called only if {@link #onSlowTermination()} was called before */
218
+ abstract void onSlowSuccessfulTermination ();
219
+ }
220
+
221
+ private class ExecutorReportingDelayShutdown extends ReportingDelayShutdown {
222
+ private final ExecutorService executorToShutdown ;
223
+ private final String executorName ;
224
+
225
+ public ExecutorReportingDelayShutdown (
226
+ ExecutorService executorToShutdown , String executorName , CompletableFuture <Void > promise ) {
227
+ super (promise );
228
+ this .executorToShutdown = executorToShutdown ;
229
+ this .executorName = executorName ;
230
+ }
231
+
232
+ @ Override
233
+ boolean isTerminated () {
234
+ return executorToShutdown .isTerminated ();
235
+ }
236
+
237
+ @ Override
238
+ void onSlowTermination () {
239
+ log .warn (
240
+ "Graceful shutdown of {} is blocked by one of the long currently processing tasks" ,
241
+ executorName );
242
+ }
243
+
244
+ @ Override
245
+ void onSuccessfulTermination () {}
246
+
247
+ @ Override
248
+ void onSlowSuccessfulTermination () {
249
+ log .warn ("{} successfully terminated" , executorName );
250
+ }
251
+ }
252
+
253
+ private class SemaphoreReportingDelayShutdown extends ReportingDelayShutdown {
254
+ private final Semaphore semaphore ;
255
+ private final int initialSemaphorePermits ;
256
+ private final String semaphoreName ;
257
+
258
+ public SemaphoreReportingDelayShutdown (
259
+ Semaphore semaphore ,
260
+ int initialSemaphorePermits ,
261
+ String semaphoreName ,
262
+ CompletableFuture <Void > promise ) {
263
+ super (promise );
264
+ this .semaphore = semaphore ;
265
+ this .initialSemaphorePermits = initialSemaphorePermits ;
266
+ this .semaphoreName = semaphoreName ;
267
+ }
268
+
269
+ @ Override
270
+ boolean isTerminated () {
271
+ return semaphore .availablePermits () == initialSemaphorePermits ;
272
+ }
273
+
274
+ @ Override
275
+ void onSlowTermination () {
276
+ log .warn ("Wait for release of slots of {} takes a long time" , semaphoreName );
277
+ }
278
+
279
+ @ Override
280
+ void onSuccessfulTermination () {}
281
+
282
+ @ Override
283
+ void onSlowSuccessfulTermination () {
284
+ log .warn ("All slots of {} were successfully released" , semaphoreName );
285
+ }
180
286
}
181
287
182
288
public static long awaitTermination (@ Nullable ExecutorService s , long timeoutMillis ) {
@@ -187,7 +293,7 @@ public static long awaitTermination(@Nullable ExecutorService s, long timeoutMil
187
293
timeoutMillis ,
188
294
() -> {
189
295
try {
190
- s .awaitTermination (timeoutMillis , TimeUnit .MILLISECONDS );
296
+ boolean ignored = s .awaitTermination (timeoutMillis , TimeUnit .MILLISECONDS );
191
297
} catch (InterruptedException e ) {
192
298
Thread .currentThread ().interrupt ();
193
299
}
0 commit comments