Skip Menu |

This queue is for tickets about the Gearman-XS CPAN distribution.

Report information
The Basics
Id: 94079
Status: new
Priority: 0/
Queue: Gearman-XS

People
Owner: Nobody in particular
Requestors: nick [...] ccl4.org
Cc:
AdminCc:

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



Subject: Gearman::XS::Client leaks previous callbacks if they are re-set
The Gearman::XS::Client object doesn't handle references to client callbacks correctly. All is well if code only assigns each callback once. However, if code takes a client and assigns any callback a second time, the previous closure is leaked. The last callback assigned is correctly freed on object destruction, which means that there is no leak if callbacks are assigned at most once. I suspect that this has gone unnoticed because most users don't re-assign callbacks. I guess they either create a client, set it up with callbacks, and use it repeatedly, or they create a client, set up custom callbacks, use it once, then release the client. In both scenarios all memory is correctly freed. The code is here is somewhat quirky, in that it creates a client once that is re-used, but for each use sets up a new callback. Hence the callbacks leak. What makes it really noticeable is that the assigned callback closes over a large (temporary) data structure. Well, it's meant to be temporary... :-( Attached patch fixes this bug. I'm happy to assign copyright on the new test to Data Differential, hence the copyright notice I put in it. Thanks for the module. It's useful. Nicholas Clark
Subject: callbackleak.patch
commit a5c9e59440b3dfa0d5faeedf20fa1b8512a3f7ec Author: Nicholas Clark <nick@ccl4.org> Date: Fri Mar 21 11:36:43 2014 +0100 When setting a callback the second time, don't leak any previous callback. diff --git a/Client.xs b/Client.xs index 1bb893f..8475900 100644 --- a/Client.xs +++ b/Client.xs @@ -566,6 +566,8 @@ set_created_fn(self, fn) gearman_xs_client *self SV * fn CODE: + /* Release any previous callback. Safe because sv_free() handles NULL). */ + sv_free(self->created_fn); self->created_fn= newSVsv(fn); gearman_client_set_created_fn(self->client, _perl_task_created_fn); @@ -574,6 +576,7 @@ set_data_fn(self, fn) gearman_xs_client *self SV * fn CODE: + sv_free(self->data_fn); self->data_fn= newSVsv(fn); gearman_client_set_data_fn(self->client, _perl_task_data_fn); @@ -582,6 +585,7 @@ set_complete_fn(self, fn) gearman_xs_client *self SV * fn CODE: + sv_free(self->complete_fn); self->complete_fn= newSVsv(fn); gearman_client_set_complete_fn(self->client, _perl_task_complete_fn); @@ -590,6 +594,7 @@ set_fail_fn(self, fn) gearman_xs_client *self SV * fn CODE: + sv_free(self->fail_fn); self->fail_fn= newSVsv(fn); gearman_client_set_fail_fn(self->client, _perl_task_fail_fn); @@ -598,6 +603,7 @@ set_status_fn(self, fn) gearman_xs_client *self SV * fn CODE: + sv_free(self->status_fn); self->status_fn= newSVsv(fn); gearman_client_set_status_fn(self->client, _perl_task_status_fn); @@ -606,6 +612,7 @@ set_warning_fn(self, fn) gearman_xs_client *self SV * fn CODE: + sv_free(self->warning_fn); self->warning_fn= newSVsv(fn); gearman_client_set_warning_fn(self->client, _perl_task_warning_fn); diff --git a/MANIFEST b/MANIFEST index da39fcf..170b85c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -28,6 +28,7 @@ t/04-live.t t/05-live-worker.t t/06_live-worker-timeout.t t/80-memleak.t +t/81-callbackleak.t t/99-perlcritic.t t/lib/TestLib.pm t/perlcriticrc diff --git a/t/81-callbackleak.t b/t/81-callbackleak.t new file mode 100644 index 0000000..84b7d32 --- /dev/null +++ b/t/81-callbackleak.t @@ -0,0 +1,93 @@ +# Gearman Perl front end +# Copyright (C) 2014 Data Differential, http://datadifferential.com/ +# All rights reserved. +# +# This library is free software; you can redistribute it and/or modify +# it under the same terms as Perl itself, either Perl version 5.8.9 or, +# at your option, any later version of Perl 5 you may have available. + +use strict; +use warnings; + +use Test::More; +use Gearman::XS::Client; + +our $count = 0; +{ + package Counter; + + sub new { + ++$count; + bless [], shift; + } + + sub DESTROY { + --$count; + } +} + +sub gen_callback { + my $counter = Counter->new(); + return sub { + $counter; + } +} + +# Simple test: +my $client = Gearman::XS::Client->new(); +isa_ok($client, 'Gearman::XS::Client'); + +is($count, 0, 'At the start we have no objects'); + +$client->set_created_fn(gen_callback()); + +is($count, 1, 'One callback'); + +undef $client; + +is($count, 0, 'No callbacks'); + +# Repeat test: +$client = Gearman::XS::Client->new(); + +$client->set_created_fn(gen_callback()); + +is($count, 1, 'One callback'); + +$client->set_created_fn(gen_callback()); + +is($count, 1, 'Still one callback'); + +$client->set_created_fn(gen_callback()); + +is($count, 1, 'Still one callback'); + +undef $client; + +is($count, 0, 'No callbacks'); + +# All the callbacks: +my @callbacks = qw(created data complete fail status warning); +my %setters = map {$_ , "set_${_}_fn"} @callbacks; +my $all = scalar @callbacks; +$client = Gearman::XS::Client->new(); + +foreach my $setter (values %setters) { + $client->$setter(gen_callback()); +} + +is($count, $all, "All $all callbacks"); + +while (my ($callback, $setter) = each %setters) { + $client->$setter(gen_callback()); + is($count, $all, "Still $all callbacks after setting $callback again"); + + $client->$setter(gen_callback()); + is($count, $all, "Still $all callbacks after setting $callback again"); +} + +undef $client; + +is($count, 0, 'No callbacks'); + +done_testing();