diff --git a/cluster.md b/cluster.md index 96d0fa4..3fd9fb1 100644 --- a/cluster.md +++ b/cluster.md @@ -193,3 +193,12 @@ The save path for cluster based session storage takes the form of a PHP GET requ * _distribute_: phpredis will randomly distribute session reads between masters and any attached slaves (load balancing). * _auth (string, empty by default)_: The password used to authenticate with the server prior to sending commands. * _stream (array)_: ssl/tls stream context options. + +### redis.session.early_refresh +Under normal operation, the client will refresh the session's expiry ttl whenever the session is closed. However, we can save this additional round-trip by updating the ttl when the session is opened instead ( This means that sessions that have not been modified will not send further commands to the server ). + +To enable, set the following INI variable: +```ini +redis.session.early_refresh = 1 +``` +Note: This is disabled by default since it may significantly reduce the session lifetime for long-running scripts. Redis server version 6.2+ required. \ No newline at end of file diff --git a/redis.c b/redis.c index 1cb1f5b..c625d81 100644 --- a/redis.c +++ b/redis.c @@ -110,6 +110,7 @@ PHP_INI_BEGIN() PHP_INI_ENTRY("redis.session.lock_expire", "0", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.session.lock_retries", "100", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.session.lock_wait_time", "20000", PHP_INI_ALL, NULL) + PHP_INI_ENTRY("redis.session.early_refresh", "0", PHP_INI_ALL, NULL) PHP_INI_END() static const zend_module_dep redis_deps[] = { diff --git a/redis_session.c b/redis_session.c index 192f890..204f651 100644 --- a/redis_session.c +++ b/redis_session.c @@ -60,7 +60,7 @@ ps_module ps_mod_redis = { }; ps_module ps_mod_redis_cluster = { - PS_MOD(rediscluster) + PS_MOD_UPDATE_TIMESTAMP(rediscluster) }; typedef struct { @@ -982,6 +982,166 @@ failure: return FAILURE; } +/* {{{ PS_CREATE_SID_FUNC + */ +PS_CREATE_SID_FUNC(rediscluster) +{ + redisCluster *c = PS_GET_MOD_DATA(); + clusterReply *reply; + char *cmd, *skey; + zend_string *sid; + int cmdlen, skeylen; + int retries = 3; + short slot; + + if (!c) { + return php_session_create_id(NULL); + } + + if (INI_INT("session.use_strict_mode") == 0) { + return php_session_create_id((void **) &c); + } + + while (retries-- > 0) { + sid = php_session_create_id((void **) &c); + + /* Create session key if it doesn't already exist */ + skey = cluster_session_key(c, ZSTR_VAL(sid), ZSTR_LEN(sid), &skeylen, &slot); + cmdlen = redis_spprintf(NULL, NULL, &cmd, "SET", "ssssd", skey, + skeylen, "", 0, "NX", 2, "EX", 2, session_gc_maxlifetime()); + + efree(skey); + + /* Attempt to kick off our command */ + c->readonly = 0; + if (cluster_send_command(c,slot,cmd,cmdlen) < 0 || c->err) { + php_error_docref(NULL, E_NOTICE, "Redis connection not available"); + efree(cmd); + zend_string_release(sid); + return php_session_create_id(NULL);; + } + + efree(cmd); + + /* Attempt to read reply */ + reply = cluster_read_resp(c, 1); + + if (!reply || c->err) { + php_error_docref(NULL, E_NOTICE, "Unable to read redis response"); + } else if (reply->len > 0) { + cluster_free_reply(reply, 1); + break; + } else { + php_error_docref(NULL, E_NOTICE, "Redis sid collision on %s, retrying %d time(s)", sid->val, retries); + } + + if (reply) { + cluster_free_reply(reply, 1); + } + + zend_string_release(sid); + sid = NULL; + } + + return sid; +} +/* }}} */ + +/* {{{ PS_VALIDATE_SID_FUNC + */ +PS_VALIDATE_SID_FUNC(rediscluster) +{ + redisCluster *c = PS_GET_MOD_DATA(); + clusterReply *reply; + char *cmd, *skey; + int cmdlen, skeylen; + int res = FAILURE; + short slot; + + /* Check key is valid and whether it already exists */ + if (php_session_valid_key(ZSTR_VAL(key)) == FAILURE) { + php_error_docref(NULL, E_NOTICE, "Invalid session key: %s", ZSTR_VAL(key)); + return FAILURE; + } + + skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot); + cmdlen = redis_spprintf(NULL, NULL, &cmd, "EXISTS", "s", skey, skeylen); + efree(skey); + + /* We send to master, to ensure consistency */ + c->readonly = 0; + if (cluster_send_command(c,slot,cmd,cmdlen) < 0 || c->err) { + php_error_docref(NULL, E_NOTICE, "Redis connection not available"); + efree(cmd); + return FAILURE; + } + + efree(cmd); + + /* Attempt to read reply */ + reply = cluster_read_resp(c, 0); + + if (!reply || c->err) { + php_error_docref(NULL, E_NOTICE, "Unable to read redis response"); + res = FAILURE; + } else if (reply->integer == 1) { + res = SUCCESS; + } + + /* Clean up */ + if (reply) { + cluster_free_reply(reply, 1); + } + + return res; +} +/* }}} */ + +/* {{{ PS_UPDATE_TIMESTAMP_FUNC + */ +PS_UPDATE_TIMESTAMP_FUNC(rediscluster) { + redisCluster *c = PS_GET_MOD_DATA(); + clusterReply *reply; + char *cmd, *skey; + int cmdlen, skeylen; + short slot; + + /* No need to update the session timestamp if we've already done so */ + if (INI_INT("redis.session.early_refresh")) { + return SUCCESS; + } + + /* Set up command and slot info */ + skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot); + cmdlen = redis_spprintf(NULL, NULL, &cmd, "EXPIRE", "sd", skey, + skeylen, session_gc_maxlifetime()); + efree(skey); + + /* Attempt to send EXPIRE command */ + c->readonly = 0; + if (cluster_send_command(c,slot,cmd,cmdlen) < 0 || c->err) { + php_error_docref(NULL, E_NOTICE, "Redis unable to update session expiry"); + efree(cmd); + return FAILURE; + } + + /* Clean up our command */ + efree(cmd); + + /* Attempt to read reply */ + reply = cluster_read_resp(c, 0); + if (!reply || c->err) { + if (reply) cluster_free_reply(reply, 1); + return FAILURE; + } + + /* Clean up */ + cluster_free_reply(reply, 1); + + return SUCCESS; +} +/* }}} */ + /* {{{ PS_READ_FUNC */ PS_READ_FUNC(rediscluster) { @@ -994,11 +1154,19 @@ PS_READ_FUNC(rediscluster) { /* Set up our command and slot information */ skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot); - cmdlen = redis_spprintf(NULL, NULL, &cmd, "GET", "s", skey, skeylen); + /* Update the session ttl if early refresh is enabled */ + if (INI_INT("redis.session.early_refresh")) { + cmdlen = redis_spprintf(NULL, NULL, &cmd, "GETEX", "ssd", skey, + skeylen, "EX", 2, session_gc_maxlifetime()); + c->readonly = 0; + } else { + cmdlen = redis_spprintf(NULL, NULL, &cmd, "GET", "s", skey, skeylen); + c->readonly = 1; + } + efree(skey); /* Attempt to kick off our command */ - c->readonly = 1; if (cluster_send_command(c,slot,cmd,cmdlen) < 0 || c->err) { efree(cmd); return FAILURE; @@ -1126,4 +1294,4 @@ PS_GC_FUNC(rediscluster) { #endif -/* vim: set tabstop=4 expandtab: */ +/* vim: set tabstop=4 expandtab: */ \ No newline at end of file diff --git a/redis_session.h b/redis_session.h index 1529c05..d72e620 100644 --- a/redis_session.h +++ b/redis_session.h @@ -20,6 +20,9 @@ PS_READ_FUNC(rediscluster); PS_WRITE_FUNC(rediscluster); PS_DESTROY_FUNC(rediscluster); PS_GC_FUNC(rediscluster); +PS_CREATE_SID_FUNC(rediscluster); +PS_VALIDATE_SID_FUNC(rediscluster); +PS_UPDATE_TIMESTAMP_FUNC(rediscluster); #endif #endif