EV-Memcached

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

Revision history for EV::Memcached

0.01  2026-04-06
    - Initial release
    - Pure XS binary protocol, no external C library
    - Full command set, multi-get (mget/mgets), fire-and-forget (SETQ/FLUSHQ)
    - SASL PLAIN auth (manual + auto-auth on connect/reconnect)
    - Pipelining, flow control, reconnection, connect/command timeouts
    - TCP, Unix socket, keepalive, EV event loop integration

README  view on Meta::CPAN

        Keep waiting queue on disconnect for replay after reconnect.

    reconnect => $bool
        Enable automatic reconnection.

    reconnect_delay => $ms (default 1000)
    max_reconnect_attempts => $num (0 = unlimited)
    priority => $num (-2 to +2)
        EV watcher priority.

    keepalive => $seconds
        TCP keepalive interval.

    username => 'Str'
    password => 'Str'
        SASL PLAIN authentication credentials. When both are set, the client
        automatically authenticates after connecting (and after each
        reconnect). Requires memcached started with "-S" flag and SASL
        support compiled in.

    loop => EV::Loop
        EV loop to use. Default: "EV::default_loop".

README  view on Meta::CPAN


  waiting_timeout([$ms])
    Get/set local queue timeout.

  resume_waiting_on_reconnect([$bool])
    Get/set waiting queue behavior on disconnect.

  priority([$num])
    Get/set EV watcher priority (-2 to +2).

  keepalive([$seconds])
    Get/set TCP keepalive.

  skip_pending
    Cancel all pending command callbacks with "(undef, "skipped")".

  skip_waiting
    Cancel all waiting command callbacks with "(undef, "skipped")".

  on_error([$cb])
  on_connect([$cb])
  on_disconnect([$cb])

lib/EV/Memcached.pm  view on Meta::CPAN

Enable automatic reconnection.

=item reconnect_delay => $ms (default 1000)

=item max_reconnect_attempts => $num (0 = unlimited)

=item priority => $num (-2 to +2)

EV watcher priority.

=item keepalive => $seconds

TCP keepalive interval.

=item username => 'Str'

=item password => 'Str'

SASL PLAIN authentication credentials. When both are set, the client
automatically authenticates after connecting (and after each reconnect).
Requires memcached started with C<-S> flag and SASL support compiled in.

=item loop => EV::Loop

lib/EV/Memcached.pm  view on Meta::CPAN

Get/set local queue timeout.

=head2 resume_waiting_on_reconnect([$bool])

Get/set waiting queue behavior on disconnect.

=head2 priority([$num])

Get/set EV watcher priority (-2 to +2).

=head2 keepalive([$seconds])

Get/set TCP keepalive.

=head2 skip_pending

Cancel all pending command callbacks with C<(undef, "skipped")>.

=head2 skip_waiting

Cancel all waiting command callbacks with C<(undef, "skipped")>.

=head2 on_error([$cb])

src/EV__Memcached.xs  view on Meta::CPAN

    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)
 * ================================================================ */

src/EV__Memcached.xs  view on Meta::CPAN


    stop_writing(self);
    if (self->connect_timer_active) {
        ev_timer_stop(self->loop, &self->connect_timer);
        self->connect_timer_active = 0;
    }

    /* Setup read watcher */
    start_reading(self);

    /* TCP keepalive */
    if (self->keepalive > 0) {
        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
    }

    mc_send_sasl_auth(aTHX_ self, NULL);

    emit_connect(aTHX_ self);
    if (check_destroyed(self)) return;

    /* Defer waiting queue drain until SASL auth completes (if active) */
    if (!self->username)

src/EV__Memcached.xs  view on Meta::CPAN

        self->connected = 1;
        ev_io_init(&self->rio, io_cb, self->fd, EV_READ);
        self->rio.data = (void *)self;
        ev_io_init(&self->wio, io_cb, self->fd, EV_WRITE);
        self->wio.data = (void *)self;
        ev_set_priority(&self->rio, self->priority);
        ev_set_priority(&self->wio, self->priority);

        start_reading(self);

        /* TCP keepalive */
        if (self->keepalive > 0 && !self->path) {
            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
        }

        self->reconnect_attempts = 0;

        mc_send_sasl_auth(aTHX_ self, NULL);

        emit_connect(aTHX_ self);
        if (check_destroyed(self)) return;
        if (!self->username)

src/EV__Memcached.xs  view on Meta::CPAN

        }
        else if (strEQ(k, "on_disconnect")) {
            if (SvOK(v) && SvROK(v)) RETVAL->on_disconnect = newSVsv(v);
        }
        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")) {

src/EV__Memcached.xs  view on Meta::CPAN

        } 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;
        /* Apply to current connection if active */
        if (self->connected && self->fd >= 0 && self->keepalive > 0) {
            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
        }
    }
    RETVAL = self->keepalive;
}
OUTPUT:
    RETVAL

void
skip_pending(EV::Memcached self)
CODE:
{
    cancel_pending_impl(aTHX_ self, err_skipped, 1);
}

t/04_features.t  view on Meta::CPAN

    run_ev();
    $mc->disconnect;
}

# --- new() constructor in XS ---
{
    my $mc = EV::Memcached->new(
        host            => $host,
        port            => $port,
        max_pending     => 10,
        keepalive       => 5,
        priority        => 1,
        connect_timeout => 3000,
        on_error        => sub { diag "error: @_" },
    );
    $mc->on_connect(sub { EV::break });
    my $t = EV::timer 5, 0, sub { fail("timeout"); EV::break };
    EV::run;

    ok($mc->is_connected, "XS new() connected");
    is($mc->max_pending, 10, "XS new() max_pending");



( run in 1.203 second using v1.01-cache-2.11-cpan-39bf76dae61 )