Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Changes

* 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))
* Add `SAVE` command for standalone and cluster clients ([#272](https://github.com/valkey-io/valkey-glide-php/pull/272))
* Add `BGREWRITEAOF` command for standalone and cluster clients ([#271](https://github.com/valkey-io/valkey-glide-php/pull/271))
Expand Down
107 changes: 107 additions & 0 deletions tests/ValkeyGlideClusterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,113 @@ public function testBgRewriteAofBatch()
}
}

public function testClientPause()
{
$key = '{client_pause_all}_' . uniqid();
$this->valkey_glide->set($key, 'before');

$pauseMs = 2000;
$result = $this->valkey_glide->clientPause($pauseMs);
$this->assertTrue($result);
Comment thread
jamesx-improving marked this conversation as resolved.

// Verify that reads are blocked until the pause expires
$start = microtime(true);
$value = $this->valkey_glide->get($key);
$elapsed = (microtime(true) - $start) * 1000;

$this->assertEquals('before', $value);
$this->assertGTE($pauseMs * 0.8, $elapsed);

$this->valkey_glide->del($key);
}

public function testClientPauseWithMode()
{
if (!$this->minVersionCheck('6.2.0')) {
$this->markTestSkipped('CLIENT PAUSE WRITE requires 6.2.0+');
return;
}

$key = '{client_pause_write}_' . uniqid();
$this->valkey_glide->set($key, 'value');

// Pause with WRITE mode - only writes are blocked, reads still allowed
$pauseMs = 60000;
$result = $this->valkey_glide->clientPause($pauseMs, 'WRITE');
$this->assertTrue($result);

try {
// Reads should still work quickly during WRITE pause
$start = microtime(true);
$value = $this->valkey_glide->get($key);
$elapsed = (microtime(true) - $start) * 1000;

$this->assertEquals('value', $value);
$this->assertLT($pauseMs, $elapsed);
} finally {
$this->valkey_glide->clientUnpause();
}

$this->valkey_glide->del($key);
}

public function testClientUnpause()
{
if (!$this->minVersionCheck('6.2.0')) {
$this->markTestSkipped('CLIENT PAUSE WRITE requires 6.2.0+');
return;
}

$key = '{client_unpause}_' . uniqid();
$this->valkey_glide->set($key, 'before');

// Pause writes for a long time
$pauseMs = 60000;
$this->valkey_glide->clientPause($pauseMs, 'WRITE');

// Unpause should unblock immediately
$result = $this->valkey_glide->clientUnpause();
$this->assertTrue($result);

// Verify writes work quickly after unpause
$start = microtime(true);
$this->valkey_glide->set($key, 'after');
$elapsed = (microtime(true) - $start) * 1000;

$this->assertLT($pauseMs, $elapsed);
$this->assertEquals('after', $this->valkey_glide->get($key));

$this->valkey_glide->del($key);
}

public function testClientPauseWithReplyLiteral()
{
$this->withOptReplyLiteralEnabled(function () {
$result = $this->valkey_glide->clientPause(100);
$this->assertIsString($result);
$this->assertEquals('OK', $result);

$result = $this->valkey_glide->clientUnpause();
$this->assertIsString($result);
$this->assertEquals('OK', $result);
});
}

public function testClientPauseBatch()
{
if (!$this->havePipeline()) {
$this->markTestSkipped('Pipeline not supported');
return;
}

$this->valkey_glide->pipeline();
$this->valkey_glide->clientPause(100);
$this->valkey_glide->clientUnpause();
$result = $this->valkey_glide->exec();
$this->assertTrue($result[0]);
$this->assertTrue($result[1]);
}

public function testReset()
{
$key = '{reset_test}_' . uniqid();
Expand Down
107 changes: 107 additions & 0 deletions tests/ValkeyGlideTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2721,6 +2721,113 @@ public function testBgRewriteAofBatch()
$this->assertTrue($result[0]);
}

public function testClientPause()
{
$key = 'client_pause_all_' . $this->createRandomString();
$this->valkey_glide->set($key, 'before');

$pauseMs = 2000;
$result = $this->valkey_glide->clientPause($pauseMs);
$this->assertTrue($result);

// Verify that reads are blocked until the pause expires
$start = microtime(true);
$value = $this->valkey_glide->get($key);
$elapsed = (microtime(true) - $start) * 1000;

$this->assertEquals('before', $value);
$this->assertGTE($pauseMs * 0.8, $elapsed);

$this->valkey_glide->del($key);
}

public function testClientPauseWithMode()
{
if (!$this->minVersionCheck('6.2.0')) {
$this->markTestSkipped('CLIENT PAUSE WRITE requires 6.2.0+');
return;
}

$key = 'client_pause_write_' . $this->createRandomString();
$this->valkey_glide->set($key, 'value');

// Pause with WRITE mode - only writes are blocked, reads still allowed
$pauseMs = 60000;
Comment thread
jamesx-improving marked this conversation as resolved.
$result = $this->valkey_glide->clientPause($pauseMs, 'WRITE');
Comment thread
jamesx-improving marked this conversation as resolved.
$this->assertTrue($result);

try {
// Reads should still work quickly during WRITE pause
$start = microtime(true);
$value = $this->valkey_glide->get($key);
$elapsed = (microtime(true) - $start) * 1000;

$this->assertEquals('value', $value);
$this->assertLT($pauseMs, $elapsed);
} finally {
$this->valkey_glide->clientUnpause();
}

$this->valkey_glide->del($key);
}

public function testClientUnpause()
{
if (!$this->minVersionCheck('6.2.0')) {
$this->markTestSkipped('CLIENT PAUSE WRITE requires 6.2.0+');
return;
}

$key = 'client_unpause_' . $this->createRandomString();
$this->valkey_glide->set($key, 'before');

// Pause writes for a long time
$pauseMs = 60000;
$this->valkey_glide->clientPause($pauseMs, 'WRITE');

// Unpause should unblock immediately
$result = $this->valkey_glide->clientUnpause();
$this->assertTrue($result);

// Verify writes work quickly after unpause
$start = microtime(true);
$this->valkey_glide->set($key, 'after');
$elapsed = (microtime(true) - $start) * 1000;

$this->assertLT($pauseMs, $elapsed);
$this->assertEquals('after', $this->valkey_glide->get($key));

$this->valkey_glide->del($key);
}

public function testClientPauseWithReplyLiteral()
{
$this->withOptReplyLiteralEnabled(function () {
$result = $this->valkey_glide->clientPause(100);
$this->assertIsString($result);
$this->assertEquals('OK', $result);

$result = $this->valkey_glide->clientUnpause();
$this->assertIsString($result);
$this->assertEquals('OK', $result);
});
}

public function testClientPauseBatch()
{
if (!$this->havePipeline()) {
$this->markTestSkipped('Pipeline not supported');
return;
}

$this->valkey_glide->pipeline();
$this->valkey_glide->clientPause(100);
$this->valkey_glide->clientUnpause();
$result = $this->valkey_glide->exec();
$this->assertTrue($result[0]);
$this->assertTrue($result[1]);
}

public function testReset()
{
$key = '{reset_test}_' . $this->createRandomString();
Expand Down
29 changes: 29 additions & 0 deletions valkey_glide.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -2360,6 +2360,35 @@ public function pfmerge(string $dst, array $srckeys): ValkeyGlide|bool;
*/
public function ping(?string $message = null): ValkeyGlide|string|bool;

/**
* Suspend all clients for the specified timeout.
*
* All subsequent commands from all clients will be blocked until the timeout expires
* or CLIENT UNPAUSE is called.
*
* @param int $timeout The time in milliseconds to pause clients.
* @param string|null $mode Optional pause mode: "ALL" (default) pauses all commands,
* "WRITE" pauses only write commands (reads still allowed).
*
* @return ValkeyGlide|bool|string Returns true on success, false on failure.
* With OPT_REPLY_LITERAL enabled, returns "OK" on success,
* false on failure.
*
* @see https://valkey.io/commands/client-pause
*/
public function clientPause(int $timeout, ?string $mode = null): ValkeyGlide|bool|string;

/**
* Resume processing commands on all clients that were paused by CLIENT PAUSE.
*
* @return ValkeyGlide|bool|string Returns true on success, false on failure.
* With OPT_REPLY_LITERAL enabled, returns "OK" on success,
* false on failure.
*
* @see https://valkey.io/commands/client-unpause
*/
public function clientUnpause(): ValkeyGlide|bool|string;

/**
* Reset the connection's server-side context.
*
Expand Down
8 changes: 8 additions & 0 deletions valkey_glide_cluster.c
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,14 @@ BGSAVE_METHOD_IMPL(ValkeyGlideCluster)
BGREWRITEAOF_METHOD_IMPL(ValkeyGlideCluster)
/* }}} */

/* {{{ proto ValkeyGlideCluster::clientPause(int timeout [, string mode]) */
CLIENT_PAUSE_METHOD_IMPL(ValkeyGlideCluster)
/* }}} */

/* {{{ proto ValkeyGlideCluster::clientUnpause() */
CLIENT_UNPAUSE_METHOD_IMPL(ValkeyGlideCluster)
/* }}} */

/* {{{ proto ValkeyGlideCluster::reset() */
RESET_METHOD_IMPL(ValkeyGlideCluster)
/* }}} */
Expand Down
10 changes: 10 additions & 0 deletions valkey_glide_cluster.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,16 @@ public function pfmerge(string $key, array $keys): ValkeyGlideCluster|bool;
*/
public function ping(mixed $route, ?string $message = null): mixed;

/**
* @see ValkeyGlide::clientPause
*/
public function clientPause(int $timeout, ?string $mode = null): ValkeyGlideCluster|bool|string;
Comment thread
Aryex marked this conversation as resolved.

/**
* @see ValkeyGlide::clientUnpause
*/
public function clientUnpause(): ValkeyGlideCluster|bool|string;

/**
* @see ValkeyGlide::reset
*/
Expand Down
79 changes: 78 additions & 1 deletion valkey_glide_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,84 @@ int execute_reset_command(zval* object, int argc, zval* return_value, zend_class
return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object);
}

/* Execute a CLIENT PAUSE command using the Valkey Glide client - UNIFIED IMPLEMENTATION */
int execute_client_pause_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) {
valkey_glide_object* valkey_glide;
long timeout;
char* mode = NULL;
size_t mode_len = 0;

/* Get ValkeyGlide object */
valkey_glide = VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object);
if (!valkey_glide || !valkey_glide->glide_client) {
return 0;
}

/* Parse parameters: timeout (required), mode (optional) */
if (zend_parse_method_parameters(
argc, object, "Ol|s!", &object, ce, &timeout, &mode, &mode_len) == FAILURE) {
return 0;
}

/* Setup core command arguments */
core_command_args_t core_args = {0};
core_args.glide_client = valkey_glide->glide_client;
core_args.cmd_type = ClientPause;
core_args.is_cluster = (ce == get_valkey_glide_cluster_ce());

/* Add timeout argument */
core_args.args[0].type = CORE_ARG_TYPE_LONG;
core_args.args[0].data.long_arg.value = timeout;
core_args.arg_count = 1;

/* Add optional mode argument (ALL or WRITE) */
if (mode && mode_len > 0) {
core_args.args[1].type = CORE_ARG_TYPE_STRING;
core_args.args[1].data.string_arg.value = mode;
core_args.args[1].data.string_arg.len = mode_len;
core_args.arg_count = 2;
}

/* Select processor based on OPT_REPLY_LITERAL */
z_result_processor_t processor = valkey_glide->opt_reply_literal
? process_core_status_string_result
: process_core_status_bool_result;

return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object);
}

/* Execute a CLIENT UNPAUSE command using the Valkey Glide client - UNIFIED IMPLEMENTATION */
int execute_client_unpause_command(zval* object,
int argc,
zval* return_value,
zend_class_entry* ce) {
valkey_glide_object* valkey_glide;

/* Get ValkeyGlide object */
valkey_glide = VALKEY_GLIDE_PHP_ZVAL_GET_OBJECT(valkey_glide_object, object);
if (!valkey_glide || !valkey_glide->glide_client) {
return 0;
}

/* No parameters - validate object only */
if (zend_parse_method_parameters(argc, object, "O", &object, ce) == FAILURE) {
return 0;
}

/* Setup core command arguments */
core_command_args_t core_args = {0};
core_args.glide_client = valkey_glide->glide_client;
core_args.cmd_type = ClientUnpause;
core_args.is_cluster = (ce == get_valkey_glide_cluster_ce());

/* Select processor based on OPT_REPLY_LITERAL */
z_result_processor_t processor = valkey_glide->opt_reply_literal
? process_core_status_string_result
: process_core_status_bool_result;

return execute_and_handle_batch(valkey_glide, &core_args, processor, return_value, object);
}

/* Execute a TIME command using the Valkey Glide client - UNIFIED IMPLEMENTATION */
int execute_time_command(zval* object, int argc, zval* return_value, zend_class_entry* ce) {
valkey_glide_object* valkey_glide;
Expand Down Expand Up @@ -531,7 +609,6 @@ int execute_unwatch_command(zval* object, int argc, zval* return_value, zend_cla
return 0;
}

/* Execute using core framework */
core_command_args_t args = {0};
args.glide_client = valkey_glide->glide_client;
args.cmd_type = UnWatch;
Expand Down
Loading
Loading