Async-Redis
view release on metacpan or search on metacpan
"ExtUtils::MakeMaker" : "0"
}
},
"develop" : {
"requires" : {
"Dist::Zilla" : "0"
}
},
"runtime" : {
"recommends" : {
"IO::Socket::SSL" : "0",
"Protocol::Redis::XS" : "0"
},
"requires" : {
"Digest::SHA" : "0",
"Future" : "0.49",
"Future::AsyncAwait" : "0.66",
"Future::IO" : "0.23",
"Future::Selector" : "0.05",
"IO::Socket::INET" : "0",
"Protocol::Redis" : "0",
generated_by: 'Dist::Zilla version 6.032, CPAN::Meta::Converter version 2.150010'
license: artistic_2
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: '1.4'
name: Async-Redis
no_index:
directory:
- examples
recommends:
IO::Socket::SSL: '0'
Protocol::Redis::XS: '0'
requires:
Digest::SHA: '0'
Future: '0.49'
Future::AsyncAwait: '0.66'
Future::IO: '0.23'
Future::Selector: '0.05'
IO::Socket::INET: '0'
Protocol::Redis: '0'
Socket: '0'
**Required:**
- Future::IO (0.19+)
- Future::AsyncAwait
- Protocol::Redis
**Recommended:**
- Protocol::Redis::XS (faster parsing)
- IO::Async (or your preferred event loop)
**Optional:**
- IO::Socket::SSL (for TLS)
- OpenTelemetry::SDK (for observability)
## See Also
- [Future::IO](https://metacpan.org/pod/Future::IO) - The underlying async I/O abstraction
- [Future::AsyncAwait](https://metacpan.org/pod/Future::AsyncAwait) - Async/await syntax
- [Redis](https://metacpan.org/pod/Redis) - Synchronous Redis client
- [Net::Async::Redis](https://metacpan.org/pod/Net::Async::Redis) - Another async Redis client
## Author
requires 'Future::IO', '0.23';
requires 'Future::Selector', '0.05';
requires 'Protocol::Redis';
requires 'IO::Socket::INET';
requires 'Socket';
requires 'Time::HiRes';
requires 'Digest::SHA';
# Optional dependencies (for performance/features)
recommends 'Protocol::Redis::XS'; # Faster RESP parsing
recommends 'IO::Socket::SSL'; # TLS support
suggests 'OpenTelemetry::SDK'; # Observability integration
# Test dependencies
on 'test' => sub {
requires 'Test2::V0';
requires 'Test::Lib';
};
# Development dependencies
on 'develop' => sub {
lib/Async/Redis.pm view on Meta::CPAN
? $raw / 1000
: $raw + 0;
# Zero means block indefinitely â no client-side deadline
return undef if $seconds == 0;
return Time::HiRes::time() + $seconds + $self->{blocking_timeout_buffer};
}
sub _ssl_verify_peer {
require IO::Socket::SSL;
return IO::Socket::SSL::SSL_VERIFY_PEER();
}
sub _ssl_verify_none {
require IO::Socket::SSL;
return IO::Socket::SSL::SSL_VERIFY_NONE();
}
# Build the IO::Socket::SSL option hash for the current connection.
# Handles chain verification, SNI, hostname identity checking, and
# client cert/key/CA forwarding. Called by _tls_upgrade and directly
# by unit tests.
sub _build_tls_options {
my ($self) = @_;
my %ssl_opts = (SSL_startHandshake => 0);
my $tls = $self->{tls};
my $tls_hash = ref $tls eq 'HASH' ? $tls : {};
lib/Async/Redis.pm view on Meta::CPAN
$ssl_opts{SSL_verify_mode} = $self->_ssl_verify_none;
}
return %ssl_opts;
}
# Non-blocking TLS upgrade
async sub _tls_upgrade {
my ($self, $socket) = @_;
require IO::Socket::SSL;
my %ssl_opts = $self->_build_tls_options;
# Start SSL (does not block because SSL_startHandshake => 0)
IO::Socket::SSL->start_SSL($socket, %ssl_opts)
or die Async::Redis::Error::Connection->new(
message => "SSL setup failed: " . IO::Socket::SSL::errstr(),
host => $self->{host},
port => $self->{port},
);
# Drive handshake with non-blocking loop
my $deadline = Time::HiRes::time() + $self->{connect_timeout};
while (1) {
# Check timeout
if (Time::HiRes::time() >= $deadline) {
lib/Async/Redis.pm view on Meta::CPAN
if ($rv) {
# Handshake complete!
return $socket;
}
# Check what the handshake needs
my $remaining = $deadline - Time::HiRes::time();
$remaining = 0.1 if $remaining <= 0;
if ($IO::Socket::SSL::SSL_ERROR == IO::Socket::SSL::SSL_ERROR_WANT_READ()) {
# Wait for socket to become readable with timeout
my $read_f = Future::IO->waitfor_readable($socket);
my $timeout_f = Future::IO->sleep($remaining)->then(sub {
return Future->fail('tls_timeout');
});
my $wait_f = Future->wait_any($read_f, $timeout_f);
await $wait_f;
if ($wait_f->is_failed) {
die Async::Redis::Error::Timeout->new(
message => "TLS handshake timed out",
timeout => $self->{connect_timeout},
);
}
}
elsif ($IO::Socket::SSL::SSL_ERROR == IO::Socket::SSL::SSL_ERROR_WANT_WRITE()) {
# Wait for socket to become writable with timeout
my $write_f = Future::IO->waitfor_writable($socket);
my $timeout_f = Future::IO->sleep($remaining)->then(sub {
return Future->fail('tls_timeout');
});
my $wait_f = Future->wait_any($write_f, $timeout_f);
await $wait_f;
if ($wait_f->is_failed) {
die Async::Redis::Error::Timeout->new(
message => "TLS handshake timed out",
timeout => $self->{connect_timeout},
);
}
}
else {
# Actual error
die Async::Redis::Error::Connection->new(
message => "TLS handshake failed: " . IO::Socket::SSL::errstr(),
host => $self->{host},
port => $self->{port},
);
}
}
}
# Reconnect with exponential backoff
async sub _reconnect {
my ($self) = @_;
t/10-connection/tls.t view on Meta::CPAN
use warnings;
use Test::Lib;
use Test::Async::Redis ':redis';
use Test2::V0;
use Async::Redis;
use Time::HiRes qw(time);
# Helper: await a Future and return its result (throws on failure)
subtest 'TLS module availability' => sub {
my $has_ssl = eval { require IO::Socket::SSL; 1 };
ok(1, "IO::Socket::SSL " . ($has_ssl ? "available" : "not available"));
};
subtest 'constructor accepts TLS parameters' => sub {
my $redis = Async::Redis->new(
host => 'localhost',
tls => 1,
);
is($redis->{tls}, 1, 'tls enabled');
};
t/10-connection/tls.t view on Meta::CPAN
subtest 'rediss URI enables TLS' => sub {
my $redis = Async::Redis->new(
uri => 'rediss://localhost:6380',
);
ok($redis->{tls}, 'TLS enabled from rediss://');
};
SKIP: {
my $has_ssl = eval { require IO::Socket::SSL; 1 };
skip "IO::Socket::SSL not available", 1 unless $has_ssl;
subtest 'TLS without server fails gracefully' => sub {
my $redis = Async::Redis->new(
host => 'localhost',
port => 16380, # unlikely to have TLS Redis here
tls => 1,
connect_timeout => 1,
);
my $error;
t/10-connection/tls.t view on Meta::CPAN
ok($error, 'connection failed (expected - no TLS server)');
};
}
# TLS tests with actual TLS Redis require specific setup
SKIP: {
skip "Set TLS_REDIS_HOST and TLS_REDIS_PORT to test TLS", 3
unless $ENV{TLS_REDIS_HOST} && $ENV{TLS_REDIS_PORT};
my $has_ssl = eval { require IO::Socket::SSL; 1 };
skip "IO::Socket::SSL not available", 3 unless $has_ssl;
subtest 'TLS connection works' => sub {
my $redis = Async::Redis->new(
host => $ENV{TLS_REDIS_HOST},
port => $ENV{TLS_REDIS_PORT},
tls => {
verify => 0, # skip verification for testing
},
);
( run in 2.056 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )