0
# Graceful Shutdown
1
2
Coordinated shutdown capabilities for application components enabling clean resource cleanup and graceful termination of long-running operations. Provides interfaces and utilities for implementing graceful shutdown patterns across the application.
3
4
## Capabilities
5
6
### GracefulShutdownCapable Interface
7
8
Core interface for components that support graceful shutdown with resource cleanup and active task reporting.
9
10
```java { .api }
11
/**
12
* Interface for beans that support graceful shutdown
13
* Enables coordinated shutdown with resource cleanup
14
*/
15
public interface GracefulShutdownCapable {
16
17
/**
18
* Perform graceful shutdown of this component
19
* Should complete ongoing operations and release resources
20
* @return CompletionStage that completes when shutdown is finished
21
*/
22
CompletionStage<?> shutdownGracefully();
23
24
/**
25
* Report the number of currently active tasks/operations
26
* Used for monitoring shutdown progress
27
* @return Optional number of active tasks, empty if not trackable
28
*/
29
OptionalLong reportActiveTasks();
30
31
/**
32
* Combine multiple completion stages into a single stage
33
* Utility method for coordinating multiple async operations
34
* @param stages Stream of completion stages to combine
35
* @return CompletionStage that completes when all input stages complete
36
*/
37
@NonNull
38
static CompletionStage<?> allOf(@NonNull Stream<CompletionStage<?>> stages);
39
40
/**
41
* Shutdown all provided GracefulShutdownCapable instances
42
* Coordinates shutdown across multiple components with exception handling
43
* @param stages Stream of components to shutdown
44
* @return CompletionStage that completes when all components are shutdown
45
*/
46
@NonNull
47
static CompletionStage<?> shutdownAll(@NonNull Stream<? extends GracefulShutdownCapable> stages);
48
49
/**
50
* Combine active task counts from multiple GracefulShutdownCapable components
51
* Aggregates active task reports across multiple components
52
* @param delegates Iterable of components to query for active tasks
53
* @return Combined active task count, or empty if no tasks are trackable
54
*/
55
@NonNull
56
static OptionalLong combineActiveTasks(@NonNull Iterable<? extends GracefulShutdownCapable> delegates);
57
}
58
```
59
60
**Usage Examples:**
61
62
```java
63
import io.micronaut.runtime.graceful.GracefulShutdownCapable;
64
import java.util.concurrent.CompletionStage;
65
import java.util.concurrent.CompletableFuture;
66
67
// Database connection pool with graceful shutdown
68
@Singleton
69
public class DatabaseConnectionPool implements GracefulShutdownCapable {
70
71
private final HikariDataSource dataSource;
72
private final AtomicLong activeConnections = new AtomicLong(0);
73
74
public DatabaseConnectionPool(HikariDataSource dataSource) {
75
this.dataSource = dataSource;
76
}
77
78
@Override
79
public CompletionStage<?> shutdownGracefully() {
80
logger.info("Starting graceful shutdown of database connection pool");
81
82
return CompletableFuture.runAsync(() -> {
83
try {
84
// Wait for active connections to complete
85
long maxWaitSeconds = 30;
86
long waitStart = System.currentTimeMillis();
87
88
while (activeConnections.get() > 0 &&
89
(System.currentTimeMillis() - waitStart) < maxWaitSeconds * 1000) {
90
Thread.sleep(100);
91
}
92
93
// Close the data source
94
dataSource.close();
95
logger.info("Database connection pool shutdown completed");
96
97
} catch (InterruptedException e) {
98
Thread.currentThread().interrupt();
99
logger.warn("Database shutdown interrupted", e);
100
}
101
});
102
}
103
104
@Override
105
public OptionalLong reportActiveTasks() {
106
return OptionalLong.of(activeConnections.get());
107
}
108
109
public Connection getConnection() throws SQLException {
110
activeConnections.incrementAndGet();
111
try {
112
return new ConnectionWrapper(dataSource.getConnection()) {
113
@Override
114
public void close() throws SQLException {
115
super.close();
116
activeConnections.decrementAndGet();
117
}
118
};
119
} catch (SQLException e) {
120
activeConnections.decrementAndGet();
121
throw e;
122
}
123
}
124
}
125
126
// Message processing service with graceful shutdown
127
@Singleton
128
public class MessageProcessor implements GracefulShutdownCapable {
129
130
private final ExecutorService processingExecutor;
131
private final AtomicLong activeMessages = new AtomicLong(0);
132
private volatile boolean shutdownRequested = false;
133
134
public MessageProcessor() {
135
this.processingExecutor = Executors.newFixedThreadPool(10);
136
}
137
138
@Override
139
public CompletionStage<?> shutdownGracefully() {
140
logger.info("Starting graceful shutdown of message processor");
141
shutdownRequested = true;
142
143
return CompletableFuture.runAsync(() -> {
144
// Stop accepting new messages
145
processingExecutor.shutdown();
146
147
try {
148
// Wait for active messages to complete (max 60 seconds)
149
boolean terminated = processingExecutor.awaitTermination(60, TimeUnit.SECONDS);
150
151
if (!terminated) {
152
logger.warn("Message processor shutdown timed out, forcing shutdown");
153
processingExecutor.shutdownNow();
154
}
155
156
logger.info("Message processor shutdown completed. Active messages: {}",
157
activeMessages.get());
158
159
} catch (InterruptedException e) {
160
Thread.currentThread().interrupt();
161
processingExecutor.shutdownNow();
162
}
163
});
164
}
165
166
@Override
167
public OptionalLong reportActiveTasks() {
168
return OptionalLong.of(activeMessages.get());
169
}
170
171
public CompletableFuture<Void> processMessage(Message message) {
172
if (shutdownRequested) {
173
return CompletableFuture.failedFuture(
174
new IllegalStateException("Shutdown in progress"));
175
}
176
177
activeMessages.incrementAndGet();
178
179
return CompletableFuture.runAsync(() -> {
180
try {
181
// Process the message
182
handleMessage(message);
183
} finally {
184
activeMessages.decrementAndGet();
185
}
186
}, processingExecutor);
187
}
188
}
189
```
190
191
### GracefulShutdownManager
192
193
Central manager for coordinating graceful shutdown across all registered components.
194
195
```java { .api }
196
/**
197
* Singleton service that manages graceful shutdown across all components
198
* Automatically discovers and coordinates GracefulShutdownCapable beans
199
*/
200
@Singleton
201
@Requires(classes = GracefulShutdownCapable.class)
202
@Experimental
203
public final class GracefulShutdownManager {
204
205
/**
206
* Initiate graceful shutdown of all GracefulShutdownCapable beans
207
* Automatically discovers all beans implementing GracefulShutdownCapable
208
* @return CompletionStage that completes when all components are shutdown
209
*/
210
@NonNull
211
public CompletionStage<?> shutdownGracefully();
212
213
/**
214
* Report combined active tasks from all GracefulShutdownCapable beans
215
* Aggregates active task counts from all discovered components
216
* @return Optional total count of active tasks across all components
217
*/
218
@NonNull
219
public OptionalLong reportActiveTasks();
220
}
221
```
222
223
**Usage Examples:**
224
225
```java
226
import io.micronaut.runtime.graceful.GracefulShutdownManager;
227
228
@Singleton
229
public class ApplicationShutdownCoordinator {
230
231
private final GracefulShutdownManager shutdownManager;
232
233
public ApplicationShutdownCoordinator(GracefulShutdownManager shutdownManager) {
234
this.shutdownManager = shutdownManager;
235
}
236
237
@EventListener
238
public void onShutdownEvent(ShutdownEvent event) {
239
logger.info("Application shutdown initiated");
240
241
// Get current active task count
242
OptionalLong activeTasks = shutdownManager.reportActiveTasks();
243
if (activeTasks.isPresent()) {
244
logger.info("Active tasks at shutdown: {}", activeTasks.getAsLong());
245
}
246
247
// Initiate graceful shutdown
248
CompletionStage<?> shutdownStage = shutdownManager.shutdownGracefully();
249
250
// Wait for completion (with timeout)
251
try {
252
shutdownStage.toCompletableFuture().get(90, TimeUnit.SECONDS);
253
logger.info("β Graceful shutdown completed successfully");
254
} catch (TimeoutException e) {
255
logger.warn("β οΈ Graceful shutdown timed out after 90 seconds");
256
} catch (Exception e) {
257
logger.error("β Error during graceful shutdown", e);
258
}
259
}
260
261
// Manual shutdown trigger (e.g., for admin endpoint)
262
public CompletableFuture<ShutdownReport> initiateShutdown() {
263
return CompletableFuture.supplyAsync(() -> {
264
long startTime = System.currentTimeMillis();
265
OptionalLong initialActiveTasks = shutdownManager.reportActiveTasks();
266
267
try {
268
shutdownManager.shutdownGracefully()
269
.toCompletableFuture()
270
.get(60, TimeUnit.SECONDS);
271
272
long duration = System.currentTimeMillis() - startTime;
273
return new ShutdownReport(true, duration, initialActiveTasks);
274
275
} catch (Exception e) {
276
long duration = System.currentTimeMillis() - startTime;
277
return new ShutdownReport(false, duration, initialActiveTasks, e);
278
}
279
});
280
}
281
}
282
283
// Shutdown report data class
284
public class ShutdownReport {
285
private final boolean successful;
286
private final long durationMs;
287
private final OptionalLong initialActiveTasks;
288
private final Exception error;
289
290
// Constructors and getters...
291
}
292
```
293
294
### GracefulShutdownListener Interface
295
296
Listener interface for responding to graceful shutdown events.
297
298
```java { .api }
299
/**
300
* Listener interface for graceful shutdown events
301
* Allows components to respond to shutdown phases
302
*/
303
public interface GracefulShutdownListener {
304
305
/**
306
* Called when graceful shutdown begins
307
* Opportunity to prepare for shutdown
308
*/
309
default void onShutdownStart() {
310
// Default implementation does nothing
311
}
312
313
/**
314
* Called during shutdown progress
315
* @param remainingTasks Number of tasks still active
316
*/
317
default void onShutdownProgress(long remainingTasks) {
318
// Default implementation does nothing
319
}
320
321
/**
322
* Called when graceful shutdown completes successfully
323
*/
324
default void onShutdownComplete() {
325
// Default implementation does nothing
326
}
327
328
/**
329
* Called if graceful shutdown fails or times out
330
* @param cause The exception that caused the failure, if any
331
*/
332
default void onShutdownFailed(Throwable cause) {
333
// Default implementation does nothing
334
}
335
}
336
```
337
338
**Usage Examples:**
339
340
```java
341
import io.micronaut.runtime.graceful.GracefulShutdownListener;
342
343
@Singleton
344
public class ShutdownProgressLogger implements GracefulShutdownListener {
345
346
private long shutdownStartTime;
347
348
@Override
349
public void onShutdownStart() {
350
shutdownStartTime = System.currentTimeMillis();
351
logger.info("π Graceful shutdown initiated");
352
}
353
354
@Override
355
public void onShutdownProgress(long remainingTasks) {
356
long elapsed = System.currentTimeMillis() - shutdownStartTime;
357
logger.info("β³ Shutdown progress: {} tasks remaining after {}ms",
358
remainingTasks, elapsed);
359
}
360
361
@Override
362
public void onShutdownComplete() {
363
long totalTime = System.currentTimeMillis() - shutdownStartTime;
364
logger.info("β Graceful shutdown completed in {}ms", totalTime);
365
}
366
367
@Override
368
public void onShutdownFailed(Throwable cause) {
369
long totalTime = System.currentTimeMillis() - shutdownStartTime;
370
logger.error("β Graceful shutdown failed after {}ms", totalTime, cause);
371
}
372
}
373
374
// Metrics collection during shutdown
375
@Singleton
376
public class ShutdownMetricsCollector implements GracefulShutdownListener {
377
378
private final MeterRegistry meterRegistry;
379
private Timer.Sample shutdownTimer;
380
381
public ShutdownMetricsCollector(MeterRegistry meterRegistry) {
382
this.meterRegistry = meterRegistry;
383
}
384
385
@Override
386
public void onShutdownStart() {
387
shutdownTimer = Timer.start(meterRegistry);
388
meterRegistry.counter("shutdown.started").increment();
389
}
390
391
@Override
392
public void onShutdownProgress(long remainingTasks) {
393
meterRegistry.gauge("shutdown.remaining.tasks", remainingTasks);
394
}
395
396
@Override
397
public void onShutdownComplete() {
398
if (shutdownTimer != null) {
399
shutdownTimer.stop(Timer.builder("shutdown.duration")
400
.description("Time taken for graceful shutdown")
401
.register(meterRegistry));
402
}
403
meterRegistry.counter("shutdown.completed").increment();
404
}
405
406
@Override
407
public void onShutdownFailed(Throwable cause) {
408
if (shutdownTimer != null) {
409
shutdownTimer.stop(Timer.builder("shutdown.duration.failed")
410
.description("Time taken for failed shutdown")
411
.register(meterRegistry));
412
}
413
meterRegistry.counter("shutdown.failed").increment();
414
}
415
}
416
```
417
418
### GracefulShutdownConfiguration
419
420
Configuration options for customizing graceful shutdown behavior.
421
422
```java { .api }
423
/**
424
* Configuration for graceful shutdown behavior
425
* Allows customization of shutdown timeouts and policies
426
*/
427
@ConfigurationProperties("micronaut.application.graceful-shutdown")
428
public class GracefulShutdownConfiguration {
429
430
/**
431
* Enable or disable graceful shutdown
432
* @return true if graceful shutdown is enabled
433
*/
434
public boolean isEnabled();
435
436
/**
437
* Maximum time to wait for graceful shutdown to complete
438
* @return Shutdown timeout duration
439
*/
440
public Duration getTimeout();
441
442
/**
443
* Time to wait between shutdown progress checks
444
* @return Progress check interval
445
*/
446
public Duration getProgressCheckInterval();
447
448
/**
449
* Whether to force shutdown if graceful shutdown times out
450
* @return true to force shutdown on timeout
451
*/
452
public boolean isForceShutdownOnTimeout();
453
454
/**
455
* Log level for shutdown progress messages
456
* @return Log level for shutdown logging
457
*/
458
public LogLevel getLogLevel();
459
}
460
```
461
462
**Configuration Examples:**
463
464
```yaml
465
# application.yml
466
micronaut:
467
application:
468
graceful-shutdown:
469
enabled: true
470
timeout: 60s
471
progress-check-interval: 5s
472
force-shutdown-on-timeout: true
473
log-level: INFO
474
```
475
476
```java
477
// Using configuration in custom shutdown logic
478
@Singleton
479
public class ConfigurableShutdownManager {
480
481
private final GracefulShutdownConfiguration config;
482
private final GracefulShutdownManager shutdownManager;
483
484
public ConfigurableShutdownManager(GracefulShutdownConfiguration config,
485
GracefulShutdownManager shutdownManager) {
486
this.config = config;
487
this.shutdownManager = shutdownManager;
488
}
489
490
public CompletableFuture<Void> shutdown() {
491
if (!config.isEnabled()) {
492
logger.info("Graceful shutdown disabled, performing immediate shutdown");
493
return CompletableFuture.completedFuture(null);
494
}
495
496
return CompletableFuture.runAsync(() -> {
497
try {
498
Duration timeout = config.getTimeout();
499
logger.log(config.getLogLevel(), "Starting graceful shutdown with {}s timeout",
500
timeout.getSeconds());
501
502
CompletableFuture<?> shutdownFuture = shutdownManager
503
.shutdownGracefully()
504
.toCompletableFuture();
505
506
// Wait with configured timeout
507
shutdownFuture.get(timeout.toMillis(), TimeUnit.MILLISECONDS);
508
509
logger.log(config.getLogLevel(), "Graceful shutdown completed successfully");
510
511
} catch (TimeoutException e) {
512
if (config.isForceShutdownOnTimeout()) {
513
logger.warn("Graceful shutdown timed out, forcing shutdown");
514
// Force shutdown logic here
515
} else {
516
logger.error("Graceful shutdown timed out and force shutdown disabled");
517
throw new RuntimeException("Shutdown timeout", e);
518
}
519
} catch (Exception e) {
520
logger.error("Error during graceful shutdown", e);
521
throw new RuntimeException("Shutdown error", e);
522
}
523
});
524
}
525
}
526
```
527
528
## Advanced Shutdown Patterns
529
530
### Resource Pool Shutdown
531
532
Example of graceful shutdown for resource pools and connection managers.
533
534
```java { .api }
535
@Singleton
536
public class ConnectionManager implements GracefulShutdownCapable {
537
538
private final Map<String, ConnectionPool> connectionPools = new ConcurrentHashMap<>();
539
private volatile boolean shutdownInitiated = false;
540
541
@Override
542
public CompletionStage<?> shutdownGracefully() {
543
shutdownInitiated = true;
544
logger.info("Shutting down {} connection pools", connectionPools.size());
545
546
List<CompletableFuture<Void>> shutdownFutures = connectionPools.values()
547
.stream()
548
.map(pool -> CompletableFuture.runAsync(() -> {
549
try {
550
pool.close();
551
} catch (Exception e) {
552
logger.error("Error closing connection pool", e);
553
}
554
}))
555
.collect(Collectors.toList());
556
557
return CompletableFuture.allOf(shutdownFutures.toArray(new CompletableFuture[0]));
558
}
559
560
@Override
561
public OptionalLong reportActiveTasks() {
562
return OptionalLong.of(
563
connectionPools.values().stream()
564
.mapToLong(ConnectionPool::getActiveConnectionCount)
565
.sum()
566
);
567
}
568
}
569
```
570
571
### Background Task Shutdown
572
573
Example of graceful shutdown for background task processors.
574
575
```java { .api }
576
@Singleton
577
public class BackgroundTaskProcessor implements GracefulShutdownCapable {
578
579
private final ScheduledExecutorService scheduler;
580
private final Map<String, CompletableFuture<Void>> activeTasks = new ConcurrentHashMap<>();
581
582
public BackgroundTaskProcessor() {
583
this.scheduler = Executors.newScheduledThreadPool(5);
584
}
585
586
@Override
587
public CompletionStage<?> shutdownGracefully() {
588
logger.info("Shutting down background task processor");
589
590
// Stop accepting new tasks
591
scheduler.shutdown();
592
593
return CompletableFuture.runAsync(() -> {
594
try {
595
// Wait for scheduled tasks to complete
596
boolean terminated = scheduler.awaitTermination(30, TimeUnit.SECONDS);
597
598
if (!terminated) {
599
logger.warn("Scheduled tasks did not complete within timeout");
600
scheduler.shutdownNow();
601
}
602
603
// Wait for active background tasks
604
CompletableFuture<Void> allTasks = CompletableFuture.allOf(
605
activeTasks.values().toArray(new CompletableFuture[0])
606
);
607
608
allTasks.get(60, TimeUnit.SECONDS);
609
logger.info("All background tasks completed");
610
611
} catch (Exception e) {
612
logger.error("Error during background task shutdown", e);
613
// Cancel remaining tasks
614
activeTasks.values().forEach(task -> task.cancel(true));
615
}
616
});
617
}
618
619
@Override
620
public OptionalLong reportActiveTasks() {
621
return OptionalLong.of(activeTasks.size());
622
}
623
}
624
```