EV-Memcached
view release on metacpan or search on metacpan
src/EV__Memcached.xs view on Meta::CPAN
int fd;
int connected;
int connecting;
/* IO watchers */
ev_io rio, wio;
int reading, writing;
/* Buffers */
char *rbuf;
size_t rbuf_len, rbuf_cap;
char *wbuf;
size_t wbuf_len, wbuf_off, wbuf_cap;
/* Callbacks */
SV *on_error;
SV *on_connect;
SV *on_disconnect;
/* Command queue */
ngx_queue_t cb_queue;
ngx_queue_t wait_queue;
int pending_count;
int waiting_count;
int max_pending; /* 0 = unlimited */
uint32_t next_opaque;
/* Reconnection */
char *host;
int port;
char *path;
int reconnect;
int reconnect_delay_ms;
int max_reconnect_attempts;
int reconnect_attempts;
ev_timer reconnect_timer;
int reconnect_timer_active;
int intentional_disconnect;
int resume_waiting_on_reconnect;
/* Timeouts */
int connect_timeout_ms;
ev_timer connect_timer;
int connect_timer_active;
int command_timeout_ms;
ev_timer cmd_timer;
int cmd_timer_active;
/* Flow control */
int waiting_timeout_ms;
ev_timer waiting_timer;
int waiting_timer_active;
/* Safety */
int callback_depth;
int in_cb_cleanup;
int in_wait_cleanup;
/* Options */
int priority;
int keepalive;
/* SASL auth */
char *username;
char *password;
};
/* ================================================================
* Shared error strings (initialized in BOOT)
* ================================================================ */
static SV *err_skipped = NULL;
static SV *err_disconnected = NULL;
static SV *err_waiting_timeout = NULL;
/* ================================================================
* Forward declarations
* ================================================================ */
static void io_cb(EV_P_ ev_io *w, int revents);
static void reconnect_timer_cb(EV_P_ ev_timer *w, int revents);
static void waiting_timer_cb(EV_P_ ev_timer *w, int revents);
static void connect_timeout_cb(EV_P_ ev_timer *w, int revents);
static void cmd_timeout_cb(EV_P_ ev_timer *w, int revents);
static void arm_cmd_timer(ev_mc_t *self);
static void disarm_cmd_timer(ev_mc_t *self);
static uint32_t mc_enqueue_cmd(pTHX_ ev_mc_t *self,
uint8_t opcode, const char *key, STRLEN key_len,
const char *value, STRLEN value_len,
const char *extras, uint8_t extras_len,
uint64_t cas, int cmd, int quiet, SV *cb);
static void start_reading(ev_mc_t *self);
static void stop_reading(ev_mc_t *self);
static void start_writing(ev_mc_t *self);
static void stop_writing(ev_mc_t *self);
static void start_connect(pTHX_ ev_mc_t *self);
static void cleanup_connection(pTHX_ ev_mc_t *self);
static void emit_error(pTHX_ ev_mc_t *self, const char *msg);
static void handle_disconnect(pTHX_ ev_mc_t *self, const char *reason);
static void schedule_reconnect(pTHX_ ev_mc_t *self);
static void apply_keepalive(ev_mc_t *self);
static void report_connect_error(pTHX_ ev_mc_t *self, const char *errbuf);
static void finish_connect_success(pTHX_ ev_mc_t *self);
static void mc_send_sasl_auth(pTHX_ ev_mc_t *self, SV *cb);
static void stop_connect_timer(ev_mc_t *self);
static void stop_reconnect_timer(ev_mc_t *self);
static void stop_waiting_timer(ev_mc_t *self);
static void send_next_waiting(pTHX_ ev_mc_t *self);
static int check_destroyed(ev_mc_t *self);
static void cancel_pending(pTHX_ ev_mc_t *self, SV *err_sv);
static void cancel_waiting(pTHX_ ev_mc_t *self, SV *err_sv);
/* ================================================================
* Binary protocol helpers (portable, no unaligned access)
* ================================================================ */
static void mc_write_u16(char *buf, uint16_t val) {
val = htons(val);
memcpy(buf, &val, 2);
}
static void mc_write_u32(char *buf, uint32_t val) {
val = htonl(val);
memcpy(buf, &val, 4);
}
static void mc_write_u64(char *buf, uint64_t val) {
uint32_t hi = htonl((uint32_t)(val >> 32));
uint32_t lo = htonl((uint32_t)(val & 0xFFFFFFFF));
memcpy(buf, &hi, 4);
memcpy(buf + 4, &lo, 4);
}
static uint16_t mc_read_u16(const char *buf) {
uint16_t val;
memcpy(&val, buf, 2);
return ntohs(val);
}
static uint32_t mc_read_u32(const char *buf) {
uint32_t val;
memcpy(&val, buf, 4);
return ntohl(val);
}
static uint64_t mc_read_u64(const char *buf) {
uint32_t hi, lo;
memcpy(&hi, buf, 4);
memcpy(&lo, buf + 4, 4);
return ((uint64_t)ntohl(hi) << 32) | ntohl(lo);
}
static void mc_encode_header(char *buf, uint8_t opcode, uint16_t key_len,
uint8_t extras_len, uint32_t body_len, uint32_t opaque, uint64_t cas)
{
buf[0] = MC_REQ_MAGIC;
buf[1] = opcode;
mc_write_u16(buf + 2, key_len);
buf[4] = extras_len;
buf[5] = 0; /* data_type = raw */
mc_write_u16(buf + 6, 0); /* vbucket / reserved */
src/EV__Memcached.xs view on Meta::CPAN
if (result)
mPUSHs(result);
else
PUSHs(&PL_sv_undef);
if (error)
mPUSHs(error);
else
PUSHs(&PL_sv_undef);
PUTBACK;
call_sv(cb, G_DISCARD | G_EVAL);
if (SvTRUE(ERRSV)) {
warn("EV::Memcached: callback error: %s", SvPV_nolen(ERRSV));
sv_setsv(ERRSV, &PL_sv_undef);
}
FREETMPS;
LEAVE;
self->callback_depth--;
}
/* Invoke a user handler with at most one mortal arg; catch exceptions
so a die in user code can't unwind through libev. */
static void invoke_handler(pTHX_ ev_mc_t *self, SV *cb, SV *arg, const char *label) {
if (NULL == cb) { if (arg) SvREFCNT_dec(arg); return; }
/* pin: the callback may clear its own handler ($mc->on_error(undef)),
which would otherwise free the CV while call_sv is still running it */
SvREFCNT_inc_simple_void_NN(cb);
self->callback_depth++;
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
if (arg) XPUSHs(sv_2mortal(arg));
PUTBACK;
call_sv(cb, G_DISCARD | G_EVAL);
if (SvTRUE(ERRSV)) {
warn("EV::Memcached: %s callback error: %s", label, SvPV_nolen(ERRSV));
sv_setsv(ERRSV, &PL_sv_undef);
}
FREETMPS;
LEAVE;
self->callback_depth--;
SvREFCNT_dec(cb);
}
static void emit_error(pTHX_ ev_mc_t *self, const char *msg) {
invoke_handler(aTHX_ self, self->on_error, newSVpv(msg, 0), "on_error");
}
static void emit_connect(pTHX_ ev_mc_t *self) {
invoke_handler(aTHX_ self, self->on_connect, NULL, "on_connect");
}
static void emit_disconnect(pTHX_ ev_mc_t *self) {
invoke_handler(aTHX_ self, self->on_disconnect, NULL, "on_disconnect");
}
static void apply_keepalive(ev_mc_t *self) {
if (self->keepalive <= 0 || self->path) return;
int one = 1;
setsockopt(self->fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one));
#ifdef TCP_KEEPIDLE
setsockopt(self->fd, IPPROTO_TCP, TCP_KEEPIDLE,
&self->keepalive, sizeof(self->keepalive));
#endif
}
/* Common tail for synchronous connect-failure paths in start_connect:
emit error, run pending callbacks, and arm reconnect if configured.
Caller returns immediately after invoking. */
static void report_connect_error(pTHX_ ev_mc_t *self, const char *errbuf) {
self->callback_depth++;
emit_error(aTHX_ self, errbuf);
self->callback_depth--;
if (check_destroyed(self)) return;
if (!self->intentional_disconnect && self->reconnect) {
schedule_reconnect(aTHX_ self);
} else {
/* terminal failure: waiting commands would otherwise hang until DESTROY */
self->callback_depth++;
cancel_waiting(aTHX_ self, err_disconnected);
self->callback_depth--;
check_destroyed(self);
}
}
/* Shared post-connect-success path. Caller must already have set
self->connected = 1 and stopped/initialized the io watchers. */
static void finish_connect_success(pTHX_ ev_mc_t *self) {
self->reconnect_attempts = 0;
start_reading(self);
apply_keepalive(self);
if (self->username && self->password) {
/* Auto-auth: on_connect fires from the SASL_AUTH success path;
on failure the connection drops without it ever firing. */
mc_send_sasl_auth(aTHX_ self, NULL);
return;
}
emit_connect(aTHX_ self);
if (check_destroyed(self)) return;
send_next_waiting(aTHX_ self);
}
/* ================================================================
* Callback entry management
* ================================================================ */
static ev_mc_cb_t* alloc_cbt(void) {
ev_mc_cb_t *cbt;
Newxz(cbt, 1, ev_mc_cb_t);
return cbt;
}
static void cleanup_cbt(pTHX_ ev_mc_cb_t *cbt) {
CLEAR_HANDLER(cbt->cb);
if (cbt->stats_hv) {
SvREFCNT_dec((SV*)cbt->stats_hv);
cbt->stats_hv = NULL;
}
if ((cbt->cmd == CB_CMD_MGET_FENCE || cbt->cmd == CB_CMD_MGETS_FENCE) && cbt->mget_results) {
SvREFCNT_dec((SV*)cbt->mget_results);
cbt->mget_results = NULL;
}
/* MGET_ENTRY has borrowed ref - don't decrement */
Safefree(cbt);
}
static void cleanup_wait(pTHX_ ev_mc_wait_t *wt) {
CLEAR_HANDLER(wt->cb);
if (wt->packet) { Safefree(wt->packet); wt->packet = NULL; }
if (wt->stats_hv) {
SvREFCNT_dec((SV*)wt->stats_hv);
wt->stats_hv = NULL;
}
if ((wt->cmd == CB_CMD_MGET_FENCE || wt->cmd == CB_CMD_MGETS_FENCE) && wt->mget_results) {
SvREFCNT_dec((SV*)wt->mget_results);
wt->mget_results = NULL;
}
Safefree(wt);
}
/* Free every queued cb/wait entry WITHOUT invoking Perl callbacks.
* Used by the DESTROY teardown paths (deferred and global destruction). */
static void free_all_queues(pTHX_ ev_mc_t *self) {
while (!ngx_queue_empty(&self->cb_queue)) {
ngx_queue_t *q = ngx_queue_head(&self->cb_queue);
ev_mc_cb_t *cbt = ngx_queue_data(q, ev_mc_cb_t, queue);
ngx_queue_remove(q);
cleanup_cbt(aTHX_ cbt);
src/EV__Memcached.xs view on Meta::CPAN
if (strEQ(k, "loop") &&
!(SvROK(v) && SvOBJECT(SvRV(v)) && sv_derived_from(v, "EV::Loop")))
croak("loop must be an EV::Loop object");
if ((strEQ(k, "on_error") || strEQ(k, "on_connect") ||
strEQ(k, "on_disconnect")))
(void)mc_handler_arg(aTHX_ v); /* croaks on bad value */
}
}
Newxz(RETVAL, 1, ev_mc_t);
RETVAL->magic = MC_MAGIC_ALIVE;
RETVAL->fd = -1;
RETVAL->port = 11211;
RETVAL->next_opaque = 1; /* reserve 0 for fire-and-forget quiet ops */
ngx_queue_init(&RETVAL->cb_queue);
ngx_queue_init(&RETVAL->wait_queue);
Newx(RETVAL->rbuf, BUF_INIT_SIZE, char);
RETVAL->rbuf_cap = BUF_INIT_SIZE;
Newx(RETVAL->wbuf, BUF_INIT_SIZE, char);
RETVAL->wbuf_cap = BUF_INIT_SIZE;
/* Default error handler: warn. Callback exceptions are caught by
G_EVAL in emit_error, so a `die` would be demoted to a warning
anyway â emit it directly and avoid the double prefix. */
RETVAL->on_error = eval_pv("sub { warn \"EV::Memcached error: @_\\n\" }", TRUE);
SvREFCNT_inc_simple_void_NN(RETVAL->on_error);
/* Parse options */
SV *host_sv = NULL, *path_sv = NULL;
int port = 11211;
int do_reconnect = 0, reconnect_delay = 1000, max_reconnect_attempts = 0;
RETVAL->loop = EV_DEFAULT;
int i;
for (i = 1; i < items; i += 2) {
const char *k = SvPV_nolen(ST(i));
SV *v = ST(i + 1);
if (strEQ(k, "host")) host_sv = v;
else if (strEQ(k, "port")) port = SvIV(v);
else if (strEQ(k, "path")) path_sv = v;
else if (strEQ(k, "on_error")) {
CLEAR_HANDLER(RETVAL->on_error);
SV *h = mc_handler_arg(aTHX_ v);
if (h) RETVAL->on_error = newSVsv(h);
}
else if (strEQ(k, "on_connect")) {
SV *h = mc_handler_arg(aTHX_ v);
if (h) RETVAL->on_connect = newSVsv(h);
}
else if (strEQ(k, "on_disconnect")) {
SV *h = mc_handler_arg(aTHX_ v);
if (h) RETVAL->on_disconnect = newSVsv(h);
}
else if (strEQ(k, "max_pending")) RETVAL->max_pending = SvIV(v);
else if (strEQ(k, "waiting_timeout")) RETVAL->waiting_timeout_ms = SvIV(v);
else if (strEQ(k, "connect_timeout")) RETVAL->connect_timeout_ms = SvIV(v);
else if (strEQ(k, "command_timeout")) RETVAL->command_timeout_ms = SvIV(v);
else if (strEQ(k, "resume_waiting_on_reconnect")) RETVAL->resume_waiting_on_reconnect = SvTRUE(v) ? 1 : 0;
else if (strEQ(k, "priority")) RETVAL->priority = SvIV(v);
else if (strEQ(k, "keepalive")) RETVAL->keepalive = SvIV(v);
else if (strEQ(k, "reconnect")) do_reconnect = SvTRUE(v) ? 1 : 0;
else if (strEQ(k, "reconnect_delay")) reconnect_delay = SvIV(v);
else if (strEQ(k, "max_reconnect_attempts")) max_reconnect_attempts = SvIV(v);
else if (strEQ(k, "username")) {
if (SvOK(v)) RETVAL->username = savepv(SvPV_nolen(v));
}
else if (strEQ(k, "password")) {
if (SvOK(v)) RETVAL->password = savepv(SvPV_nolen(v));
}
else if (strEQ(k, "loop")) {
/* EV's T_LOOP convention: pointer in the IV slot of the
blessed referent (SvPVX there is NULL) */
RETVAL->loop = INT2PTR(struct ev_loop *, SvIVX(SvRV(v)));
RETVAL->loop_sv = newSVsv(v); /* keep a user loop alive */
}
}
if (host_sv && path_sv) {
Safefree(RETVAL->rbuf);
Safefree(RETVAL->wbuf);
CLEAR_HANDLER(RETVAL->on_error);
CLEAR_HANDLER(RETVAL->on_connect);
CLEAR_HANDLER(RETVAL->on_disconnect);
CLEAR_HANDLER(RETVAL->loop_sv);
if (RETVAL->username) Safefree(RETVAL->username);
if (RETVAL->password) Safefree(RETVAL->password);
Safefree(RETVAL);
croak("cannot specify both 'host' and 'path'");
}
RETVAL->port = port;
if (do_reconnect) {
RETVAL->reconnect = 1;
RETVAL->reconnect_delay_ms = reconnect_delay >= 0 ? reconnect_delay : 0;
RETVAL->max_reconnect_attempts = max_reconnect_attempts >= 0 ? max_reconnect_attempts : 0;
}
if (host_sv && SvOK(host_sv)) {
RETVAL->host = savepv(SvPV_nolen(host_sv));
start_connect(aTHX_ RETVAL);
}
else if (path_sv && SvOK(path_sv)) {
RETVAL->path = savepv(SvPV_nolen(path_sv));
start_connect(aTHX_ RETVAL);
}
}
OUTPUT:
RETVAL
void
DESTROY(EV::Memcached self)
CODE:
{
if (self->magic == MC_MAGIC_FREED) return;
/* If we're inside a callback, defer destruction.
* check_destroyed() in io_cb/timer_cb will Safefree after unwind. */
if (self->callback_depth > 0) {
/* Fire pending/waiting callbacks once with "disconnected" (the
DESTRUCTION contract). callback_depth is already >0 and pins
self; refcount 0 means no callback can resurrect it. Under
global destruction, calling into Perl is unsafe: free silently. */
if (!PL_dirty) {
cancel_pending(aTHX_ self, err_disconnected);
cancel_waiting(aTHX_ self, err_disconnected);
}
self->magic = MC_MAGIC_FREED;
/* Stop watchers */
stop_reading(self);
stop_writing(self);
stop_connect_timer(self);
disarm_cmd_timer(self);
src/EV__Memcached.xs view on Meta::CPAN
if (self->command_timeout_ms == 0)
disarm_cmd_timer(self);
else if (!ngx_queue_empty(&self->cb_queue))
arm_cmd_timer(self);
}
RETVAL = self->command_timeout_ms;
}
OUTPUT:
RETVAL
void
reconnect(EV::Memcached self, int enable, int delay_ms = 1000, int max_attempts = 0)
CODE:
{
self->reconnect = enable ? 1 : 0;
self->reconnect_delay_ms = delay_ms >= 0 ? delay_ms : 0;
self->max_reconnect_attempts = max_attempts >= 0 ? max_attempts : 0;
if (!enable) {
self->reconnect_attempts = 0;
stop_reconnect_timer(self);
}
}
int
reconnect_enabled(EV::Memcached self)
CODE:
RETVAL = self->reconnect;
OUTPUT:
RETVAL
int
priority(EV::Memcached self, ...)
CODE:
{
if (items > 1) {
self->priority = SvIV(ST(1));
if (self->priority < -2) self->priority = -2;
if (self->priority > 2) self->priority = 2;
/* Apply to active watchers */
if (self->reading) {
ev_io_stop(self->loop, &self->rio);
ev_set_priority(&self->rio, self->priority);
ev_io_start(self->loop, &self->rio);
} else {
ev_set_priority(&self->rio, self->priority);
}
if (self->writing) {
ev_io_stop(self->loop, &self->wio);
ev_set_priority(&self->wio, self->priority);
ev_io_start(self->loop, &self->wio);
} else {
ev_set_priority(&self->wio, self->priority);
}
}
RETVAL = self->priority;
}
OUTPUT:
RETVAL
int
keepalive(EV::Memcached self, ...)
CODE:
{
if (items > 1) {
self->keepalive = SvIV(ST(1));
if (self->keepalive < 0) self->keepalive = 0;
if (self->connected && self->fd >= 0)
apply_keepalive(self);
}
RETVAL = self->keepalive;
}
OUTPUT:
RETVAL
void
skip_pending(EV::Memcached self)
CODE:
{
self->callback_depth++;
skip_pending_impl(aTHX_ self, err_skipped);
self->callback_depth--;
check_destroyed(self);
}
void
skip_waiting(EV::Memcached self)
CODE:
{
self->callback_depth++;
cancel_waiting(aTHX_ self, err_skipped);
self->callback_depth--;
check_destroyed(self);
}
( run in 2.383 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )