NAME
EV::Redis - Asynchronous redis client using hiredis and EV
SYNOPSIS
use EV::Redis;
my $redis = EV::Redis->new;
$redis->connect('127.0.0.1');
# or
my $redis = EV::Redis->new( host => '127.0.0.1' );
# command
$redis->set('foo' => 'bar', sub {
my ($res, $err) = @_;
print $res; # OK
$redis->get('foo', sub {
my ($res, $err) = @_;
print $res; # bar
$redis->disconnect;
});
});
# start main loop
EV::run;
DESCRIPTION
EV::Redis is a fork of EV::Hiredis by Daisuke Murase (typester), extended with reconnection, flow control, TLS, and RESP3 support. It is a drop-in replacement for EV::Hiredis, except that commands which would pair replies with the wrong callbacks croak (see Reply order note).
This is an asynchronous client for Redis using hiredis and EV as backend. It connects to EV with C-level interface so that it runs faster.
ANYEVENT INTEGRATION
AnyEvent has a support for EV as its one of backends, so EV::Redis can be used in your AnyEvent applications seamlessly.
NO UTF-8 SUPPORT
Unlike other redis modules, this module doesn't support utf-8 string.
This module handles all values as bytes: a string with characters above 0xFF croaks with a "Wide character" error. Encode utf-8 strings before passing them:
use Encode;
# set $val
$redis->set(foo => encode_utf8 $val, sub { ... });
# get $val
$redis->get('foo', sub {
my $val = decode_utf8 $_[0];
});
SIGPIPE
Writing to a connection the server has closed raises SIGPIPE, which kills the process by default. Set $SIG{PIPE} = 'IGNORE' to get the error in on_error instead.
FORK
A child process must not use an inherited object: its connection fails in the child with "connection inherited from the parent process" (with reconnect, the child then connects on its own); the parent is not affected. A loop other than the default needs $loop->loop_fork in the child. Do not fork inside a callback. Objects cannot be copied: Storable and Sereal with freeze_callbacks croak on them. Other serialized or Clone copies are inert: their methods croak and destroying them does nothing.
RESP3 REPLIES
After HELLO 3, maps and sets arrive as array references (a map as a flat key, value list), doubles as numbers, booleans as 1 or 0, and big numbers and verbatim strings as plain strings. Attribute replies are not supported: one ahead of a reply is dropped, one inside an array or map fails the connection.
METHODS
new(%options);
Create new EV::Redis instance.
Available %options are:
host => 'Str'
port => 'Int'
Hostname and port number of redis-server to connect. Mutually exclusive with
path.path => 'Str'
UNIX socket path to connect. Mutually exclusive with
host.on_error => $cb->($errstr)
Called on connection-level errors. The default handler dies, but exceptions thrown in handlers and command callbacks are caught and reported as warnings, so connection errors become warnings unless you install your own.
undefhere keeps the default;on_error(undef)removes it. They never endEV::run, including one from a%SIGhandler such asalarm: usecommand_timeoutor anEV::timerfor timeouts.on_connect => $cb->()
Called when the connection is established (with
tls, once TCP is up; a failed handshake then arrives ason_error).on_disconnect => $cb->()
Called when the connection closes, normally or on error.
on_push => $cb->($reply)
Called with RESP3 push messages (Redis 6.0+), as an array reference.
The handlers can be set later with the methods of the same name.
connect_timeout => $num_of_milliseconds
Connection timeout.
command_timeout => $num_of_milliseconds
Command timeout.
max_pending => $num
waiting_timeout => $num_of_milliseconds
See the methods of the same name.
resume_waiting_on_reconnect => $bool
If true and
reconnectis on, commands waiting locally are kept across a lost connection and sent after reconnecting. Otherwise (the default) a lost connection or failed connect attempt cancels them.disconnect(), or giving up on reconnecting, cancels them either way. A transaction does not survive: commands issued insideWATCH/MULTI..EXECare replayed only when none of the transaction reached the server, and fail with the lost connection otherwise.reconnect => $bool
Enable automatic reconnection on connection failure or unexpected disconnection. Default is disabled (0).
reconnect_delay => $num_of_milliseconds
Delay between reconnection attempts. Default is 1000 (1 second). Used only with
reconnect, as ismax_reconnect_attempts.max_reconnect_attempts => $num
Maximum number of reconnect attempts in a row; an established connection resets the count. 0 (default) means unlimited.
priority => $num
Priority for the underlying libev IO watchers. Higher priority watchers are invoked before lower priority ones. Valid range is -2 (lowest) to +2 (highest), with 0 being the default. See EV documentation for details on priorities.
keepalive => $seconds
Enable TCP keepalive probes on idle connections, with this interval in seconds (at most 32767; the interval itself is set with glibc and on macOS only). 0 means disabled (default). Ignored for unix sockets.
prefer_ipv4 => $bool
prefer_ipv6 => $bool
Resolve host names to that address family, falling back to the other only when the name has no address of it. IPv4 is the default.
source_addr => 'Str'
Local address to bind the outbound connection to. Useful on multi-homed servers to select a specific network interface. Ignored for unix sockets.
tcp_user_timeout => $num_of_milliseconds
Set TCP_USER_TIMEOUT (Linux): how long sent data may stay unacknowledged before the connection is dropped. Ignored for unix sockets; where the system lacks the option, TCP connects fail.
cloexec => $bool
Set close-on-exec on the Redis connection socket. Prevents the file descriptor from leaking to child processes after fork/exec. Default is enabled.
reuseaddr => $bool
Set SO_REUSEADDR on the Redis connection socket. Allows rebinding to an address that is still in TIME_WAIT state. Default is disabled. Only takes effect with
source_addr.tls => $bool
Enable TLS/SSL encryption for the connection. Requires that the module was built with TLS support (auto-detected at build time, or forced with
EV_REDIS_SSL=1). Only valid withhostconnections, notpath.tls_ca => 'Str'
Path to CA certificate file for server verification. If not specified, uses the system default CA store.
tls_capath => 'Str'
Path to a directory containing CA certificate files in OpenSSL-compatible format (hashed filenames). Alternative to
tls_cafor multiple CA certs.tls_cert => 'Str'
Path to client certificate file for mutual TLS authentication. Must be specified together with
tls_key.tls_key => 'Str'
Path to client private key file. Must be specified together with
tls_cert.tls_server_name => 'Str'
Server name for SNI, sent on every connection; without it no SNI is sent. It is not checked against the certificate.
tls_verify => $bool
Verify the server certificate (default true). Only the chain is checked, not the host name, so use
tls_cawith a CA dedicated to your Redis servers rather than the system store.loop => 'EV::Loop',
EV loop for running this instance. Default is
EV::default_loop.
All parameters are optional. Unknown ones warn, unless new is called on a subclass.
If parameters about connection (host&port or path) is not passed, you should call connect or connect_unix method by hand to connect to redis-server.
connect($hostname [, $port])
connect_unix($path)
Connect to a redis-server for $hostname:$port (default 6379) or $path. Croaks if a connection is already active or the port or path is invalid. A failure found at once (a missing socket, a name that does not resolve) reaches on_error before it returns. Host names are resolved synchronously, by each reconnect too, outside connect_timeout: pass an IP address where DNS can be slow.
command($commands..., [$cb->($result, $error)])
Do a redis command and return its result by callback. Returns REDIS_OK (0), or REDIS_ERR (-1) if it could not be enqueued (the callback gets the error too).
$redis->command('get', 'foo', sub {
my ($result, $error) = @_;
print $result; # value for key 'foo'
print $error; # redis error string, undef if no error
});
On error, $error holds the message and $result is undef; otherwise $error is undef. An error inside an array reply (such as EXEC's result for a failed queued command) arrives as its error text.
The callback is optional: only a code reference in the last position is taken as one, so an undef there is sent as an argument. Without one the command is fire-and-forget: its reply and errors are discarded (connection errors still reach on_error):
$redis->set('counter', 42); # fire-and-forget, no callback
With a million commands outstanding, a closure per command makes their completion take minutes; share one code reference instead.
All commands can also be called via the AUTOLOAD interface:
$redis->command('get', 'foo', sub { ... });
is equivalent to:
$redis->get('foo', sub { ... });
The Redis COMMAND command itself is reached as $redis->command('command', ...), since command is this method.
Note: command() croaks with "connection required before calling command" while not connected, unless a reconnect is pending: commands then wait locally (see resume_waiting_on_reconnect). In on_error and on_disconnect it still croaks, as the reconnect is scheduled after they return. A retry issued from a failed command's callback waits for the reconnect or fails in turn (after a failed connect with no reconnect scheduled, it croaks); it never recurses.
Pub/Sub note: subscribe and psubscribe take at least one name and a persistent callback, which also receives the unsubscribe confirmations (a callback passed to unsubscribe is ignored unless the command is refused). Subscribing again to a name moves it to the new callback. An error reply on a subscribed connection closes it, so keep pub/sub on a connection of its own; set keepalive to notice a server that vanished, as a subscribed connection has no timeout. When the connection closes, a subscribe callback gets one error for each channel or pattern it still holds. Sharded pub/sub (ssubscribe, sunsubscribe) is not supported and croaks; spublish works.
MONITOR note: monitor requires an idle connection, so it cannot be issued from within a reply callback; once it is active command() croaks on that connection. pmonitor is not supported. Use a dedicated one.
Reply order note: hiredis hands each reply to the oldest waiting callback, so commands that change how the server answers croak: CLIENT REPLY OFF and SKIP, REPLCONF ACK and GETACK, SYNC, PSYNC, and RESET while subscribed. Pub/sub commands, monitor and HELLO inside MULTI fail through their callback; pub/sub and HELLO first wait locally for outstanding transaction replies, a round trip each. An EXEC cancelled before it was sent leaves the transaction open.
Nested event loop note: while a callback for an event on this connection runs, its I/O is paused, so a nested EV::run inside it cannot receive replies for the same connection. Use a separate connection to wait for Redis inside a callback.
disconnect
Disconnect from redis-server; safe when already disconnected. Stops any pending reconnect and cancels waiting commands with "disconnected" before it returns. Commands already sent are not cancelled (except on a subscribed connection): the connection closes once they are answered, then on_disconnect runs. It runs only for a connection that was established. So it does not drop a server that stopped answering: use command_timeout, or destroy the object. Sending QUIT instead is reported as a lost connection, and reconnect connects again.
is_connected
Returns true (1) if a connection context is active (including while the connection is being established), false (0) otherwise.
has_ssl
Class method. Returns true (1) if the module was built with TLS support, false (0) otherwise.
if (EV::Redis->has_ssl) {
# TLS connections are available
}
connect_timeout([$ms])
Get or set the connection timeout in milliseconds (0 disables; undef if never set). It covers the connect attempt after name resolution; with tls, the TCP connect only (command_timeout ends a stalled handshake once a command is outstanding). Without it, a unix socket whose server has a full listen queue is retried in a busy loop.
command_timeout([$ms])
Get or set the command timeout in milliseconds (0 disables; undef if never set). It fires when replies are outstanding and nothing has arrived for that long; new commands do not extend it, a large command going out does. Subscriptions are not covered; an idle MONITOR connection times out. A command that timed out, or lost its connection, may still have run, or run later: retry only commands safe to run twice. Changes apply at once.
on_error([$cb->($errstr)])
Set the error callback. Like all handler methods (on_error, on_connect, on_disconnect, on_push): a CODE reference replaces the handler and is returned; undef or no argument clears it (so the current handler cannot be read); any other value clears it with a warning.
on_connect([$cb->()])
Set the connect callback. Commands issued from it go first, past max_pending, so it suits per-connection setup such as AUTH or SELECT. Commands waiting locally go after that setup; one sent while the connection was being established goes before it, unless both reconnect and resume_waiting_on_reconnect are on.
on_disconnect([$cb->()])
Set the disconnect callback, called on both normal and error disconnections.
on_push([$cb->($reply)])
Set the RESP3 push callback (Redis 6.0+); it receives the push message as an array reference.
$redis->on_push(sub {
my ($msg) = @_;
# $msg is an array ref, e.g. ['invalidate', ['key1', 'key2']]
});
reconnect($enable, $delay_ms, $max_attempts)
Configure automatic reconnection.
$redis->reconnect(1); # enable with defaults (1s delay, unlimited)
$redis->reconnect(1, 0); # enable with immediate reconnect
$redis->reconnect(1, 2000); # enable with 2 second delay
$redis->reconnect(1, 1000, 5); # enable with 1s delay, max 5 attempts
$redis->reconnect(0); # disable
$delay_ms defaults to 1000; 0 retries at once, in a busy loop against a server that refuses connections. $max_attempts defaults to 0 (unlimited). Explicit undef keeps the current value of that argument.
It reconnects after a failed connect or an unexpected disconnection, not after disconnect(). A new connection starts fresh: subscriptions, AUTH, SELECT and HELLO are not restored, so issue them from on_connect.
reconnect_enabled
Returns true (1) if automatic reconnection is enabled, false (0) otherwise.
pending_count
Returns the number of commands sent to Redis awaiting replies, not counting (p)subscribe, (p)unsubscribe and monitor. Inside a reply callback the count still includes that command.
waiting_count
Returns the number of commands queued locally, not yet sent: over max_pending, during a reconnect, or held for transaction replies, and those queued behind them.
max_pending($limit)
Get or set the maximum number of commands sent to Redis at once (0, the default, means unlimited); further commands wait locally and go out as replies arrive. (P)subscribe, (p)unsubscribe and monitor hold no slot.
waiting_timeout($ms)
Get or set the maximum time in milliseconds a command can wait locally before it fails with "waiting timeout" (0, the default, means unlimited). Time held only for transaction replies does not count.
resume_waiting_on_reconnect($bool)
Get or set the option of the same name (see new).
priority($priority)
Get or set the priority for the underlying libev IO watchers. Higher priority watchers are invoked before lower priority ones when multiple watchers are pending. Valid range is -2 (lowest) to +2 (highest), with 0 being the default. Values outside this range are clamped automatically. Can be changed at any time, including while connected.
$redis->priority(1); # higher priority
$redis->priority(-1); # lower priority
$redis->priority(99); # clamped to 2
my $prio = $redis->priority; # get current priority
keepalive($seconds)
Get or set the TCP keepalive interval (see new). A positive value set while connected over TCP applies at once, and croaks if the system refuses it; 0 applies from the next connection.
prefer_ipv4($bool)
prefer_ipv6($bool)
Get or set the address family preference (see new); setting one to a true value clears the other. Takes effect on the next connection.
source_addr($addr)
Get or set the local address to bind TCP connections to (undef clears). Takes effect on the next connection.
tcp_user_timeout($ms)
cloexec($bool)
reuseaddr($bool)
Get or set the option of the same name (see new). Takes effect on the next connection.
skip_waiting
Cancel only waiting (not yet sent) command callbacks. Each callback is invoked with (undef, "skipped"). In-flight commands continue normally. Commands issued by those callbacks are not cancelled.
skip_pending
Cancel all pending and waiting command callbacks: each is invoked at once with (undef, "skipped"), and replies that arrive later are discarded. Commands issued by those callbacks are not cancelled. On a MONITOR connection the monitor stream is left running.
can($method)
Returns a code reference for real methods, and for Redis commands once they have been called; undef otherwise.
DESTRUCTION BEHAVIOR
When an EV::Redis object is destroyed with commands still pending or waiting, their callbacks get "disconnected" (pending ones get the connection error, if one is in flight). An object still alive at global destruction (a package variable, or one kept by a reference cycle) runs no callbacks.
Circular references: callbacks that close over $redis form a cycle that keeps the object alive. Break it by clearing the handlers:
$redis->on_error(undef);
$redis->on_connect(undef);
$redis->on_disconnect(undef);
$redis->on_push(undef);
BENCHMARKS
Measured on Linux with Unix socket connection, 100,000 commands with 100-byte values, Perl 5.40, Redis 8.x (bench/benchmark.pl in the source repository, BENCH_COMMANDS=100000):
Pipeline SET ~107K ops/sec
Pipeline GET ~112K ops/sec
Mixed workload ~112K ops/sec
Fire-and-forget SET ~655K ops/sec
Sequential round-trip ~39K ops/sec (SET+GET pairs)
Fire-and-forget mode (no callback) is roughly 6x faster than callback mode due to zero Perl-side overhead per command. Pipeline throughput is bounded by the event loop round-trip, not by hiredis or the network.
Flow control (max_pending) has minimal impact at reasonable limits:
unlimited ~180K ops/sec
max_pending=500 ~186K ops/sec
max_pending=100 ~146K ops/sec
Run perl bench/benchmark.pl in a checkout of the repository for full results. Set BENCH_COMMANDS and BENCH_VALUE_SIZE environment variables to customize; the default of 10,000 commands gives different rates.
AUTHOR
Daisuke Murase (typester) (original EV::Hiredis)
vividsnow
COPYRIGHT AND LICENSE
Copyright (c) 2013 Daisuke Murase, 2026 vividsnow. All rights reserved.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.