diff --git a/.gitignore b/.gitignore index dc2f5e12..c22047fd 100644 --- a/.gitignore +++ b/.gitignore @@ -104,7 +104,7 @@ modules/valkey_glide.so libtool cluster_scan_cursor_legacy_arginfo.h cluster_scan_cursor_arginfo.h -tests/valkey_data/*/valkey.log +tests/valkey_data/ composer.lock configure configure.ac diff --git a/CHANGELOG.md b/CHANGELOG.md index 93ed941c..6d3077d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changes +* Add circuit breaker configuration support for standalone and cluster clients ([#238](https://github.com/valkey-io/valkey-glide-php/issues/238)) * Add `MIGRATE` command for standalone and cluster clients ([#276](https://github.com/valkey-io/valkey-glide-php/pull/276)) * Add `CLIENT PAUSE` and `CLIENT UNPAUSE` commands for standalone and cluster clients ([#274](https://github.com/valkey-io/valkey-glide-php/pull/274)) * Add `RESET` command for standalone and cluster clients ([#273](https://github.com/valkey-io/valkey-glide-php/pull/273)) diff --git a/common.h b/common.h index 81ad350a..d853a3df 100644 --- a/common.h +++ b/common.h @@ -145,6 +145,31 @@ typedef struct { bool has_max_decompressed_size; /* true if user explicitly set max_decompressed_size */ } valkey_glide_compression_config_t; +/* Circuit breaker configuration */ +#define VALKEY_GLIDE_CB_WINDOW_SIZE_MS "window_size_ms" +#define VALKEY_GLIDE_CB_FAILURE_RATE_THRESHOLD "failure_rate_threshold" +#define VALKEY_GLIDE_CB_MIN_ERRORS "min_errors" +#define VALKEY_GLIDE_CB_OPEN_TIMEOUT_MS "open_timeout_ms" +#define VALKEY_GLIDE_CB_COUNT_TIMEOUTS "count_timeouts" +#define VALKEY_GLIDE_CB_CONSECUTIVE_SUCCESSES "consecutive_successes" + +#define VALKEY_GLIDE_CB_DEFAULT_WINDOW_SIZE_MS 10000 +#define VALKEY_GLIDE_CB_DEFAULT_FAILURE_RATE_THRESHOLD 0.5 +#define VALKEY_GLIDE_CB_DEFAULT_MIN_ERRORS 50 +#define VALKEY_GLIDE_CB_DEFAULT_OPEN_TIMEOUT_MS 5000 +#define VALKEY_GLIDE_CB_DEFAULT_COUNT_TIMEOUTS false +#define VALKEY_GLIDE_CB_DEFAULT_CONSECUTIVE_SUCCESSES 3 +#define VALKEY_GLIDE_ERROR_TYPE_CIRCUIT_BREAKER_OPEN 4 + +typedef struct { + uint32_t window_size_ms; + float failure_rate_threshold; + uint32_t min_errors; + uint32_t open_timeout_ms; + bool count_timeouts; + uint32_t consecutive_successes; +} valkey_glide_circuit_breaker_config_t; + /* Client-side cache configuration */ typedef struct { char* cache_id; /* Unique cache identifier */ @@ -187,6 +212,7 @@ typedef struct { valkey_glide_advanced_base_client_configuration_t* advanced_config; /* NULL if not set */ valkey_glide_compression_config_t* compression_config; /* NULL if not set */ valkey_glide_client_side_cache_config_t* client_side_cache; /* NULL if not set */ + valkey_glide_circuit_breaker_config_t* circuit_breaker; /* NULL if not set */ valkey_glide_read_from_t read_from; int addresses_count; int request_timeout; /* -1 if not set */ @@ -222,6 +248,7 @@ typedef struct { zval* context; /* Stream context for TLS */ zval* compression; /* Compression configuration */ zval* client_side_cache; /* Client-side cache configuration */ + zval* circuit_breaker; /* Circuit breaker configuration */ char* client_name; char* client_az; size_t client_name_len; @@ -318,6 +345,7 @@ PHP_MINFO_FUNCTION(redis); zend_class_entry* get_valkey_glide_ce(void); zend_class_entry* get_valkey_glide_exception_ce(void); +zend_class_entry* get_valkey_glide_circuit_breaker_exception_ce(void); zend_class_entry* get_valkey_glide_cluster_ce(void); diff --git a/src/client_constructor_mock.c b/src/client_constructor_mock.c index a533af56..8ba90a8d 100644 --- a/src/client_constructor_mock.c +++ b/src/client_constructor_mock.c @@ -92,7 +92,7 @@ PHP_METHOD(ClientConstructorMock, simulate_standalone_constructor) { valkey_glide_php_common_constructor_params_t common_params; valkey_glide_init_common_constructor_params(&common_params); - ZEND_PARSE_PARAMETERS_START(0, 15) + ZEND_PARSE_PARAMETERS_START(0, 16) Z_PARAM_OPTIONAL Z_PARAM_ARRAY_OR_NULL(common_params.addresses) Z_PARAM_BOOL(common_params.use_tls) @@ -110,6 +110,7 @@ PHP_METHOD(ClientConstructorMock, simulate_standalone_constructor) { Z_PARAM_ARRAY_OR_NULL(common_params.compression) Z_PARAM_ARRAY_OR_NULL(common_params.client_side_cache) Z_PARAM_ZVAL_OR_NULL(common_params.address_resolver) + Z_PARAM_ARRAY_OR_NULL(common_params.circuit_breaker) ZEND_PARSE_PARAMETERS_END_EX(RETURN_THROWS()); /* Validate database_id range before setting */ @@ -159,7 +160,7 @@ PHP_METHOD(ClientConstructorMock, simulate_cluster_constructor) { valkey_glide_php_common_constructor_params_t common_params; valkey_glide_init_common_constructor_params(&common_params); - ZEND_PARSE_PARAMETERS_START(0, 16) + ZEND_PARSE_PARAMETERS_START(0, 17) Z_PARAM_OPTIONAL Z_PARAM_ARRAY_OR_NULL(common_params.addresses) Z_PARAM_BOOL(common_params.use_tls) @@ -178,6 +179,7 @@ PHP_METHOD(ClientConstructorMock, simulate_cluster_constructor) { Z_PARAM_ARRAY_OR_NULL(common_params.compression) Z_PARAM_ARRAY_OR_NULL(common_params.client_side_cache) Z_PARAM_ZVAL_OR_NULL(common_params.address_resolver) + Z_PARAM_ARRAY_OR_NULL(common_params.circuit_breaker) ZEND_PARSE_PARAMETERS_END_EX(RETURN_THROWS()); /* Validate database_id range before setting */ diff --git a/src/client_constructor_mock.stub.php b/src/client_constructor_mock.stub.php index 8447fd9d..e4c59bf6 100644 --- a/src/client_constructor_mock.stub.php +++ b/src/client_constructor_mock.stub.php @@ -118,6 +118,7 @@ public static function simulate_standalone_constructor( ?array $compression = null, ?array $client_side_cache = null, ?callable $address_resolver = null, + ?array $circuit_breaker = null, ): \Connection_request\ConnectionRequest; /** @@ -164,5 +165,6 @@ public static function simulate_cluster_constructor( ?array $compression = null, ?array $client_side_cache = null, ?callable $address_resolver = null, + ?array $circuit_breaker = null, ): \Connection_request\ConnectionRequest; } diff --git a/tests/ConnectionRequestTest.php b/tests/ConnectionRequestTest.php index 510f6df8..01f8929b 100644 --- a/tests/ConnectionRequestTest.php +++ b/tests/ConnectionRequestTest.php @@ -75,6 +75,7 @@ require_once __DIR__ . "/TestSuite.php"; require_once __DIR__ . "/../vendor/autoload.php"; require_once __DIR__ . "/Connection_request/AuthenticationInfo.php"; +require_once __DIR__ . "/Connection_request/ClientCircuitBreakerConfig.php"; require_once __DIR__ . "/Connection_request/CompressionBackend.php"; require_once __DIR__ . "/Connection_request/CompressionConfig.php"; require_once __DIR__ . "/Connection_request/ConnectionRequest.php"; @@ -742,4 +743,94 @@ private function get_addresses(\Connection_request\ConnectionRequest $request): { return array_map(fn($a) => $a->getHost(), iterator_to_array($request->getAddresses())); } + + // ================================================================ + // Circuit Breaker Tests + // ================================================================ + + public function testCircuitBreakerStandaloneDefaults() + { + $request = ClientConstructorMock::simulate_standalone_constructor( + circuit_breaker: [] + ); + + $cb = $request->getClientCircuitBreaker(); + $this->assertNotNull($cb); + $this->assertEquals(10000, $cb->getWindowSizeMs()); + $this->assertTrue(abs($cb->getFailureRateThreshold() - 0.5) < 0.001); + $this->assertEquals(50, $cb->getMinErrors()); + $this->assertEquals(5000, $cb->getOpenTimeoutMs()); + $this->assertFalse($cb->getCountTimeouts()); + $this->assertEquals(3, $cb->getConsecutiveSuccesses()); + } + + public function testCircuitBreakerClusterDefaults() + { + $request = ClientConstructorMock::simulate_cluster_constructor( + circuit_breaker: [] + ); + + $cb = $request->getClientCircuitBreaker(); + $this->assertNotNull($cb); + $this->assertEquals(10000, $cb->getWindowSizeMs()); + $this->assertTrue(abs($cb->getFailureRateThreshold() - 0.5) < 0.001); + $this->assertEquals(50, $cb->getMinErrors()); + $this->assertEquals(5000, $cb->getOpenTimeoutMs()); + $this->assertFalse($cb->getCountTimeouts()); + $this->assertEquals(3, $cb->getConsecutiveSuccesses()); + } + + public function testCircuitBreakerStandaloneCustomValues() + { + $request = ClientConstructorMock::simulate_standalone_constructor( + circuit_breaker: [ + 'window_size_ms' => 20000, + 'failure_rate_threshold' => 0.8, + 'min_errors' => 10, + 'open_timeout_ms' => 3000, + 'count_timeouts' => true, + 'consecutive_successes' => 5, + ] + ); + + $cb = $request->getClientCircuitBreaker(); + $this->assertNotNull($cb); + $this->assertEquals(20000, $cb->getWindowSizeMs()); + $this->assertTrue(abs($cb->getFailureRateThreshold() - 0.8) < 0.001); + $this->assertEquals(10, $cb->getMinErrors()); + $this->assertEquals(3000, $cb->getOpenTimeoutMs()); + $this->assertTrue($cb->getCountTimeouts()); + $this->assertEquals(5, $cb->getConsecutiveSuccesses()); + } + + public function testCircuitBreakerClusterCustomValues() + { + $request = ClientConstructorMock::simulate_cluster_constructor( + circuit_breaker: [ + 'window_size_ms' => 15000, + 'failure_rate_threshold' => 0.3, + 'min_errors' => 25, + 'open_timeout_ms' => 10000, + 'count_timeouts' => true, + 'consecutive_successes' => 2, + ] + ); + + $cb = $request->getClientCircuitBreaker(); + $this->assertNotNull($cb); + $this->assertEquals(15000, $cb->getWindowSizeMs()); + $this->assertTrue(abs($cb->getFailureRateThreshold() - 0.3) < 0.001); + $this->assertEquals(25, $cb->getMinErrors()); + $this->assertEquals(10000, $cb->getOpenTimeoutMs()); + $this->assertTrue($cb->getCountTimeouts()); + $this->assertEquals(2, $cb->getConsecutiveSuccesses()); + } + + public function testCircuitBreakerNotSetByDefault() + { + $request = ClientConstructorMock::simulate_standalone_constructor(); + + $cb = $request->getClientCircuitBreaker(); + $this->assertNull($cb); + } } diff --git a/valkey_glide.c b/valkey_glide.c index 7ff923f5..3bb93150 100644 --- a/valkey_glide.c +++ b/valkey_glide.c @@ -77,6 +77,7 @@ static const size_t SSL_PREFIX_LEN = 6; zend_class_entry* valkey_glide_ce; zend_class_entry* valkey_glide_exception_ce; +zend_class_entry* valkey_glide_circuit_breaker_exception_ce; zend_class_entry* valkey_glide_cluster_ce; @@ -92,6 +93,10 @@ zend_class_entry* get_valkey_glide_exception_ce(void) { return valkey_glide_exception_ce; } +zend_class_entry* get_valkey_glide_circuit_breaker_exception_ce(void) { + return valkey_glide_circuit_breaker_exception_ce; +} + zend_class_entry* get_valkey_glide_cluster_ce(void) { return valkey_glide_cluster_ce; } @@ -183,6 +188,7 @@ void valkey_glide_init_common_constructor_params( params->context = NULL; params->compression = NULL; params->client_side_cache = NULL; + params->circuit_breaker = NULL; params->address_resolver = NULL; } @@ -573,6 +579,60 @@ int valkey_glide_build_client_config_base(valkey_glide_php_common_constructor_pa config->client_side_cache = NULL; } + /* Process circuit breaker configuration if provided */ + if (params->circuit_breaker && Z_TYPE_P(params->circuit_breaker) == IS_ARRAY) { + HashTable* cb_ht = Z_ARRVAL_P(params->circuit_breaker); + + config->circuit_breaker = ecalloc(1, sizeof(valkey_glide_circuit_breaker_config_t)); + + /* Parse window_size_ms (optional, default 10000) */ + zval* window_zv = zend_hash_str_find( + cb_ht, VALKEY_GLIDE_CB_WINDOW_SIZE_MS, sizeof(VALKEY_GLIDE_CB_WINDOW_SIZE_MS) - 1); + config->circuit_breaker->window_size_ms = window_zv + ? (uint32_t) zval_get_long(window_zv) + : VALKEY_GLIDE_CB_DEFAULT_WINDOW_SIZE_MS; + + /* Parse failure_rate_threshold (optional, default 0.5) */ + zval* threshold_zv = zend_hash_str_find(cb_ht, + VALKEY_GLIDE_CB_FAILURE_RATE_THRESHOLD, + sizeof(VALKEY_GLIDE_CB_FAILURE_RATE_THRESHOLD) - 1); + config->circuit_breaker->failure_rate_threshold = + threshold_zv ? (float) zval_get_double(threshold_zv) + : VALKEY_GLIDE_CB_DEFAULT_FAILURE_RATE_THRESHOLD; + + /* Parse min_errors (optional, default 50) */ + zval* min_errors_zv = zend_hash_str_find( + cb_ht, VALKEY_GLIDE_CB_MIN_ERRORS, sizeof(VALKEY_GLIDE_CB_MIN_ERRORS) - 1); + config->circuit_breaker->min_errors = min_errors_zv + ? (uint32_t) zval_get_long(min_errors_zv) + : VALKEY_GLIDE_CB_DEFAULT_MIN_ERRORS; + + /* Parse open_timeout_ms (optional, default 5000) */ + zval* open_timeout_zv = zend_hash_str_find( + cb_ht, VALKEY_GLIDE_CB_OPEN_TIMEOUT_MS, sizeof(VALKEY_GLIDE_CB_OPEN_TIMEOUT_MS) - 1); + config->circuit_breaker->open_timeout_ms = open_timeout_zv + ? (uint32_t) zval_get_long(open_timeout_zv) + : VALKEY_GLIDE_CB_DEFAULT_OPEN_TIMEOUT_MS; + + /* Parse count_timeouts (optional, default false) */ + zval* count_timeouts_zv = zend_hash_str_find( + cb_ht, VALKEY_GLIDE_CB_COUNT_TIMEOUTS, sizeof(VALKEY_GLIDE_CB_COUNT_TIMEOUTS) - 1); + config->circuit_breaker->count_timeouts = count_timeouts_zv + ? zval_is_true(count_timeouts_zv) + : VALKEY_GLIDE_CB_DEFAULT_COUNT_TIMEOUTS; + + /* Parse consecutive_successes (optional, default 3) */ + zval* consecutive_zv = + zend_hash_str_find(cb_ht, + VALKEY_GLIDE_CB_CONSECUTIVE_SUCCESSES, + sizeof(VALKEY_GLIDE_CB_CONSECUTIVE_SUCCESSES) - 1); + config->circuit_breaker->consecutive_successes = + consecutive_zv ? (uint32_t) zval_get_long(consecutive_zv) + : VALKEY_GLIDE_CB_DEFAULT_CONSECUTIVE_SUCCESSES; + } else { + config->circuit_breaker = NULL; + } + config->address_resolver = params->address_resolver; _initialize_open_telemetry(params, is_cluster); @@ -623,6 +683,14 @@ PHP_MINIT_FUNCTION(valkey_glide) { return FAILURE; } + /* CircuitBreakerException class */ + valkey_glide_circuit_breaker_exception_ce = + register_class_CircuitBreakerException(valkey_glide_exception_ce); + if (!valkey_glide_circuit_breaker_exception_ce) { + php_error_docref(NULL, E_ERROR, "Failed to register CircuitBreakerException class"); + return FAILURE; + } + /* Set object creation handlers */ if (valkey_glide_ce) { valkey_glide_ce->create_object = create_valkey_glide_object; @@ -780,6 +848,11 @@ void valkey_glide_cleanup_client_config(valkey_glide_base_client_configuration_t config->client_side_cache = NULL; } + if (config->circuit_breaker) { + efree(config->circuit_breaker); + config->circuit_breaker = NULL; + } + /* address_resolver is a borrowed reference from PHP, don't free it */ config->address_resolver = NULL; } @@ -890,7 +963,8 @@ static int valkey_glide_create_connection(valkey_glide_object* valkey_glide, zval* context, zval* compression, zval* client_side_cache, - zval* address_resolver) { + zval* address_resolver, + zval* circuit_breaker) { valkey_glide_php_common_constructor_params_t common_params; valkey_glide_init_common_constructor_params(&common_params); @@ -937,6 +1011,7 @@ static int valkey_glide_create_connection(valkey_glide_object* valkey_glide, common_params.compression = compression; common_params.client_side_cache = client_side_cache; common_params.address_resolver = address_resolver; + common_params.circuit_breaker = circuit_breaker; /* Default to localhost:6379 if no addresses provided */ zval addresses_array; @@ -1056,8 +1131,9 @@ PHP_METHOD(ValkeyGlide, connect) { zval* compression = NULL; zval* client_side_cache = NULL; zval* address_resolver = NULL; + zval* circuit_breaker = NULL; - ZEND_PARSE_PARAMETERS_START(0, 21) + ZEND_PARSE_PARAMETERS_START(0, 22) Z_PARAM_OPTIONAL Z_PARAM_STRING_OR_NULL(host, host_len) Z_PARAM_ZVAL_OR_NULL(port_zval) @@ -1080,6 +1156,7 @@ PHP_METHOD(ValkeyGlide, connect) { Z_PARAM_ARRAY_OR_NULL(compression) Z_PARAM_ARRAY_OR_NULL(client_side_cache) Z_PARAM_ZVAL_OR_NULL(address_resolver) + Z_PARAM_ARRAY_OR_NULL(circuit_breaker) ZEND_PARSE_PARAMETERS_END_EX(RETURN_THROWS()); /* Apply defaults for nullable parameters */ @@ -1153,7 +1230,8 @@ PHP_METHOD(ValkeyGlide, connect) { context, compression, client_side_cache, - address_resolver); + address_resolver, + circuit_breaker); /* Clean up temporary addresses array if we created it */ if (host != NULL) { diff --git a/valkey_glide.stub.php b/valkey_glide.stub.php index b2cfa9d8..57e5c777 100644 --- a/valkey_glide.stub.php +++ b/valkey_glide.stub.php @@ -378,6 +378,10 @@ public function __construct() * @param array|null $client_side_cache Client-side cache configuration array from ClientSideCache::toArray(): * ['cache_id' => string, 'max_cache_kb' => int, 'entry_ttl_ms' => int, * 'eviction_policy' => ?int, 'enable_metrics' => bool] + * @param array|null $circuit_breaker Circuit breaker configuration: + * ['window_size_ms' => int, 'failure_rate_threshold' => float, + * 'min_errors' => int, 'open_timeout_ms' => int, + * 'count_timeouts' => bool, 'consecutive_successes' => int] * @return bool True on successful connection, false on failure * * @throws ValkeyGlideException If conflicting parameters are specified or connection fails @@ -412,6 +416,7 @@ public function connect( ?array $compression = null, ?array $client_side_cache = null, ?callable $address_resolver = null, + ?array $circuit_breaker = null, ): bool; public function __destruct(); @@ -5161,3 +5166,7 @@ public function ftAliasList(): ValkeyGlide|array|false; class ValkeyGlideException extends RuntimeException { } + +class CircuitBreakerException extends ValkeyGlideException +{ +} diff --git a/valkey_glide_cluster.c b/valkey_glide_cluster.c index 515f66b3..316e284b 100644 --- a/valkey_glide_cluster.c +++ b/valkey_glide_cluster.c @@ -184,12 +184,13 @@ PHP_METHOD(ValkeyGlideCluster, __construct) { zval* compression = NULL; zval* client_side_cache = NULL; zval* address_resolver = NULL; + zval* circuit_breaker = NULL; valkey_glide_php_common_constructor_params_t common_params; valkey_glide_init_common_constructor_params(&common_params); valkey_glide_object* valkey_glide; - ZEND_PARSE_PARAMETERS_START(0, 22) + ZEND_PARSE_PARAMETERS_START(0, 23) Z_PARAM_OPTIONAL Z_PARAM_STRING_OR_NULL(name, name_len) Z_PARAM_ARRAY_OR_NULL(seeds) @@ -213,6 +214,7 @@ PHP_METHOD(ValkeyGlideCluster, __construct) { Z_PARAM_ARRAY_OR_NULL(compression) Z_PARAM_ARRAY_OR_NULL(client_side_cache) Z_PARAM_ZVAL_OR_NULL(address_resolver) + Z_PARAM_ARRAY_OR_NULL(circuit_breaker) ZEND_PARSE_PARAMETERS_END_EX(RETURN_THROWS()); valkey_glide = VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, getThis()); @@ -273,6 +275,7 @@ PHP_METHOD(ValkeyGlideCluster, __construct) { common_params.compression = compression; common_params.client_side_cache = client_side_cache; common_params.address_resolver = address_resolver; + common_params.circuit_breaker = circuit_breaker; /* Call helper function to create cluster connection */ valkey_glide_cluster_create_connection( diff --git a/valkey_glide_cluster.stub.php b/valkey_glide_cluster.stub.php index 6de4ff2d..2b9a38ef 100644 --- a/valkey_glide_cluster.stub.php +++ b/valkey_glide_cluster.stub.php @@ -252,6 +252,10 @@ class ValkeyGlideCluster * @param array|null $client_side_cache Client-side cache configuration array from ClientSideCache::toArray(): * ['cache_id' => string, 'max_cache_kb' => int, 'entry_ttl_ms' => int, * 'eviction_policy' => ?int, 'enable_metrics' => bool] + * @param array|null $circuit_breaker Circuit breaker configuration: + * ['window_size_ms' => int, 'failure_rate_threshold' => float, + * 'min_errors' => int, 'open_timeout_ms' => int, + * 'count_timeouts' => bool, 'consecutive_successes' => int] * * Note: Cannot mix PHPRedis-style and ValkeyGlide-style parameters. */ @@ -278,6 +282,7 @@ public function __construct( ?array $compression = null, ?array $client_side_cache = null, ?callable $address_resolver = null, + ?array $circuit_breaker = null, ) { } diff --git a/valkey_glide_commands_3.c b/valkey_glide_commands_3.c index 58ffc2a9..1d4f3c5e 100644 --- a/valkey_glide_commands_3.c +++ b/valkey_glide_commands_3.c @@ -1132,8 +1132,7 @@ static int execute_fcall_command_internal(zval* object, } if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } diff --git a/valkey_glide_commands_common.h b/valkey_glide_commands_common.h index f0ddd803..105e1aab 100644 --- a/valkey_glide_commands_common.h +++ b/valkey_glide_commands_common.h @@ -62,6 +62,15 @@ static inline bool is_sha1_hash(const char* str, size_t len) { return len == SHA1_HASH_LENGTH && strspn(str, "0123456789abcdefABCDEF") == SHA1_HASH_LENGTH; } +/* Helper to throw the correct exception for a command error */ +static inline void throw_command_error(CommandError* error) { + zend_class_entry* exception_ce = get_valkey_glide_exception_ce(); + if (error->command_error_type == VALKEY_GLIDE_ERROR_TYPE_CIRCUIT_BREAKER_OPEN) { + exception_ce = get_valkey_glide_circuit_breaker_exception_ce(); + } + zend_throw_exception(exception_ce, error->command_error_message, 0); +} + /* Helper function to handle command result with consistent error handling */ static inline void handle_command_result_or_throw(CommandResult* result, const char* command_name, @@ -74,8 +83,7 @@ static inline void handle_command_result_or_throw(CommandResult* result, return; } if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return; } diff --git a/valkey_glide_core_commands.c b/valkey_glide_core_commands.c index 9c1b7b87..9cf73a93 100644 --- a/valkey_glide_core_commands.c +++ b/valkey_glide_core_commands.c @@ -243,6 +243,20 @@ uint8_t* create_connection_request(size_t* len conn_req.client_side_cache = &client_side_cache_msg; } + /* Set circuit breaker configuration */ + ConnectionRequest__ClientCircuitBreakerConfig circuit_breaker_msg = + CONNECTION_REQUEST__CLIENT_CIRCUIT_BREAKER_CONFIG__INIT; + if (config->circuit_breaker) { + circuit_breaker_msg.window_size_ms = config->circuit_breaker->window_size_ms; + circuit_breaker_msg.failure_rate_threshold = + config->circuit_breaker->failure_rate_threshold; + circuit_breaker_msg.min_errors = config->circuit_breaker->min_errors; + circuit_breaker_msg.open_timeout_ms = config->circuit_breaker->open_timeout_ms; + circuit_breaker_msg.count_timeouts = config->circuit_breaker->count_timeouts; + circuit_breaker_msg.consecutive_successes = config->circuit_breaker->consecutive_successes; + conn_req.client_circuit_breaker = &circuit_breaker_msg; + } + /* Calculate the size of the serialized message */ *len = connection_request__connection_request__get_packed_size(&conn_req); @@ -2275,8 +2289,7 @@ void execute_update_connection_password(zval* object, result->command_error->command_error_message ? result->command_error->command_error_message : "Unknown error"); - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return; } @@ -2350,8 +2363,7 @@ void execute_refresh_iam_token(zval* object, zval* return_value, zend_class_entr result->command_error->command_error_message ? result->command_error->command_error_message : "Unknown error"); - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return; } @@ -2429,8 +2441,7 @@ void execute_get_cache_metrics(zval* object, result->command_error->command_error_message ? result->command_error->command_error_message : "Unknown error"); - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return; } diff --git a/valkey_glide_ft_common.c b/valkey_glide_ft_common.c index 318717a7..12a82c84 100644 --- a/valkey_glide_ft_common.c +++ b/valkey_glide_ft_common.c @@ -53,8 +53,7 @@ int execute_ft_command_internal(const void* glide_client, return 0; } if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } diff --git a/valkey_glide_json_common.c b/valkey_glide_json_common.c index 6337165e..708038d7 100644 --- a/valkey_glide_json_common.c +++ b/valkey_glide_json_common.c @@ -152,8 +152,7 @@ int execute_json_set_command(zval* object, int argc, zval* return_value, zend_cl if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } @@ -333,8 +332,7 @@ int execute_json_get_command(zval* object, int argc, zval* return_value, zend_cl if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } @@ -414,8 +412,7 @@ static int execute_json_key_path_command( if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } @@ -546,8 +543,7 @@ int execute_json_mget_command(zval* object, int argc, zval* return_value, zend_c if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } @@ -624,8 +620,7 @@ static int execute_json_num_command( if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } @@ -736,8 +731,7 @@ int execute_json_strappend_command(zval* object, if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } @@ -816,8 +810,7 @@ static int execute_json_debug_command(zval* object, if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } @@ -926,8 +919,7 @@ int execute_json_arrappend_command(zval* object, if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } @@ -1029,8 +1021,7 @@ int execute_json_arrinsert_command(zval* object, if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } @@ -1139,8 +1130,7 @@ int execute_json_arrindex_command(zval* object, if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } @@ -1226,8 +1216,7 @@ int execute_json_arrpop_command(zval* object, int argc, zval* return_value, zend if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; } @@ -1293,8 +1282,7 @@ int execute_json_arrtrim_command(zval* object, int argc, zval* return_value, zen if (result) { if (result->command_error) { - zend_throw_exception( - get_valkey_glide_exception_ce(), result->command_error->command_error_message, 0); + throw_command_error(result->command_error); free_command_result(result); return 0; }