mirror of
https://github.com/php-win-ext/php-memcached.git
synced 2026-03-24 00:52:18 +01:00
552 lines
14 KiB
C
552 lines
14 KiB
C
/*
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2009-2010 The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt. |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
| Authors: Andrei Zmievski <andrei@php.net> |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "php_memcached.h"
|
|
#include "php_memcached_private.h"
|
|
#include "php_memcached_session.h"
|
|
|
|
#include "Zend/zend_smart_str_public.h"
|
|
|
|
extern ZEND_DECLARE_MODULE_GLOBALS(php_memcached)
|
|
|
|
#define MEMC_SESS_DEFAULT_LOCK_WAIT 150000
|
|
#define MEMC_SESS_LOCK_EXPIRATION 30
|
|
|
|
#define REALTIME_MAXDELTA 60*60*24*30
|
|
|
|
ps_module ps_mod_memcached = {
|
|
PS_MOD_UPDATE_TIMESTAMP(memcached)
|
|
};
|
|
|
|
typedef struct {
|
|
zend_bool is_persistent;
|
|
zend_bool has_sasl_data;
|
|
zend_bool is_locked;
|
|
zend_string *lock_key;
|
|
} php_memcached_user_data;
|
|
|
|
#ifndef MIN
|
|
# define MIN(a,b) (((a)<(b))?(a):(b))
|
|
#endif
|
|
|
|
#ifndef MAX
|
|
# define MAX(a,b) (((a)>(b))?(a):(b))
|
|
#endif
|
|
|
|
#ifdef ZTS
|
|
#define MEMC_SESS_INI(v) TSRMG(php_memcached_globals_id, zend_php_memcached_globals *, session.v)
|
|
#else
|
|
#define MEMC_SESS_INI(v) (php_memcached_globals.session.v)
|
|
#endif
|
|
|
|
#define MEMC_SESS_STR_INI(vv) ((MEMC_SESS_INI(vv) && *MEMC_SESS_INI(vv)) ? MEMC_SESS_INI(vv) : NULL)
|
|
|
|
static
|
|
int le_memc_sess;
|
|
|
|
static
|
|
int s_memc_sess_list_entry(void)
|
|
{
|
|
return le_memc_sess;
|
|
}
|
|
|
|
static
|
|
void s_destroy_mod_data(memcached_st *memc)
|
|
{
|
|
php_memcached_user_data *user_data = memcached_get_user_data(memc);
|
|
|
|
#ifdef HAVE_MEMCACHED_SASL
|
|
if (user_data->has_sasl_data) {
|
|
memcached_destroy_sasl_auth_data(memc);
|
|
}
|
|
#endif
|
|
|
|
memcached_free(memc);
|
|
pefree(memc, user_data->is_persistent);
|
|
pefree(user_data, user_data->is_persistent);
|
|
}
|
|
|
|
ZEND_RSRC_DTOR_FUNC(php_memc_sess_dtor)
|
|
{
|
|
if (res->ptr) {
|
|
s_destroy_mod_data((memcached_st *) res->ptr);
|
|
res->ptr = NULL;
|
|
}
|
|
}
|
|
|
|
int php_memc_session_minit(int module_number)
|
|
{
|
|
le_memc_sess =
|
|
zend_register_list_destructors_ex(NULL, php_memc_sess_dtor, "Memcached Sessions persistent connection", module_number);
|
|
|
|
php_session_register_module(ps_memcached_ptr);
|
|
return SUCCESS;
|
|
}
|
|
|
|
static
|
|
time_t s_adjust_expiration(zend_long expiration)
|
|
{
|
|
if (expiration <= REALTIME_MAXDELTA) {
|
|
return expiration;
|
|
} else {
|
|
return time(NULL) + expiration;
|
|
}
|
|
}
|
|
|
|
static
|
|
time_t s_lock_expiration()
|
|
{
|
|
if (MEMC_SESS_INI(lock_expiration) > 0) {
|
|
return s_adjust_expiration(MEMC_SESS_INI(lock_expiration));
|
|
}
|
|
else {
|
|
zend_long max_execution_time = zend_ini_long(ZEND_STRL("max_execution_time"), 0);
|
|
if (max_execution_time > 0) {
|
|
return s_adjust_expiration(max_execution_time);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
time_t s_session_expiration(zend_long maxlifetime)
|
|
{
|
|
if (maxlifetime > 0) {
|
|
return s_adjust_expiration(maxlifetime);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static
|
|
zend_bool s_lock_session(memcached_st *memc, zend_string *sid)
|
|
{
|
|
memcached_return rc;
|
|
char *lock_key;
|
|
size_t lock_key_len;
|
|
time_t expiration;
|
|
zend_long wait_time, retries;
|
|
php_memcached_user_data *user_data = memcached_get_user_data(memc);
|
|
|
|
lock_key_len = spprintf(&lock_key, 0, "lock.%s", sid->val);
|
|
expiration = s_lock_expiration();
|
|
|
|
wait_time = MEMC_SESS_INI(lock_wait_min);
|
|
retries = MEMC_SESS_INI(lock_retries);
|
|
|
|
do {
|
|
rc = memcached_add(memc, lock_key, lock_key_len, "1", sizeof ("1") - 1, expiration, 0);
|
|
|
|
switch (rc) {
|
|
|
|
case MEMCACHED_SUCCESS:
|
|
user_data->lock_key = zend_string_init(lock_key, lock_key_len, user_data->is_persistent);
|
|
user_data->is_locked = 1;
|
|
break;
|
|
|
|
case MEMCACHED_NOTSTORED:
|
|
case MEMCACHED_DATA_EXISTS:
|
|
if (retries > 0) {
|
|
usleep(wait_time * 1000);
|
|
wait_time = MIN(MEMC_SESS_INI(lock_wait_max), wait_time * 2);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
php_error_docref(NULL, E_WARNING, "Failed to write session lock: %s", memcached_strerror (memc, rc));
|
|
break;
|
|
}
|
|
} while (!user_data->is_locked && retries-- > 0);
|
|
|
|
efree(lock_key);
|
|
return user_data->is_locked;
|
|
}
|
|
|
|
static
|
|
void s_unlock_session(memcached_st *memc)
|
|
{
|
|
php_memcached_user_data *user_data = memcached_get_user_data(memc);
|
|
|
|
if (user_data->is_locked) {
|
|
memcached_delete(memc, user_data->lock_key->val, user_data->lock_key->len, 0);
|
|
user_data->is_locked = 0;
|
|
zend_string_release (user_data->lock_key);
|
|
}
|
|
}
|
|
|
|
static
|
|
zend_bool s_configure_from_ini_values(memcached_st *memc, zend_bool silent)
|
|
{
|
|
memcached_return rc;
|
|
|
|
#define check_set_behavior(behavior, value) \
|
|
if ((rc = memcached_behavior_set(memc, (behavior), (value))) != MEMCACHED_SUCCESS) { \
|
|
if (!silent) { php_error_docref(NULL, E_WARNING, "failed to initialise session memcached configuration: %s", memcached_strerror(memc, rc)); } \
|
|
return 0; \
|
|
}
|
|
|
|
if (MEMC_SESS_INI(binary_protocol_enabled)) {
|
|
check_set_behavior(MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1);
|
|
}
|
|
|
|
if (MEMC_SESS_INI(consistent_hash_enabled)) {
|
|
check_set_behavior(MEMCACHED_BEHAVIOR_KETAMA, 1);
|
|
}
|
|
|
|
if (MEMC_SESS_INI(server_failure_limit)) {
|
|
check_set_behavior(MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, MEMC_SESS_INI(server_failure_limit));
|
|
}
|
|
|
|
if (MEMC_SESS_INI(number_of_replicas)) {
|
|
check_set_behavior(MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, MEMC_SESS_INI(number_of_replicas));
|
|
}
|
|
|
|
if (MEMC_SESS_INI(randomize_replica_read_enabled)) {
|
|
check_set_behavior(MEMCACHED_BEHAVIOR_RANDOMIZE_REPLICA_READ, 1);
|
|
}
|
|
|
|
if (MEMC_SESS_INI(remove_failed_servers_enabled)) {
|
|
check_set_behavior(MEMCACHED_BEHAVIOR_REMOVE_FAILED_SERVERS, 1);
|
|
}
|
|
|
|
if (MEMC_SESS_INI(connect_timeout)) {
|
|
check_set_behavior(MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT, MEMC_SESS_INI(connect_timeout));
|
|
}
|
|
|
|
if (MEMC_SESS_STR_INI(prefix)) {
|
|
memcached_callback_set(memc, MEMCACHED_CALLBACK_NAMESPACE, MEMC_SESS_STR_INI(prefix));
|
|
}
|
|
|
|
if (MEMC_SESS_STR_INI(sasl_username) && MEMC_SESS_STR_INI(sasl_password)) {
|
|
php_memcached_user_data *user_data;
|
|
|
|
if (!php_memc_init_sasl_if_needed()) {
|
|
return 0;
|
|
}
|
|
|
|
check_set_behavior(MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1);
|
|
|
|
if (memcached_set_sasl_auth_data(memc, MEMC_SESS_STR_INI(sasl_username), MEMC_SESS_STR_INI(sasl_password)) == MEMCACHED_FAILURE) {
|
|
php_error_docref(NULL, E_WARNING, "failed to set memcached session sasl credentials");
|
|
return 0;
|
|
}
|
|
user_data = memcached_get_user_data(memc);
|
|
user_data->has_sasl_data = 1;
|
|
}
|
|
|
|
#undef safe_set_behavior
|
|
|
|
return 1;
|
|
}
|
|
|
|
static
|
|
void *s_pemalloc_fn(const memcached_st *memc, size_t size, void *context)
|
|
{
|
|
zend_bool *is_persistent = memcached_get_user_data(memc);
|
|
|
|
return
|
|
pemalloc(size, *is_persistent);
|
|
}
|
|
|
|
static
|
|
void s_pefree_fn(const memcached_st *memc, void *mem, void *context)
|
|
{
|
|
zend_bool *is_persistent = memcached_get_user_data(memc);
|
|
|
|
return
|
|
pefree(mem, *is_persistent);
|
|
}
|
|
|
|
static
|
|
void *s_perealloc_fn(const memcached_st *memc, void *mem, const size_t size, void *context)
|
|
{
|
|
zend_bool *is_persistent = memcached_get_user_data(memc);
|
|
|
|
return
|
|
perealloc(mem, size, *is_persistent);
|
|
}
|
|
|
|
static
|
|
void *s_pecalloc_fn(const memcached_st *memc, size_t nelem, const size_t elsize, void *context)
|
|
{
|
|
zend_bool *is_persistent = memcached_get_user_data(memc);
|
|
|
|
return
|
|
pecalloc(nelem, elsize, *is_persistent);
|
|
}
|
|
|
|
|
|
static
|
|
memcached_st *s_init_mod_data (const memcached_server_list_st servers, zend_bool is_persistent)
|
|
{
|
|
void *buffer;
|
|
php_memcached_user_data *user_data;
|
|
memcached_st *memc;
|
|
|
|
buffer = pecalloc(1, sizeof(memcached_st), is_persistent);
|
|
memc = memcached_create (buffer);
|
|
|
|
if (!memc) {
|
|
php_error_docref(NULL, E_ERROR, "failed to allocate memcached structure");
|
|
/* not reached */
|
|
}
|
|
|
|
memcached_set_memory_allocators(memc, s_pemalloc_fn, s_pefree_fn, s_perealloc_fn, s_pecalloc_fn, NULL);
|
|
|
|
user_data = pecalloc(1, sizeof(php_memcached_user_data), is_persistent);
|
|
user_data->is_persistent = is_persistent;
|
|
user_data->has_sasl_data = 0;
|
|
user_data->lock_key = NULL;
|
|
user_data->is_locked = 0;
|
|
|
|
memcached_set_user_data(memc, user_data);
|
|
memcached_server_push (memc, servers);
|
|
memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_VERIFY_KEY, 1);
|
|
return memc;
|
|
}
|
|
|
|
PS_OPEN_FUNC(memcached)
|
|
{
|
|
memcached_st *memc = NULL;
|
|
char *plist_key = NULL;
|
|
size_t plist_key_len = 0;
|
|
|
|
memcached_server_list_st servers;
|
|
|
|
// First parse servers
|
|
servers = memcached_servers_parse(save_path);
|
|
|
|
if (!servers) {
|
|
php_error_docref(NULL, E_WARNING, "failed to parse session.save_path");
|
|
PS_SET_MOD_DATA(NULL);
|
|
return FAILURE;
|
|
}
|
|
|
|
if (MEMC_SESS_INI(persistent_enabled)) {
|
|
zend_resource *le_p;
|
|
|
|
plist_key_len = spprintf(&plist_key, 0, "memc-session:%s", save_path);
|
|
|
|
if ((le_p = zend_hash_str_find_ptr(&EG(persistent_list), plist_key, plist_key_len)) != NULL) {
|
|
if (le_p->type == s_memc_sess_list_entry()) {
|
|
memc = (memcached_st *) le_p->ptr;
|
|
|
|
if (!s_configure_from_ini_values(memc, 1)) {
|
|
// Remove existing plist entry
|
|
zend_hash_str_del(&EG(persistent_list), plist_key, plist_key_len);
|
|
memc = NULL;
|
|
}
|
|
else {
|
|
efree(plist_key);
|
|
PS_SET_MOD_DATA(memc);
|
|
return SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
memc = s_init_mod_data(servers, MEMC_SESS_INI(persistent_enabled));
|
|
memcached_server_list_free(servers);
|
|
|
|
if (!s_configure_from_ini_values(memc, 0)) {
|
|
if (plist_key) {
|
|
efree(plist_key);
|
|
}
|
|
s_destroy_mod_data(memc);
|
|
PS_SET_MOD_DATA(NULL);
|
|
return FAILURE;
|
|
}
|
|
|
|
if (plist_key) {
|
|
zend_resource le;
|
|
|
|
le.type = s_memc_sess_list_entry();
|
|
le.ptr = memc;
|
|
|
|
GC_REFCOUNT(&le) = 1;
|
|
|
|
/* plist_key is not a persistent allocated key, thus we use str_update here */
|
|
if (zend_hash_str_update_mem(&EG(persistent_list), plist_key, plist_key_len, &le, sizeof(le)) == NULL) {
|
|
php_error_docref(NULL, E_ERROR, "Could not register persistent entry for the memcached session");
|
|
/* not reached */
|
|
}
|
|
efree(plist_key);
|
|
}
|
|
PS_SET_MOD_DATA(memc);
|
|
return SUCCESS;
|
|
}
|
|
|
|
PS_CLOSE_FUNC(memcached)
|
|
{
|
|
php_memcached_user_data *user_data;
|
|
memcached_st *memc = PS_GET_MOD_DATA();
|
|
|
|
if (!memc) {
|
|
php_error_docref(NULL, E_WARNING, "Session is not allocated, check session.save_path value");
|
|
return FAILURE;
|
|
}
|
|
|
|
user_data = memcached_get_user_data(memc);
|
|
|
|
if (user_data->is_locked) {
|
|
s_unlock_session(memc);
|
|
}
|
|
|
|
if (!user_data->is_persistent) {
|
|
s_destroy_mod_data(memc);
|
|
}
|
|
|
|
PS_SET_MOD_DATA(NULL);
|
|
return SUCCESS;
|
|
}
|
|
|
|
PS_READ_FUNC(memcached)
|
|
{
|
|
char *payload = NULL;
|
|
size_t payload_len = 0;
|
|
uint32_t flags = 0;
|
|
memcached_return status;
|
|
memcached_st *memc = PS_GET_MOD_DATA();
|
|
|
|
if (!memc) {
|
|
php_error_docref(NULL, E_WARNING, "Session is not allocated, check session.save_path value");
|
|
return FAILURE;
|
|
}
|
|
|
|
if (MEMC_SESS_INI(lock_enabled)) {
|
|
if (!s_lock_session(memc, key)) {
|
|
php_error_docref(NULL, E_WARNING, "Unable to clear session lock record");
|
|
return FAILURE;
|
|
}
|
|
}
|
|
|
|
payload = memcached_get(memc, key->val, key->len, &payload_len, &flags, &status);
|
|
|
|
if (status == MEMCACHED_SUCCESS) {
|
|
*val = zend_string_init(payload, payload_len, 0);
|
|
efree(payload);
|
|
return SUCCESS;
|
|
} else if (status == MEMCACHED_NOTFOUND) {
|
|
*val = ZSTR_EMPTY_ALLOC();
|
|
return SUCCESS;
|
|
} else {
|
|
php_error_docref(NULL TSRMLS_CC, E_WARNING, "error getting session from memcached: %s", memcached_last_error_message(memc));
|
|
return FAILURE;
|
|
}
|
|
}
|
|
|
|
PS_WRITE_FUNC(memcached)
|
|
{
|
|
zend_long retries = 1;
|
|
memcached_st *memc = PS_GET_MOD_DATA();
|
|
time_t expiration = s_session_expiration(maxlifetime);
|
|
|
|
if (!memc) {
|
|
php_error_docref(NULL, E_WARNING, "Session is not allocated, check session.save_path value");
|
|
return FAILURE;
|
|
}
|
|
|
|
/* Set the number of write retry attempts to the number of replicas times the number of attempts to remove a server plus the initial write */
|
|
if (MEMC_SESS_INI(remove_failed_servers_enabled)) {
|
|
zend_long replicas, failure_limit;
|
|
|
|
replicas = memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS);
|
|
failure_limit = memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT);
|
|
|
|
retries = 1 + replicas * (failure_limit + 1);
|
|
}
|
|
|
|
do {
|
|
if (memcached_set(memc, key->val, key->len, val->val, val->len, expiration, 0) == MEMCACHED_SUCCESS) {
|
|
return SUCCESS;
|
|
} else {
|
|
php_error_docref(NULL TSRMLS_CC, E_WARNING, "error saving session to memcached: %s", memcached_last_error_message(memc));
|
|
}
|
|
} while (--retries > 0);
|
|
|
|
return FAILURE;
|
|
}
|
|
|
|
PS_DESTROY_FUNC(memcached)
|
|
{
|
|
php_memcached_user_data *user_data;
|
|
memcached_st *memc = PS_GET_MOD_DATA();
|
|
|
|
if (!memc) {
|
|
php_error_docref(NULL, E_WARNING, "Session is not allocated, check session.save_path value");
|
|
return FAILURE;
|
|
}
|
|
|
|
memcached_delete(memc, key->val, key->len, 0);
|
|
user_data = memcached_get_user_data(memc);
|
|
|
|
if (user_data->is_locked) {
|
|
s_unlock_session(memc);
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
PS_GC_FUNC(memcached)
|
|
{
|
|
return SUCCESS;
|
|
}
|
|
|
|
PS_CREATE_SID_FUNC(memcached)
|
|
{
|
|
zend_string *sid;
|
|
memcached_st *memc = PS_GET_MOD_DATA();
|
|
|
|
if (!memc) {
|
|
sid = php_session_create_id(NULL);
|
|
}
|
|
else {
|
|
int retries = 3;
|
|
while (retries-- > 0) {
|
|
sid = php_session_create_id((void **) &memc);
|
|
|
|
if (memcached_add (memc, sid->val, sid->len, NULL, 0, s_lock_expiration(), 0) == MEMCACHED_SUCCESS) {
|
|
break;
|
|
}
|
|
zend_string_release(sid);
|
|
sid = NULL;
|
|
}
|
|
}
|
|
return sid;
|
|
}
|
|
|
|
PS_VALIDATE_SID_FUNC(memcached)
|
|
{
|
|
memcached_st *memc = PS_GET_MOD_DATA();
|
|
|
|
if (memcached_exist(memc, key->val, key->len) == MEMCACHED_SUCCESS) {
|
|
return SUCCESS;
|
|
} else {
|
|
return FAILURE;
|
|
}
|
|
}
|
|
|
|
PS_UPDATE_TIMESTAMP_FUNC(memcached)
|
|
{
|
|
memcached_st *memc = PS_GET_MOD_DATA();
|
|
time_t expiration = s_session_expiration(maxlifetime);
|
|
|
|
if (memcached_touch(memc, key->val, key->len, expiration) == MEMCACHED_FAILURE) {
|
|
return FAILURE;
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
/* }}} */
|
|
|