diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index af65d54b4bb..95ab0ace308 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -1083,6 +1083,22 @@ native_transport_allow_older_protocols: true # native_transport_rate_limiting_enabled: false # native_transport_max_requests_per_second: 1000000 +# When enabled, nodes will signal connected clients before shutting down, +# allowing in-flight requests to complete without client-visible timeouts. +# This applies to intentional shutdowns (nodetool drain, rolling restarts, +# controlled JVM shutdown). Clients must subscribe to the GRACEFUL_DISCONNECT +# event via REGISTER to benefit from this behavior. +# +# Requires driver support for the GRACEFUL_DISCONNECT event type. +# See: https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=406619103 + +# Enable or disable graceful disconnect. When false, shutdown behavior is +# unchanged from previous versions. +# graceful_disconnect_enabled: true + +# Time given to clients to stop sending new requests after the GRACEFUL_DISCONNECT event is emitted. +# graceful_disconnect_grace_period: 5000 + # The address or interface to bind the native transport server to. # # Set rpc_address OR rpc_interface, not both. diff --git a/conf/cassandra_latest.yaml b/conf/cassandra_latest.yaml index 2599b6e7203..5022f86e669 100644 --- a/conf/cassandra_latest.yaml +++ b/conf/cassandra_latest.yaml @@ -1072,6 +1072,22 @@ native_transport_allow_older_protocols: true # native_transport_rate_limiting_enabled: false # native_transport_max_requests_per_second: 1000000 +# When enabled, nodes will signal connected clients before shutting down, +# allowing in-flight requests to complete without client-visible timeouts. +# This applies to intentional shutdowns (nodetool drain, rolling restarts, +# controlled JVM shutdown). Clients must subscribe to the GRACEFUL_DISCONNECT +# event via REGISTER to benefit from this behavior. +# +# Requires driver support for the GRACEFUL_DISCONNECT event type. +# See: https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=406619103 + +# Enable or disable graceful disconnect. When false, shutdown behavior is +# unchanged from previous versions. +graceful_disconnect_enabled: true + +# Time given to clients to stop sending new requests after the GRACEFUL_DISCONNECT event is emitted. +# graceful_disconnect_grace_period: 5000 + # The address or interface to bind the native transport server to. # # Set rpc_address OR rpc_interface, not both. diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java index dd75d910c83..0bf7651c333 100644 --- a/src/java/org/apache/cassandra/config/Config.java +++ b/src/java/org/apache/cassandra/config/Config.java @@ -167,6 +167,10 @@ public static Set splitCommaDelimited(String src) public volatile DurationSpec.LongMillisecondsBound accord_preaccept_timeout = new DurationSpec.LongMillisecondsBound("1s"); + public boolean graceful_disconnect_enabled = false; + + public volatile DurationSpec.LongMillisecondsBound graceful_disconnect_grace_period = new DurationSpec.LongMillisecondsBound("5s"); + @Replaces(oldName = "truncate_request_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true) public volatile DurationSpec.LongMillisecondsBound truncate_request_timeout = new DurationSpec.LongMillisecondsBound("60000ms"); diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index bafe1715e75..a3bd1aa78f6 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -2634,6 +2634,21 @@ public static void setRpcTimeout(long timeOutInMillis) conf.request_timeout = new DurationSpec.LongMillisecondsBound(timeOutInMillis); } + public static long getGracefulDisconnectGracePeriod() + { + return conf.graceful_disconnect_grace_period.toMilliseconds(); + } + + public static void setGracefulDisconnectGracePeriod(long gracefulDisconnectGracePeriod) + { + conf.graceful_disconnect_grace_period = new DurationSpec.LongMillisecondsBound(gracefulDisconnectGracePeriod); + } + + public static boolean getGracefulDisconnectEnabled() + { + return conf.graceful_disconnect_enabled; + } + public static long getReadRpcTimeout(TimeUnit unit) { return conf.read_request_timeout.to(unit); diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index b7460b1be50..004def707cb 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -1201,6 +1201,27 @@ public long getRpcTimeout() return DatabaseDescriptor.getRpcTimeout(MILLISECONDS); } + + @Override + public boolean getGracefulDisconnectEnabled() + { + return DatabaseDescriptor.getGracefulDisconnectEnabled(); + } + + @Override + public void setGracefulDisconnectGracePeriod(long value) + { + if (value <= 0 && DatabaseDescriptor.getGracefulDisconnectEnabled()) + throw new IllegalArgumentException("Graceful disconnect grace period must be positive when graceful disconnect is enabled. Got " + value); + DatabaseDescriptor.setGracefulDisconnectGracePeriod(value); + } + + @Override + public long getGracefulDisconnectGracePeriod() + { + return DatabaseDescriptor.getGracefulDisconnectGracePeriod(); + } + public void setReadRpcTimeout(long value) { DatabaseDescriptor.setReadRpcTimeout(value); diff --git a/src/java/org/apache/cassandra/service/StorageServiceMBean.java b/src/java/org/apache/cassandra/service/StorageServiceMBean.java index b9f47b3b2dd..e55741c1e2a 100644 --- a/src/java/org/apache/cassandra/service/StorageServiceMBean.java +++ b/src/java/org/apache/cassandra/service/StorageServiceMBean.java @@ -773,6 +773,11 @@ default int upgradeSSTables(String keyspaceName, boolean excludeCurrentVersion, public void setRpcTimeout(long value); public long getRpcTimeout(); + public void setGracefulDisconnectGracePeriod(long value); + public long getGracefulDisconnectGracePeriod(); + + public boolean getGracefulDisconnectEnabled(); + public void setReadRpcTimeout(long value); public long getReadRpcTimeout(); diff --git a/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java b/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java index 2a44e763e00..54be2ce3bb7 100644 --- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java +++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java @@ -791,6 +791,24 @@ public void testRowIndexSizeWarnEnabledAbortDisabled() DatabaseDescriptor.applyThresholdsValidations(conf); } + @Test + public void testGracefulDisconnectEnabled() + { + Config config = new Config(); + boolean originalValue = config.graceful_disconnect_enabled; + Assert.assertTrue("Default value of graceful_disconnect_enabled must be true", originalValue); + } + + @Test + public void testGracefulDisconnectGracePeriod() + { + long originalValue = DatabaseDescriptor.getGracefulDisconnectGracePeriod(); + Assert.assertEquals("Default value of graceful_disconnect_grace_period must be 5000", 5000, originalValue); + DatabaseDescriptor.setGracefulDisconnectGracePeriod(3000); + Assert.assertEquals("graceful_disconnect_grace_period should be updated to 3000", 3000, DatabaseDescriptor.getGracefulDisconnectGracePeriod()); + DatabaseDescriptor.setGracefulDisconnectGracePeriod(originalValue); + } + @Test public void testRowIndexSizeAbortEnabledWarnDisabled() { diff --git a/test/unit/org/apache/cassandra/service/StorageServiceTest.java b/test/unit/org/apache/cassandra/service/StorageServiceTest.java index 8742cdac239..f1a293a4eaf 100644 --- a/test/unit/org/apache/cassandra/service/StorageServiceTest.java +++ b/test/unit/org/apache/cassandra/service/StorageServiceTest.java @@ -24,6 +24,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import org.apache.cassandra.ServerTestUtils; import org.apache.cassandra.concurrent.ScheduledExecutors; @@ -201,6 +202,37 @@ public void testColumnIndexSizeInKiB() } } + @Test + public void testGracefulDisconnectGracePeriod() + { + StorageService storageService = StorageService.instance; + long originalGracePeriod = storageService.getGracefulDisconnectGracePeriod(); + try + { + storageService.setGracefulDisconnectGracePeriod(3000); + Assertions.assertEquals(3000, storageService.getGracefulDisconnectGracePeriod()); + + try + { + Assertions.assertThrows(IllegalArgumentException.class, () -> storageService.setGracefulDisconnectGracePeriod(-1)); + } + catch (IllegalArgumentException ignored) {} + + Assertions.assertEquals(3000, storageService.getGracefulDisconnectGracePeriod()); + } + finally + { + storageService.setGracefulDisconnectGracePeriod(originalGracePeriod); + } + } + + @Test + public void testGracefulDisconnectEnabled() + { + Assertions.assertFalse(StorageService.instance.getGracefulDisconnectEnabled(), + "Default value of graceful_disconnect_enabled must be false"); + } + @Test public void testColumnIndexCacheSizeInKiB() {