|
Packit |
4e8bc4 |
#!/usr/bin/perl
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
use strict;
|
|
Packit |
4e8bc4 |
use warnings;
|
|
Packit |
4e8bc4 |
use Test::More;
|
|
Packit |
4e8bc4 |
use FindBin qw($Bin);
|
|
Packit |
4e8bc4 |
use lib "$Bin/lib";
|
|
Packit |
4e8bc4 |
use MemcachedTest;
|
|
Packit |
4e8bc4 |
use Data::Dumper qw/Dumper/;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $ext_path;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
if (!supports_extstore()) {
|
|
Packit |
4e8bc4 |
plan skip_all => 'extstore not enabled';
|
|
Packit |
4e8bc4 |
exit 0;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
$ext_path = "/tmp/extstore.$$";
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $server = new_memcached("-m 64 -U 0 -o ext_page_size=8,ext_wbuf_size=2,ext_threads=1,ext_io_depth=2,ext_item_size=512,ext_item_age=2,ext_recache_rate=10000,ext_max_frag=0.9,ext_path=$ext_path:64m,no_lru_crawler,slab_automove=0");
|
|
Packit |
4e8bc4 |
ok($server, "started the server");
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# Based almost 100% off testClient.py which is:
|
|
Packit |
4e8bc4 |
# Copyright (c) 2007 Dustin Sallings <dustin@spy.net>
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# Command constants
|
|
Packit |
4e8bc4 |
use constant CMD_GET => 0x00;
|
|
Packit |
4e8bc4 |
use constant CMD_SET => 0x01;
|
|
Packit |
4e8bc4 |
use constant CMD_ADD => 0x02;
|
|
Packit |
4e8bc4 |
use constant CMD_REPLACE => 0x03;
|
|
Packit |
4e8bc4 |
use constant CMD_DELETE => 0x04;
|
|
Packit |
4e8bc4 |
use constant CMD_INCR => 0x05;
|
|
Packit |
4e8bc4 |
use constant CMD_DECR => 0x06;
|
|
Packit |
4e8bc4 |
use constant CMD_QUIT => 0x07;
|
|
Packit |
4e8bc4 |
use constant CMD_FLUSH => 0x08;
|
|
Packit |
4e8bc4 |
use constant CMD_GETQ => 0x09;
|
|
Packit |
4e8bc4 |
use constant CMD_NOOP => 0x0A;
|
|
Packit |
4e8bc4 |
use constant CMD_VERSION => 0x0B;
|
|
Packit |
4e8bc4 |
use constant CMD_GETK => 0x0C;
|
|
Packit |
4e8bc4 |
use constant CMD_GETKQ => 0x0D;
|
|
Packit |
4e8bc4 |
use constant CMD_APPEND => 0x0E;
|
|
Packit |
4e8bc4 |
use constant CMD_PREPEND => 0x0F;
|
|
Packit |
4e8bc4 |
use constant CMD_STAT => 0x10;
|
|
Packit |
4e8bc4 |
use constant CMD_SETQ => 0x11;
|
|
Packit |
4e8bc4 |
use constant CMD_ADDQ => 0x12;
|
|
Packit |
4e8bc4 |
use constant CMD_REPLACEQ => 0x13;
|
|
Packit |
4e8bc4 |
use constant CMD_DELETEQ => 0x14;
|
|
Packit |
4e8bc4 |
use constant CMD_INCREMENTQ => 0x15;
|
|
Packit |
4e8bc4 |
use constant CMD_DECREMENTQ => 0x16;
|
|
Packit |
4e8bc4 |
use constant CMD_QUITQ => 0x17;
|
|
Packit |
4e8bc4 |
use constant CMD_FLUSHQ => 0x18;
|
|
Packit |
4e8bc4 |
use constant CMD_APPENDQ => 0x19;
|
|
Packit |
4e8bc4 |
use constant CMD_PREPENDQ => 0x1A;
|
|
Packit |
4e8bc4 |
use constant CMD_TOUCH => 0x1C;
|
|
Packit |
4e8bc4 |
use constant CMD_GAT => 0x1D;
|
|
Packit |
4e8bc4 |
use constant CMD_GATQ => 0x1E;
|
|
Packit |
4e8bc4 |
use constant CMD_GATK => 0x23;
|
|
Packit |
4e8bc4 |
use constant CMD_GATKQ => 0x24;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# REQ and RES formats are divided even though they currently share
|
|
Packit |
4e8bc4 |
# the same format, since they _could_ differ in the future.
|
|
Packit |
4e8bc4 |
use constant REQ_PKT_FMT => "CCnCCnNNNN";
|
|
Packit |
4e8bc4 |
use constant RES_PKT_FMT => "CCnCCnNNNN";
|
|
Packit |
4e8bc4 |
use constant INCRDECR_PKT_FMT => "NNNNN";
|
|
Packit |
4e8bc4 |
use constant MIN_RECV_BYTES => length(pack(RES_PKT_FMT));
|
|
Packit |
4e8bc4 |
use constant REQ_MAGIC => 0x80;
|
|
Packit |
4e8bc4 |
use constant RES_MAGIC => 0x81;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $mc = MC::Client->new;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $check = sub {
|
|
Packit |
4e8bc4 |
my ($key, $orig_flags, $orig_val) = @_;
|
|
Packit |
4e8bc4 |
my ($flags, $val, $cas) = $mc->get($key);
|
|
Packit |
4e8bc4 |
is($flags, $orig_flags, "Flags is set properly");
|
|
Packit |
4e8bc4 |
ok($val eq $orig_val || $val == $orig_val, $val . " = " . $orig_val);
|
|
Packit |
4e8bc4 |
};
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $set = sub {
|
|
Packit |
4e8bc4 |
my ($key, $exp, $orig_flags, $orig_value) = @_;
|
|
Packit |
4e8bc4 |
$mc->set($key, $orig_value, $orig_flags, $exp);
|
|
Packit |
4e8bc4 |
$check->($key, $orig_flags, $orig_value);
|
|
Packit |
4e8bc4 |
};
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $empty = sub {
|
|
Packit |
4e8bc4 |
my $key = shift;
|
|
Packit |
4e8bc4 |
my $rv =()= eval { $mc->get($key) };
|
|
Packit |
4e8bc4 |
is($rv, 0, "Didn't get a result from get");
|
|
Packit |
4e8bc4 |
ok($@->not_found, "We got a not found error when we expected one");
|
|
Packit |
4e8bc4 |
};
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $delete = sub {
|
|
Packit |
4e8bc4 |
my ($key, $when) = @_;
|
|
Packit |
4e8bc4 |
$mc->delete($key, $when);
|
|
Packit |
4e8bc4 |
$empty->($key);
|
|
Packit |
4e8bc4 |
};
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $value;
|
|
Packit |
4e8bc4 |
my $bigvalue;
|
|
Packit |
4e8bc4 |
{
|
|
Packit |
4e8bc4 |
my @chars = ("C".."Z");
|
|
Packit |
4e8bc4 |
for (1 .. 20000) {
|
|
Packit |
4e8bc4 |
$value .= $chars[rand @chars];
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
for (1 .. 800000) {
|
|
Packit |
4e8bc4 |
$bigvalue .= $chars[rand @chars];
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# diag "small object";
|
|
Packit |
4e8bc4 |
$set->('x', 10, 19, "somevalue");
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# check extstore counters
|
|
Packit |
4e8bc4 |
{
|
|
Packit |
4e8bc4 |
my %stats = $mc->stats('');
|
|
Packit |
4e8bc4 |
is($stats{extstore_objects_written}, 0);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# diag "Delete";
|
|
Packit |
4e8bc4 |
#$delete->('x');
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# diag "Flush";
|
|
Packit |
4e8bc4 |
#$empty->('y');
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# fill some larger objects
|
|
Packit |
4e8bc4 |
{
|
|
Packit |
4e8bc4 |
my $keycount = 1000;
|
|
Packit |
4e8bc4 |
for (1 .. $keycount) {
|
|
Packit |
4e8bc4 |
$set->("nfoo$_", 0, 19, $value);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
# wait for a flush
|
|
Packit |
4e8bc4 |
sleep 4;
|
|
Packit |
4e8bc4 |
# value returns for one flushed object.
|
|
Packit |
4e8bc4 |
$check->('nfoo1', 19, $value);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# check extstore counters
|
|
Packit |
4e8bc4 |
my %stats = $mc->stats('');
|
|
Packit |
4e8bc4 |
cmp_ok($stats{extstore_page_allocs}, '>', 0, 'at least one page allocated');
|
|
Packit |
4e8bc4 |
cmp_ok($stats{extstore_objects_written}, '>', $keycount / 2, 'some objects written');
|
|
Packit |
4e8bc4 |
cmp_ok($stats{extstore_bytes_written}, '>', length($value) * 2, 'some bytes written');
|
|
Packit |
4e8bc4 |
cmp_ok($stats{get_extstore}, '>', 0, 'one object was fetched');
|
|
Packit |
4e8bc4 |
cmp_ok($stats{extstore_objects_read}, '>', 0, 'one object read');
|
|
Packit |
4e8bc4 |
cmp_ok($stats{extstore_bytes_read}, '>', length($value), 'some bytes read');
|
|
Packit |
4e8bc4 |
# Test multiget
|
|
Packit |
4e8bc4 |
my $rv = $mc->get_multi(qw(nfoo2 nfoo3 noexist));
|
|
Packit |
4e8bc4 |
is($rv->{nfoo2}->[1], $value, 'multiget nfoo2');
|
|
Packit |
4e8bc4 |
is($rv->{nfoo3}->[1], $value, 'multiget nfoo2');
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# Remove half of the keys for the next test.
|
|
Packit |
4e8bc4 |
for (1 .. $keycount) {
|
|
Packit |
4e8bc4 |
next unless $_ % 2 == 0;
|
|
Packit |
4e8bc4 |
$delete->("nfoo$_");
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my %stats2 = $mc->stats('');
|
|
Packit |
4e8bc4 |
cmp_ok($stats{extstore_bytes_used}, '>', $stats2{extstore_bytes_used},
|
|
Packit |
4e8bc4 |
'bytes used dropped after deletions');
|
|
Packit |
4e8bc4 |
cmp_ok($stats{extstore_objects_used}, '>', $stats2{extstore_objects_used},
|
|
Packit |
4e8bc4 |
'objects used dropped after deletions');
|
|
Packit |
4e8bc4 |
is($stats2{badcrc_from_extstore}, 0, 'CRC checks successful');
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# delete the rest
|
|
Packit |
4e8bc4 |
for (1 .. $keycount) {
|
|
Packit |
4e8bc4 |
next unless $_ % 2 == 1;
|
|
Packit |
4e8bc4 |
$delete->("nfoo$_");
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# check evictions and misses
|
|
Packit |
4e8bc4 |
{
|
|
Packit |
4e8bc4 |
my $keycount = 1000;
|
|
Packit |
4e8bc4 |
for (1 .. $keycount) {
|
|
Packit |
4e8bc4 |
$set->("mfoo$_", 0, 19, $value);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
sleep 4;
|
|
Packit |
4e8bc4 |
for ($keycount .. ($keycount*3)) {
|
|
Packit |
4e8bc4 |
$set->("mfoo$_", 0, 19, $value);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
sleep 4;
|
|
Packit |
4e8bc4 |
# FIXME: Need to sample through a few values, or fix eviction to be
|
|
Packit |
4e8bc4 |
# more accurate. On 32bit systems some pages unused to this point get
|
|
Packit |
4e8bc4 |
# filled after the first few items, then the eviction algo pulls those
|
|
Packit |
4e8bc4 |
# pages since they have the lowest version number, leaving older objects
|
|
Packit |
4e8bc4 |
# in memory and evicting newer ones.
|
|
Packit |
4e8bc4 |
for (1 .. ($keycount*3)) {
|
|
Packit |
4e8bc4 |
next unless $_ % 100 == 0;
|
|
Packit |
4e8bc4 |
eval { $mc->get("mfoo$_"); };
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my %s = $mc->stats('');
|
|
Packit |
4e8bc4 |
cmp_ok($s{extstore_objects_evicted}, '>', 0);
|
|
Packit |
4e8bc4 |
cmp_ok($s{miss_from_extstore}, '>', 0);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# store and re-fetch a chunked value
|
|
Packit |
4e8bc4 |
{
|
|
Packit |
4e8bc4 |
my %stats = $mc->stats('');
|
|
Packit |
4e8bc4 |
$set->("bigvalue", 0, 0, $bigvalue);
|
|
Packit |
4e8bc4 |
sleep 4;
|
|
Packit |
4e8bc4 |
$check->("bigvalue", 0, $bigvalue);
|
|
Packit |
4e8bc4 |
my %stats2 = $mc->stats('');
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
cmp_ok($stats2{extstore_objects_written}, '>',
|
|
Packit |
4e8bc4 |
$stats{extstore_objects_written}, "a large value flushed");
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# ensure ASCII can still fetch the chunked value.
|
|
Packit |
4e8bc4 |
{
|
|
Packit |
4e8bc4 |
my $ns = $server->new_sock;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my %s1 = $mc->stats('');
|
|
Packit |
4e8bc4 |
mem_get_is($ns, "bigvalue", $bigvalue);
|
|
Packit |
4e8bc4 |
print $ns "extstore recache_rate 1\r\n";
|
|
Packit |
4e8bc4 |
is(scalar <$ns>, "OK\r\n", "recache rate upped");
|
|
Packit |
4e8bc4 |
for (1..3) {
|
|
Packit |
4e8bc4 |
mem_get_is($ns, "bigvalue", $bigvalue);
|
|
Packit |
4e8bc4 |
$check->('bigvalue', 0, $bigvalue);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
my %s2 = $mc->stats('');
|
|
Packit |
4e8bc4 |
cmp_ok($s2{recache_from_extstore}, '>', $s1{recache_from_extstore},
|
|
Packit |
4e8bc4 |
'a new recache happened');
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
done_testing();
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
END {
|
|
Packit |
4e8bc4 |
unlink $ext_path if $ext_path;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
# ######################################################################
|
|
Packit |
4e8bc4 |
# Test ends around here.
|
|
Packit |
4e8bc4 |
# ######################################################################
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
package MC::Client;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
use strict;
|
|
Packit |
4e8bc4 |
use warnings;
|
|
Packit |
4e8bc4 |
use fields qw(socket);
|
|
Packit |
4e8bc4 |
use IO::Socket::INET;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub new {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($s) = @_;
|
|
Packit |
4e8bc4 |
$s = $server unless defined $s;
|
|
Packit |
4e8bc4 |
my $sock = $s->sock;
|
|
Packit |
4e8bc4 |
$self = fields::new($self);
|
|
Packit |
4e8bc4 |
$self->{socket} = $sock;
|
|
Packit |
4e8bc4 |
return $self;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub build_command {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
die "Not enough args to send_command" unless @_ >= 4;
|
|
Packit |
4e8bc4 |
my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
$extra_header = '' unless defined $extra_header;
|
|
Packit |
4e8bc4 |
my $keylen = length($key);
|
|
Packit |
4e8bc4 |
my $vallen = length($val);
|
|
Packit |
4e8bc4 |
my $extralen = length($extra_header);
|
|
Packit |
4e8bc4 |
my $datatype = 0; # field for future use
|
|
Packit |
4e8bc4 |
my $reserved = 0; # field for future use
|
|
Packit |
4e8bc4 |
my $totallen = $keylen + $vallen + $extralen;
|
|
Packit |
4e8bc4 |
my $ident_hi = 0;
|
|
Packit |
4e8bc4 |
my $ident_lo = 0;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
if ($cas) {
|
|
Packit |
4e8bc4 |
$ident_hi = int($cas / 2 ** 32);
|
|
Packit |
4e8bc4 |
$ident_lo = int($cas % 2 ** 32);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $msg = pack(::REQ_PKT_FMT, ::REQ_MAGIC, $cmd, $keylen, $extralen,
|
|
Packit |
4e8bc4 |
$datatype, $reserved, $totallen, $opaque, $ident_hi,
|
|
Packit |
4e8bc4 |
$ident_lo);
|
|
Packit |
4e8bc4 |
my $full_msg = $msg . $extra_header . $key . $val;
|
|
Packit |
4e8bc4 |
return $full_msg;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub send_command {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
die "Not enough args to send_command" unless @_ >= 4;
|
|
Packit |
4e8bc4 |
my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $full_msg = $self->build_command($cmd, $key, $val, $opaque, $extra_header, $cas);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $sent = $self->{socket}->send($full_msg);
|
|
Packit |
4e8bc4 |
die("Send failed: $!") unless $sent;
|
|
Packit |
4e8bc4 |
if($sent != length($full_msg)) {
|
|
Packit |
4e8bc4 |
die("only sent $sent of " . length($full_msg) . " bytes");
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub flush_socket {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
$self->{socket}->flush;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# Send a silent command and ensure it doesn't respond.
|
|
Packit |
4e8bc4 |
sub send_silent {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
die "Not enough args to send_silent" unless @_ >= 4;
|
|
Packit |
4e8bc4 |
my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
$self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
|
|
Packit |
4e8bc4 |
$self->send_command(::CMD_NOOP, '', '', $opaque + 1);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my ($ropaque, $data) = $self->_handle_single_response;
|
|
Packit |
4e8bc4 |
Test::More::is($ropaque, $opaque + 1);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub silent_mutation {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($cmd, $key, $value) = @_;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
$empty->($key);
|
|
Packit |
4e8bc4 |
my $extra = pack "NN", 82, 0;
|
|
Packit |
4e8bc4 |
$mc->send_silent($cmd, $key, $value, 7278552, $extra, 0);
|
|
Packit |
4e8bc4 |
$check->($key, 82, $value);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub _handle_single_response {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my $myopaque = shift;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $hdr = "";
|
|
Packit |
4e8bc4 |
while(::MIN_RECV_BYTES - length($hdr) > 0) {
|
|
Packit |
4e8bc4 |
$self->{socket}->recv(my $response, ::MIN_RECV_BYTES - length($hdr));
|
|
Packit |
4e8bc4 |
$hdr .= $response;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
Test::More::is(length($hdr), ::MIN_RECV_BYTES, "Expected read length");
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my ($magic, $cmd, $keylen, $extralen, $datatype, $status, $remaining,
|
|
Packit |
4e8bc4 |
$opaque, $ident_hi, $ident_lo) = unpack(::RES_PKT_FMT, $hdr);
|
|
Packit |
4e8bc4 |
Test::More::is($magic, ::RES_MAGIC, "Got proper response magic");
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $cas = ($ident_hi * 2 ** 32) + $ident_lo;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
return ($opaque, '', $cas, 0) if($remaining == 0);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# fetch the value
|
|
Packit |
4e8bc4 |
my $rv="";
|
|
Packit |
4e8bc4 |
while($remaining - length($rv) > 0) {
|
|
Packit |
4e8bc4 |
$self->{socket}->recv(my $buf, $remaining - length($rv));
|
|
Packit |
4e8bc4 |
$rv .= $buf;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
if(length($rv) != $remaining) {
|
|
Packit |
4e8bc4 |
my $found = length($rv);
|
|
Packit |
4e8bc4 |
die("Expected $remaining bytes, got $found");
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
if (defined $myopaque) {
|
|
Packit |
4e8bc4 |
Test::More::is($opaque, $myopaque, "Expected opaque");
|
|
Packit |
4e8bc4 |
} else {
|
|
Packit |
4e8bc4 |
Test::More::pass("Implicit pass since myopaque is undefined");
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
if ($status) {
|
|
Packit |
4e8bc4 |
die MC::Error->new($status, $rv);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
return ($opaque, $rv, $cas, $keylen);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub _do_command {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
die unless @_ >= 3;
|
|
Packit |
4e8bc4 |
my ($cmd, $key, $val, $extra_header, $cas) = @_;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
$extra_header = '' unless defined $extra_header;
|
|
Packit |
4e8bc4 |
my $opaque = int(rand(2**32));
|
|
Packit |
4e8bc4 |
$self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
|
|
Packit |
4e8bc4 |
my (undef, $rv, $rcas) = $self->_handle_single_response($opaque);
|
|
Packit |
4e8bc4 |
return ($rv, $rcas);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub _incrdecr_header {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($amt, $init, $exp) = @_;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $amt_hi = int($amt / 2 ** 32);
|
|
Packit |
4e8bc4 |
my $amt_lo = int($amt % 2 ** 32);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $init_hi = int($init / 2 ** 32);
|
|
Packit |
4e8bc4 |
my $init_lo = int($init % 2 ** 32);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $extra_header = pack(::INCRDECR_PKT_FMT, $amt_hi, $amt_lo, $init_hi,
|
|
Packit |
4e8bc4 |
$init_lo, $exp);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
return $extra_header;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub _incrdecr_cas {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($cmd, $key, $amt, $init, $exp) = @_;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my ($data, $rcas) = $self->_do_command($cmd, $key, '',
|
|
Packit |
4e8bc4 |
$self->_incrdecr_header($amt, $init, $exp));
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $header = substr $data, 0, 8, '';
|
|
Packit |
4e8bc4 |
my ($resp_hi, $resp_lo) = unpack "NN", $header;
|
|
Packit |
4e8bc4 |
my $resp = ($resp_hi * 2 ** 32) + $resp_lo;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
return $resp, $rcas;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub _incrdecr {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($v, $c) = $self->_incrdecr_cas(@_);
|
|
Packit |
4e8bc4 |
return $v
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub silent_incrdecr {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($cmd, $key, $amt, $init, $exp) = @_;
|
|
Packit |
4e8bc4 |
my $opaque = 8275753;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
$mc->send_silent($cmd, $key, '', $opaque,
|
|
Packit |
4e8bc4 |
$mc->_incrdecr_header($amt, $init, $exp));
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub stats {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my $key = shift;
|
|
Packit |
4e8bc4 |
my $cas = 0;
|
|
Packit |
4e8bc4 |
my $opaque = int(rand(2**32));
|
|
Packit |
4e8bc4 |
$self->send_command(::CMD_STAT, $key, '', $opaque, '', $cas);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my %rv = ();
|
|
Packit |
4e8bc4 |
my $found_key = '';
|
|
Packit |
4e8bc4 |
my $found_val = '';
|
|
Packit |
4e8bc4 |
do {
|
|
Packit |
4e8bc4 |
my ($op, $data, $cas, $keylen) = $self->_handle_single_response($opaque);
|
|
Packit |
4e8bc4 |
if($keylen > 0) {
|
|
Packit |
4e8bc4 |
$found_key = substr($data, 0, $keylen);
|
|
Packit |
4e8bc4 |
$found_val = substr($data, $keylen);
|
|
Packit |
4e8bc4 |
$rv{$found_key} = $found_val;
|
|
Packit |
4e8bc4 |
} else {
|
|
Packit |
4e8bc4 |
$found_key = '';
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
} while($found_key ne '');
|
|
Packit |
4e8bc4 |
return %rv;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub get {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my $key = shift;
|
|
Packit |
4e8bc4 |
my ($rv, $cas) = $self->_do_command(::CMD_GET, $key, '', '');
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $header = substr $rv, 0, 4, '';
|
|
Packit |
4e8bc4 |
my $flags = unpack("N", $header);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
return ($flags, $rv, $cas);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub get_multi {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my @keys = @_;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
for (my $i = 0; $i < @keys; $i++) {
|
|
Packit |
4e8bc4 |
$self->send_command(::CMD_GETQ, $keys[$i], '', $i, '', 0);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $terminal = @keys + 10;
|
|
Packit |
4e8bc4 |
$self->send_command(::CMD_NOOP, '', '', $terminal);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my %return;
|
|
Packit |
4e8bc4 |
while (1) {
|
|
Packit |
4e8bc4 |
my ($opaque, $data) = $self->_handle_single_response;
|
|
Packit |
4e8bc4 |
last if $opaque == $terminal;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $header = substr $data, 0, 4, '';
|
|
Packit |
4e8bc4 |
my $flags = unpack("N", $header);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
$return{$keys[$opaque]} = [$flags, $data];
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
return %return if wantarray;
|
|
Packit |
4e8bc4 |
return \%return;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub touch {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($key, $expire) = @_;
|
|
Packit |
4e8bc4 |
my $extra_header = pack "N", $expire;
|
|
Packit |
4e8bc4 |
my $cas = 0;
|
|
Packit |
4e8bc4 |
return $self->_do_command(::CMD_TOUCH, $key, '', $extra_header, $cas);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub gat {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my $key = shift;
|
|
Packit |
4e8bc4 |
my $expire = shift;
|
|
Packit |
4e8bc4 |
my $extra_header = pack "N", $expire;
|
|
Packit |
4e8bc4 |
my ($rv, $cas) = $self->_do_command(::CMD_GAT, $key, '', $extra_header);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
my $header = substr $rv, 0, 4, '';
|
|
Packit |
4e8bc4 |
my $flags = unpack("N", $header);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
return ($flags, $rv, $cas);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub version {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
return $self->_do_command(::CMD_VERSION, '', '');
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub flush {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
return $self->_do_command(::CMD_FLUSH, '', '');
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub add {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($key, $val, $flags, $expire) = @_;
|
|
Packit |
4e8bc4 |
my $extra_header = pack "NN", $flags, $expire;
|
|
Packit |
4e8bc4 |
my $cas = 0;
|
|
Packit |
4e8bc4 |
return $self->_do_command(::CMD_ADD, $key, $val, $extra_header, $cas);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub set {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($key, $val, $flags, $expire, $cas) = @_;
|
|
Packit |
4e8bc4 |
my $extra_header = pack "NN", $flags, $expire;
|
|
Packit |
4e8bc4 |
return $self->_do_command(::CMD_SET, $key, $val, $extra_header, $cas);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub _append_prepend {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($cmd, $key, $val, $cas) = @_;
|
|
Packit |
4e8bc4 |
return $self->_do_command($cmd, $key, $val, '', $cas);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub replace {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($key, $val, $flags, $expire) = @_;
|
|
Packit |
4e8bc4 |
my $extra_header = pack "NN", $flags, $expire;
|
|
Packit |
4e8bc4 |
my $cas = 0;
|
|
Packit |
4e8bc4 |
return $self->_do_command(::CMD_REPLACE, $key, $val, $extra_header, $cas);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub delete {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($key) = @_;
|
|
Packit |
4e8bc4 |
return $self->_do_command(::CMD_DELETE, $key, '');
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub incr {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($key, $amt, $init, $exp) = @_;
|
|
Packit |
4e8bc4 |
$amt = 1 unless defined $amt;
|
|
Packit |
4e8bc4 |
$init = 0 unless defined $init;
|
|
Packit |
4e8bc4 |
$exp = 0 unless defined $exp;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
return $self->_incrdecr(::CMD_INCR, $key, $amt, $init, $exp);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub incr_cas {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($key, $amt, $init, $exp) = @_;
|
|
Packit |
4e8bc4 |
$amt = 1 unless defined $amt;
|
|
Packit |
4e8bc4 |
$init = 0 unless defined $init;
|
|
Packit |
4e8bc4 |
$exp = 0 unless defined $exp;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
return $self->_incrdecr_cas(::CMD_INCR, $key, $amt, $init, $exp);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub decr {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
my ($key, $amt, $init, $exp) = @_;
|
|
Packit |
4e8bc4 |
$amt = 1 unless defined $amt;
|
|
Packit |
4e8bc4 |
$init = 0 unless defined $init;
|
|
Packit |
4e8bc4 |
$exp = 0 unless defined $exp;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
return $self->_incrdecr(::CMD_DECR, $key, $amt, $init, $exp);
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub noop {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
return $self->_do_command(::CMD_NOOP, '', '');
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
package MC::Error;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
use strict;
|
|
Packit |
4e8bc4 |
use warnings;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
use constant ERR_UNKNOWN_CMD => 0x81;
|
|
Packit |
4e8bc4 |
use constant ERR_NOT_FOUND => 0x1;
|
|
Packit |
4e8bc4 |
use constant ERR_EXISTS => 0x2;
|
|
Packit |
4e8bc4 |
use constant ERR_TOO_BIG => 0x3;
|
|
Packit |
4e8bc4 |
use constant ERR_EINVAL => 0x4;
|
|
Packit |
4e8bc4 |
use constant ERR_NOT_STORED => 0x5;
|
|
Packit |
4e8bc4 |
use constant ERR_DELTA_BADVAL => 0x6;
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
use overload '""' => sub {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
return "Memcache Error ($self->[0]): $self->[1]";
|
|
Packit |
4e8bc4 |
};
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub new {
|
|
Packit |
4e8bc4 |
my $class = shift;
|
|
Packit |
4e8bc4 |
my $error = [@_];
|
|
Packit |
4e8bc4 |
my $self = bless $error, (ref $class || $class);
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
return $self;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub not_found {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
return $self->[0] == ERR_NOT_FOUND;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub exists {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
return $self->[0] == ERR_EXISTS;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub too_big {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
return $self->[0] == ERR_TOO_BIG;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub delta_badval {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
return $self->[0] == ERR_DELTA_BADVAL;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
sub einval {
|
|
Packit |
4e8bc4 |
my $self = shift;
|
|
Packit |
4e8bc4 |
return $self->[0] == ERR_EINVAL;
|
|
Packit |
4e8bc4 |
}
|
|
Packit |
4e8bc4 |
|
|
Packit |
4e8bc4 |
# vim: filetype=perl
|
|
Packit |
4e8bc4 |
|