Skip Menu |

This queue is for tickets about the Cache-Memcached CPAN distribution.

Report information
The Basics
Id: 62872
Status: resolved
Priority: 0/
Queue: Cache-Memcached

People
Owner: Nobody in particular
Requestors: dst [...] heise.de
Cc:
AdminCc:

Bug Information
Severity: Important
Broken in: 1.29
Fixed in: (no value)



Subject: Problems when using more than one instance of C::M
Hi, beginning with version 1.29 it is impossible to use more than one instance of Cache::Memcached. Calling "disconnect_all" on one instance can leave another instance in a broken state, making it die on subsequent calls. I've attached a small script that can reproduce this problem here using Perl 5.12.1 and Cache::MemCached 1.29. The same script works properly on version 1.28. I think there is some confusion around the instance variables "buckets", "buck2sock" and the lexical variables "sock_map" and "cache_sock". I have attached a patch that makes "sock_map" and "cache_sock" local to each instance. The patch seems to fix this issue for us but it has not gotten much testing and I'm not sure whether it has any side effects. Cheers, Dennis
Subject: instances.diff
--- /tmp/Memcached.pm 2010-11-09 15:54:43.447990000 +0100 +++ /opt/perl/5.12.1/local/share/perl/5.12.1/Cache/Memcached.pm 2010-11-09 16:39:07.000000000 +0100 @@ -27,6 +27,8 @@ connect_timeout cb_connect_fail parser_class buck2sock + cache_sock + sock_map }; # flag definitions @@ -56,7 +58,6 @@ eval { $FLAG_NOSIGNAL = MSG_NOSIGNAL; }; my %host_dead; # host -> unixtime marked dead until -my %cache_sock; # host -> socket my $PROTO_TCP; @@ -80,6 +81,9 @@ $self->{'readonly'} = $args->{'readonly'}; $self->{'parser_class'} = $args->{'parser_class'} || $parser_class; + $self->{'cache_sock'} = {}; + $self->{'sock_map'} = {}; + # TODO: undocumented $self->{'connect_timeout'} = $args->{'connect_timeout'} || 0.25; $self->{'select_timeout'} = $args->{'select_timeout'} || 1.0; @@ -104,6 +108,9 @@ $self->init_buckets; $self->{'buck2sock'}= []; + $self->{'cache_sock'} = {}; + $self->{'sock_map'} = {}; + $self->{'_single_sock'} = undef; if (@{$self->{'servers'}} == 1) { $self->{'_single_sock'} = $self->{'servers'}[0]; @@ -164,16 +171,14 @@ $self->{'stat_callback'} = $stat_callback; } -my %sock_map; # stringified-$sock -> "$ip:$port" - sub _dead_sock { my ($self, $sock, $ret, $dead_for) = @_; - if (my $ipport = $sock_map{$sock}) { + if (my $ipport = $self->{sock_map}{$sock}) { my $now = time(); $host_dead{$ipport} = $now + $dead_for if $dead_for; - delete $cache_sock{$ipport}; - delete $sock_map{$sock}; + delete $self->{cache_sock}{$ipport}; + delete $self->{sock_map}{$sock}; } $self->{'buck2sock'} = [] if $self; return $ret; # 0 or undef, probably, depending on what caller wants @@ -181,10 +186,10 @@ sub _close_sock { my ($self, $sock) = @_; - if (my $ipport = $sock_map{$sock}) { + if (my $ipport = $self->{sock_map}{$sock}) { close $sock; - delete $cache_sock{$ipport}; - delete $sock_map{$sock}; + delete $self->{cache_sock}{$ipport}; + delete $self->{sock_map}{$sock}; } $self->{'buck2sock'} = []; } @@ -231,7 +236,7 @@ sub sock_to_host { # (host) #why is this public? I wouldn't have to worry about undef $self if it weren't. my Cache::Memcached $self = ref $_[0] ? shift : undef; my $host = $_[0]; - return $cache_sock{$host} if $cache_sock{$host}; + return $self->{cache_sock}{$host} if $self->{cache_sock}{$host}; my $now = time(); my ($ip, $port) = $host =~ /(.*):(\d+)$/; @@ -255,12 +260,12 @@ if ($HAVE_SOCKET6 && index($prefip, ':') != -1) { no strict 'subs'; # for PF_INET6 and AF_INET6, weirdly imported socket($sock, PF_INET6, SOCK_STREAM, $proto); - $sock_map{$sock} = $host; + $self->{sock_map}{$sock} = $host; $sin = Socket6::pack_sockaddr_in6($port, Socket6::inet_pton(AF_INET6, $prefip)); } else { socket($sock, PF_INET, SOCK_STREAM, $proto); - $sock_map{$sock} = $host; + $self->{sock_map}{$sock} = $host; $sin = Socket::sockaddr_in($port, Socket::inet_aton($prefip)); } @@ -279,12 +284,12 @@ if ($HAVE_SOCKET6 && index($ip, ':') != -1) { no strict 'subs'; # for PF_INET6 and AF_INET6, weirdly imported socket($sock, PF_INET6, SOCK_STREAM, $proto); - $sock_map{$sock} = $host; + $self->{sock_map}{$sock} = $host; $sin = Socket6::pack_sockaddr_in6($port, Socket6::inet_pton(AF_INET6, $ip)); } else { socket($sock, PF_INET, SOCK_STREAM, $proto); - $sock_map{$sock} = $host; + $self->{sock_map}{$sock} = $host; $sin = Socket::sockaddr_in($port, Socket::inet_aton($ip)); } @@ -297,7 +302,7 @@ } } else { # it's a unix domain/local socket socket($sock, PF_UNIX, SOCK_STREAM, 0); - $sock_map{$sock} = $host; + $self->{sock_map}{$sock} = $host; $sin = Socket::sockaddr_un($host); my $timeout = $self ? $self->{connect_timeout} : 0.25; unless (_connect_sock($sock,$sin,$timeout)) { @@ -312,7 +317,7 @@ $| = 1; select($old); - $cache_sock{$host} = $sock; + $self->{cache_sock}{$host} = $sock; return $sock; } @@ -353,10 +358,10 @@ sub disconnect_all { my Cache::Memcached $self = shift; my $sock; - foreach $sock (values %cache_sock) { + foreach $sock (values %{ $self->{cache_sock} } ) { close $sock; } - %cache_sock = (); + $self->{cache_sock} = {}; $self->{'buck2sock'} = []; } @@ -714,8 +719,8 @@ }; foreach (keys %$sock_keys) { - my $ipport = $sock_map{$_} or die "No map found matching for $_"; - my $sock = $cache_sock{$ipport} or die "No sock found for $ipport"; + my $ipport = $self->{sock_map}{$_} or die "No map found matching for $_"; + my $sock = $self->{cache_sock}{$ipport} or die "No sock found for $ipport"; print STDERR "processing socket $_\n" if $self->{'debug'} >= 2; $writing{$_} = $sock; if ($self->{namespace}) {
Subject: cache-memcached.pl
use strict; use warnings; use Cache::Memcached; my @servers = qw(host01:11211 host02:11211 host03:11211); my $memd1 = Cache::Memcached->new( { servers => \@servers } ); my $memd2 = Cache::Memcached->new( { servers => \@servers } ); $memd1->get_multi("foo"); $memd2->get_multi("bar"); $memd2->disconnect_all; $memd1->get_multi("foo"); # dies with: "No sock found for host03:11211 at /opt/perl/5.12.1/local/share/perl/5.12.1/Cache/Memcached.pm line 718"
From: bergner [...] cs.umu.se
Hi, To anyone using Cache::Memcached, this fix is more important than it would seem at first. The global state being kept in Cache::Memcached screws up modules like Cache::Memcached::Managed if you do things like: * Create a Cache::Memcached::Managed instance $c and $c->set() a value in memcached. * Destroy the $c object, e.g. undef $c; * Fork N child processes * Child processes connect using Cache::Memcached::Managed a do a get() on a value that was set by the parent. With that scenario it is trivial to reproduce problems like sysread() failing with EWOULDBLOCK and select() timing out, the end result being that no data is returned from the get() operation. The attached patch, which is very similar to the one already provided in this issue handles this situation without any noticable issues as far as I can tell at this point. It could be argued that this is a Cache::Memcached::Managed issue, since the documentation of that module claims that "Sockets are automatically reset in forked processes, no manual reset needed". I do think that the current setup with global caching makes it a lot more error prone, and significantly more difficult to build non-trivial functionality on top of Cache::Memcached. Kind regards, Marcus Bergner
Subject: Cache_Memcached.diff
--- original/Cache/Memcached.pm 2011-09-14 14:53:29.000000000 +0200 +++ patched/Cache/Memcached.pm 2011-09-14 17:02:56.000000000 +0200 @@ -26,6 +26,9 @@ bucketcount _single_sock _stime connect_timeout cb_connect_fail parser_class + buck2sock + cache_sock + sock_map }; # flag definitions @@ -55,8 +58,6 @@ eval { $FLAG_NOSIGNAL = MSG_NOSIGNAL; }; my %host_dead; # host -> unixtime marked dead until -my %cache_sock; # host -> socket -my @buck2sock; # bucket number -> $sock my $PROTO_TCP; @@ -78,6 +79,8 @@ $self->{'stat_callback'} = $args->{'stat_callback'} || undef; $self->{'readonly'} = $args->{'readonly'}; $self->{'parser_class'} = $args->{'parser_class'} || $parser_class; + $self->{'cache_sock'} = {}; + $self->{'sock_map'} = {}; # TODO: undocumented $self->{'connect_timeout'} = $args->{'connect_timeout'} || 0.25; @@ -101,7 +104,9 @@ $self->{'buckets'} = undef; $self->{'bucketcount'} = 0; $self->init_buckets; - @buck2sock = (); + $self->{'buck2sock'} = []; + $self->{'cache_sock'} = {}; + $self->{'sock_map'} = {}; $self->{'_single_sock'} = undef; if (@{$self->{'servers'}} == 1) { @@ -152,8 +157,9 @@ } sub forget_dead_hosts { + my ($self) = @_; + $self->{'buck2sock'} = []; %host_dead = (); - @buck2sock = (); } sub set_stat_callback { @@ -162,29 +168,27 @@ $self->{'stat_callback'} = $stat_callback; } -my %sock_map; # stringified-$sock -> "$ip:$port" - sub _dead_sock { - my ($sock, $ret, $dead_for) = @_; - if (my $ipport = $sock_map{$sock}) { + my ($self, $sock, $ret, $dead_for) = @_; + if (my $ipport = $self->{'sock_map'}{$sock}) { my $now = time(); $host_dead{$ipport} = $now + $dead_for if $dead_for; - delete $cache_sock{$ipport}; - delete $sock_map{$sock}; + delete $self->{'cache_sock'}{$ipport}; + delete $self->{'sock_map'}{$sock}; } - @buck2sock = (); + $self->{'buck2sock'} = []; return $ret; # 0 or undef, probably, depending on what caller wants } sub _close_sock { - my ($sock) = @_; - if (my $ipport = $sock_map{$sock}) { + my ($self, $sock) = @_; + if (my $ipport = $self->{'sock_map'}{$sock}) { close $sock; - delete $cache_sock{$ipport}; - delete $sock_map{$sock}; + delete $self->{'cache_sock'}{$ipport}; + delete $self->{'sock_map'}{$sock}; } - @buck2sock = (); + $self->{'buck2sock'} = []; } sub _connect_sock { # sock, sin, timeout @@ -229,7 +233,7 @@ sub sock_to_host { # (host) my Cache::Memcached $self = ref $_[0] ? shift : undef; my $host = $_[0]; - return $cache_sock{$host} if $cache_sock{$host}; + return $self->{'cache_sock'}{$host} if $self->{'cache_sock'}{$host}; my $now = time(); my ($ip, $port) = $host =~ /(.*):(\d+)$/; @@ -251,12 +255,12 @@ if ($HAVE_SOCKET6 && index($prefip, ':') != -1) { no strict 'subs'; # for PF_INET6 and AF_INET6, weirdly imported socket($sock, PF_INET6, SOCK_STREAM, $proto); - $sock_map{$sock} = $host; + $self->{'sock_map'}{$sock} = $host; $sin = Socket6::pack_sockaddr_in6($port, Socket6::inet_pton(AF_INET6, $prefip)); } else { socket($sock, PF_INET, SOCK_STREAM, $proto); - $sock_map{$sock} = $host; + $self->{'sock_map'}{$sock} = $host; $sin = Socket::sockaddr_in($port, Socket::inet_aton($prefip)); } @@ -275,12 +279,12 @@ if ($HAVE_SOCKET6 && index($ip, ':') != -1) { no strict 'subs'; # for PF_INET6 and AF_INET6, weirdly imported socket($sock, PF_INET6, SOCK_STREAM, $proto); - $sock_map{$sock} = $host; + $self->{'sock_map'}{$sock} = $host; $sin = Socket6::pack_sockaddr_in6($port, Socket6::inet_pton(AF_INET6, $ip)); } else { socket($sock, PF_INET, SOCK_STREAM, $proto); - $sock_map{$sock} = $host; + $self->{'sock_map'}{$sock} = $host; $sin = Socket::sockaddr_in($port, Socket::inet_aton($ip)); } @@ -288,18 +292,18 @@ unless (_connect_sock($sock, $sin, $timeout)) { my $cb = $self ? $self->{cb_connect_fail} : undef; $cb->($ip) if $cb; - return _dead_sock($sock, undef, 20 + int(rand(10))); + return $self->_dead_sock($sock, undef, 20 + int(rand(10))); } } } else { # it's a unix domain/local socket socket($sock, PF_UNIX, SOCK_STREAM, 0); - $sock_map{$sock} = $host; + $self->{'sock_map'}{$sock} = $host; $sin = Socket::sockaddr_un($host); my $timeout = $self ? $self->{connect_timeout} : 0.25; unless (_connect_sock($sock,$sin,$timeout)) { my $cb = $self ? $self->{cb_connect_fail} : undef; $cb->($host) if $cb; - return _dead_sock($sock, undef, 20 + int(rand(10))); + return $self->_dead_sock($sock, undef, 20 + int(rand(10))); } } @@ -308,7 +312,7 @@ $| = 1; select($old); - $cache_sock{$host} = $sock; + $self->{'cache_sock'}{$host} = $sock; return $sock; } @@ -347,12 +351,13 @@ } sub disconnect_all { + my ($self) = @_; my $sock; - foreach $sock (values %cache_sock) { + foreach $sock (values %{$self->{'cache_sock'}}) { close $sock; } - %cache_sock = (); - @buck2sock = (); + $self->{'cache_sock'} = {}; + $self->{'buck2sock'} = []; } # writes a line, then reads result. by default stops reading after a @@ -396,7 +401,7 @@ next if not defined $res and $!==EWOULDBLOCK; unless ($res > 0) { - _close_sock($sock); + $self->_close_sock($sock); return undef; } if ($res == length($line)) { # all sent @@ -411,7 +416,7 @@ next if !defined($res) and $!==EWOULDBLOCK; if ($res == 0) { # catches 0=conn closed or undef=error - _close_sock($sock); + $self->_close_sock($sock); return undef; } $offset += $res; @@ -420,7 +425,7 @@ } unless ($state == 2) { - _dead_sock($sock); # improperly finished + $self->_dead_sock($sock); # improperly finished return undef; } @@ -614,9 +619,9 @@ # and last; # but this variant doesn't crash: - $sock = $buck2sock[$bucket] || $self->sock_to_host($self->{buckets}[ $bucket ]); + $sock = $self->{'buck2sock'}[$bucket] || $self->sock_to_host($self->{buckets}[ $bucket ]); if ($sock) { - $buck2sock[$bucket] = $sock; + $self->{'buck2sock'}[$bucket] = $sock; last; } @@ -676,7 +681,7 @@ } close $sock; - _dead_sock($sock); + $self->_dead_sock($sock); }; # $finalize->($key, $flags) @@ -709,8 +714,8 @@ }; foreach (keys %$sock_keys) { - my $ipport = $sock_map{$_} or die "No map found matching for $_"; - my $sock = $cache_sock{$ipport} or die "No sock found for $ipport"; + my $ipport = $self->{'sock_map'}{$_} or die "No map found matching for $_"; + my $sock = $self->{'cache_sock'}{$ipport} or die "No sock found for $ipport"; print STDERR "processing socket $_\n" if $self->{'debug'} >= 2; $writing{$_} = $sock; if ($self->{namespace}) { @@ -891,7 +896,7 @@ return $$bref =~ /^(?:END|ERROR)\r?\n/m; }); unless ($lines) { - _dead_sock($sock); + $self->_dead_sock($sock); next HOST; } @@ -938,7 +943,7 @@ my $sock = $self->sock_to_host($host); my $ok = _write_and_read($self, $sock, "stats reset"); unless (defined $ok && $ok eq "RESET\r\n") { - _dead_sock($sock); + $self->_dead_sock($sock); } } return 1;
RT-Send-CC: bergner [...] cs.umu.se
I have been having issues with running multiple memcached clients in the same process, which may be related to this. If one of the servers is restarted, I get this error over and over: No map found matching for GLOB(0x130170d0) at /mnt/db/perl5/lib/perl5/Cache/Memcached.pm line 717. Which seems to reference sock_map. Related issue?
From: bergner [...] cs.umu.se
On Tue Sep 27 18:39:50 2011, REVMISCHA wrote: Show quoted text
> No map found matching for GLOB(0x130170d0) at > /mnt/db/perl5/lib/perl5/Cache/Memcached.pm line 717. > > Which seems to reference sock_map. Related issue?
May be related yes. Do you have a version of Cache/Memcached.pm that uses a global @buck2sock array? If so I think that could be the culprit in this case. The newest CPAN version uses an object local buck2sock property, which feels much safer. get_multi() will use $buck2sock[$bucket] as the key in the %sock_keys hash which is later used in _load_multi() to index into the sock_map hash. If buck2sock is global you would need to call disconnect_all() at various places to avoid having old useless data in the buck2sock array if multiple instances of Cache::Memcached are created and destroyed. Still, having sock_map and cache_sock also as local properties are a good thing if you ask me (it certainly worked much better for us in our setup), even though I don't think it is directly relevant to your issue. On the other hand I'm not sure I get all the ins and outs of the buckets and local caching going on in Cache::Memcached so take my advice with the usual healthy degree of scepticism. Kind regards, Marcus Bergner
I am using the latest version from CPAN, and on occasion when I restart my webapp process or restart memcacheds, it surfaces and refuses to go away until I restart everything. On Fri Sep 30 16:34:13 2011, bergner wrote: Show quoted text
> On Tue Sep 27 18:39:50 2011, REVMISCHA wrote:
> > No map found matching for GLOB(0x130170d0) at > > /mnt/db/perl5/lib/perl5/Cache/Memcached.pm line 717. > > > > Which seems to reference sock_map. Related issue?
> > May be related yes. Do you have a version of Cache/Memcached.pm that > uses a global @buck2sock array? If so I think that could be the culprit > in this case. The newest CPAN version uses an object local buck2sock > property, which feels much safer. > > get_multi() will use $buck2sock[$bucket] as the key in the %sock_keys > hash which is later used in _load_multi() to index into the sock_map > hash. If buck2sock is global you would need to call disconnect_all() at > various places to avoid having old useless data in the buck2sock array > if multiple instances of Cache::Memcached are created and destroyed. > > Still, having sock_map and cache_sock also as local properties are a > good thing if you ask me (it certainly worked much better for us in our > setup), even though I don't think it is directly relevant to your issue. > On the other hand I'm not sure I get all the ins and outs of the buckets > and local caching going on in Cache::Memcached so take my advice with > the usual healthy degree of scepticism. > > Kind regards, > > Marcus Bergner
From: terjekr [...] gmail.com
I have the same problem, getting the "No map found matching for GLOB" error message. I have also a setup with several memcached servers. This is a 1.29 issue, as downgrading to 1.28 solved the problem. Regards Terje Kristensen On Mon Oct 10 15:23:11 2011, REVMISCHA wrote: Show quoted text
> I am using the latest version from CPAN, and on occasion when I > restart my webapp process > or restart memcacheds, it surfaces and refuses to go away until I > restart everything. > > On Fri Sep 30 16:34:13 2011, bergner wrote:
> > On Tue Sep 27 18:39:50 2011, REVMISCHA wrote:
> > > No map found matching for GLOB(0x130170d0) at > > > /mnt/db/perl5/lib/perl5/Cache/Memcached.pm line 717. > > > > > > Which seems to reference sock_map. Related issue?
> > > > May be related yes. Do you have a version of Cache/Memcached.pm that > > uses a global @buck2sock array? If so I think that could be the
> culprit
> > in this case. The newest CPAN version uses an object local buck2sock > > property, which feels much safer. > > > > get_multi() will use $buck2sock[$bucket] as the key in the
> %sock_keys
> > hash which is later used in _load_multi() to index into the sock_map > > hash. If buck2sock is global you would need to call disconnect_all()
> at
> > various places to avoid having old useless data in the buck2sock
> array
> > if multiple instances of Cache::Memcached are created and destroyed. > > > > Still, having sock_map and cache_sock also as local properties are a > > good thing if you ask me (it certainly worked much better for us in
> our
> > setup), even though I don't think it is directly relevant to your
> issue.
> > On the other hand I'm not sure I get all the ins and outs of the
> buckets
> > and local caching going on in Cache::Memcached so take my advice
> with
> > the usual healthy degree of scepticism. > > > > Kind regards, > > > > Marcus Bergner
>
RT-Send-CC: revmischa [...] cpan.org, terjekr [...] gmail.com, bergner [...] cs.umu.se
Thank you for the submitted report and patch. This was a major issue in the client. I've opted to go a different route on the fix, as you can see here: https://github.com/memcached/perl-Cache- Memcached/commit/30392985c7e2f14924383016ddf4a95249d86333 I've opted to have a generational counter for the socket caches so they can be resynced as necessary. This allows us to keep the global socket cache and also cache the buckets to sockets mapping in each client object. This fix will be included in the next release, and that should be due out soon here.
patched!