Graceful Shutdown
Gracefully shut down a KubeMQ Java SDK client, releasing connections and resources cleanly on application exit.
Overview
A graceful shutdown stops KubeMQ clients without dropping in-flight messages or leaking server-side subscription state. Killing a process outright, or closing a client mid-callback, can truncate a handler or leave the server thinking a consumer is still there. In a container platform that sends SIGTERM before force-killing a pod, an orderly shutdown sequence turns a rolling deploy into a clean handoff instead of a burst of errors.
The pattern is a fixed sequence: cancel subscriptions first so no new messages arrive, give pending callbacks a brief window to drain, then close each client. subscription.cancel() stops new deliveries while letting any callback already running finish. When an application holds several clients — here a PubSubClient, QueuesClient, and CQClient — they're closed in reverse construction order, so a client that might still reference another isn't torn down first.
Gotchas: a fixed Thread.sleep() after cancelling is a placeholder, not a guarantee — track in-flight callbacks with a CountDownLatch and wait on that instead. Closing clients out of order can leave one trying to use a transport another already shut down.
Prerequisites
- KubeMQ server running on
localhost:50000 - Java SDK installed (
implementation 'io.kubemq.sdk:kubemq-sdk-Java:3.1.1'(Gradle) or Maven dependency from Getting Started)
Code
package io.kubemq.example.errorhandling;
import io.kubemq.sdk.pubsub.*;
import io.kubemq.sdk.queues.*;
import io.kubemq.sdk.cq.*;
import java.util.ArrayList;
import java.util.List;
/**
* Graceful Shutdown Example
*
* Demonstrates proper shutdown procedures for KubeMQ clients.
*/
public class GracefulShutdownExample {
private static final String ADDRESS = "localhost:50000";
private static final String CLIENT_ID = "java-errorhandling-graceful-shutdown-client";
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Graceful Shutdown ===\n");
// Create multiple clients
PubSubClient pubSubClient = PubSubClient.builder()
.address(ADDRESS).clientId(CLIENT_ID + "-pubsub").build();
QueuesClient queuesClient = QueuesClient.builder()
.address(ADDRESS).clientId(CLIENT_ID + "-queues").build();
CQClient cqClient = CQClient.builder()
.address(ADDRESS).clientId(CLIENT_ID + "-cq").build();
System.out.println("Created 3 clients.");
// Create channel and subscribe
pubSubClient.createEventsChannel("java-errorhandling.shutdown-test");
EventsSubscription sub = EventsSubscription.builder()
.channel("java-errorhandling.shutdown-test")
.onReceiveEventCallback(e -> {}).onErrorCallback(err -> {}).build();
// Start the subscription
pubSubClient.subscribeToEvents(sub);
System.out.println("Subscription active.\n");
// Send messages before shutdown
for (int i = 1; i <= 3; i++) {
pubSubClient.publishEvent(EventMessage.builder()
.channel("java-errorhandling.shutdown-test")
.body(("Message " + i).getBytes()).build());
}
Thread.sleep(300);
// Graceful shutdown sequence
System.out.println("--- Initiating Graceful Shutdown ---\n");
// Step 1: Cancel subscriptions
sub.cancel();
System.out.println("1. Subscriptions cancelled.");
// Step 2: Wait for pending operations
Thread.sleep(200);
System.out.println("2. Pending operations completed.");
// Step 3: Clean up channels
try { pubSubClient.deleteEventsChannel("java-errorhandling.shutdown-test"); } catch (Exception e) {}
System.out.println("3. Channels cleaned up.");
// Step 4: Close clients in reverse order
cqClient.close();
queuesClient.close();
pubSubClient.close();
System.out.println("4. All clients closed.\n");
System.out.println("Graceful shutdown complete.");
}
}
How It Works
- The shutdown sequence follows four ordered steps: cancel subscriptions, wait for in-flight callbacks to drain, clean up channels, then close clients in reverse construction order.
subscription.cancel()signals the SDK to stop delivering new events to the callback; any callback already running completes before the subscription is fully torn down.Thread.sleep(200)after cancel gives pending callbacks time to finish — in production replace this with aCountDownLatchorCompletableFuturetracked inside the callback.- Closing clients in reverse order (CQ → Queues → PubSub) prevents a scenario where a still-active client tries to publish on a transport that is already shut down.
Related
Was this page helpful?