Skip to content

Commit

Permalink
Added MaxBuffer support
Browse files Browse the repository at this point in the history
Created a constructor that takes named parameters.
Above constructor will also accept the old syntax
Documentation for above
Test cases for above
Added POE::Fitler::Reference::FIRST_UNUSED
  • Loading branch information
Philip Gwyn committed Apr 9, 2014
1 parent a974215 commit b49ccba
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 43 deletions.
164 changes: 123 additions & 41 deletions lib/POE/Filter/Reference.pm
Expand Up @@ -17,6 +17,19 @@ sub FREEZE () { 1 }
sub THAW () { 2 }
sub COMPRESS () { 3 }
sub NO_FATALS () { 4 }
sub MAX_BUFFER () { 5 }
sub BAD_BUFFER () { 6 }
sub FIRST_UNUSED () { 7 }

use base 'Exporter';
our @EXPORT_OK = qw( FIRST_UNUSED );

my %KNOWN_PARAMS = (
Compression => 1,
Serializer => 1,
NoFatals => 1,
MaxBuffer => 1
);

#------------------------------------------------------------------------------
# Try to require one of the default freeze/thaw packages.
Expand Down Expand Up @@ -78,10 +91,42 @@ sub _get_methods {

#------------------------------------------------------------------------------

sub new {
my($type, $freezer, $compression, $no_fatals) = @_;
sub new
{
my $type = shift;

# Convert from old style to new style
# $l == 1
# ->new( undef ) => (Serializer => undef)
# ->new( $class ) => (Serializer => class)
# not defined $_[0]
# ->new( undef, 1 ) => (Serializer => undef, Compression => 1)
# ->new( undef, undef, 1 ) => (Serializer => undef, Compression => undef, NoFatals =>1)
# $l == 3
# ->new( $class, 1, 1 ) => (Serializer => undef, Compression => 1, NoFatals =>1)
# ($l <= 3 and not $KNOWN_PARAMS{$_[0]})
# ->new( $class, 1 )
my %params;
my $l = scalar @_;
if( $l == 1 or $l == 3 or not defined $_[0] or
( $l<=3 and not $KNOWN_PARAMS{$_[0]}) ) {
if( 'HASH' eq ref $_[0] ) { # do we
%params = %{ $_[0] };
}
else {
%params = ( Serializer => $_[0],
Compression => $_[1],
NoFatals => $_[2]
);
}
}
else {
croak "$type requires an even number of parameters" if @_ and @_ & 1;
%params = @_;
}

my($freeze, $thaw);
my $freezer = $params{Serializer};
unless (defined $freezer) {
# Okay, load the default one!
$freezer = $DEF_FREEZER;
Expand Down Expand Up @@ -131,8 +176,11 @@ sub new {
# wants?
return unless $freeze and $thaw;

# Maximum buffer
my $max_buffer = $type->__param_max( MaxBuffer => 512*1024*1024, \%params );

# Compression
$compression ||= 0;
my $compression = $params{Compression}||0;
if ($compression) {
my $zlib_status = _include_zlib();
if ($zlib_status ne '') {
Expand All @@ -142,12 +190,21 @@ sub new {
}
}

# No fatals
my $no_fatals = $params{NoFatals}||0;

delete @params{ keys %KNOWN_PARAMS };
carp("$type ignores unknown parameters: ", join(', ', sort keys %params))
if scalar keys %params;

my $self = bless [
'', # BUFFER
$freeze, # FREEZE
$thaw, # THAW
$compression, # COMPRESS
$no_fatals || 0, # NO_FATALS
$no_fatals, # NO_FATALS
$max_buffer, # MAX_BUFFER
'' # BAD_BUFFER
], $type;
$self;
}
Expand Down Expand Up @@ -176,6 +233,10 @@ sub get {
sub get_one_start {
my ($self, $stream) = @_;
$self->[BUFFER] .= join('', @$stream);
if( $self->[MAX_BUFFER] < length( $self->[BUFFER] ) ) {
$self->[BAD_BUFFER] = "Framing buffer exceeds the limit";
die $self->[BAD_BUFFER] unless $self->[NO_FATALS];
}
}

sub get_one {
Expand All @@ -184,6 +245,12 @@ sub get_one {
# Need to check lengths in octets, not characters.
BEGIN { eval { require bytes } and bytes->import; }

if( $self->[BAD_BUFFER] ) {
my $err = $self->[BAD_BUFFER];
$self->[BAD_BUFFER] = '';
return [ $err ];
}

if (
$self->[BUFFER] =~ /^(\d+)\0/ and
length($self->[BUFFER]) >= $1 + length($1) + 1
Expand Down Expand Up @@ -287,24 +354,55 @@ different serializer may be specified at construction time.
=head1 PUBLIC FILTER METHODS
POE::Filter::Reference deviates from the standard POE::Filter API in
the following ways.
=head2 new [SERIALIZER [, COMPRESSION [, NO_FATALS]]]
=head2 new
new() creates and initializes a POE::Filter::Reference object. It
will use Storable as its default SERIALIZER if none other is
specified.
accepts a list of named parameters.
=head3 Serializer
Any class that supports nfreeze() (or freeze()) and thaw() may be used
as a Serializer. If a Serializer implements both nfreeze() and
freeze(), then the "network" (nfreeze) version will be used.
Serializer may be a class name:
# Use Storable explicitly, specified by package name.
my $filter = POE::Filter::Reference->newer( Serializer=>"Storable" );
# Use YAML instead. Compress its output, as it may be verbose.
my $filter = POE::Filter::Reference->new("YAML", 1);
Serializer may also be an object:
If COMPRESSION is true, Compress::Zlib will be called upon to reduce
# Use an object.
my $serializer = Data::Serializer::Something->new();
my $filter = POE::Filter::Reference->newer( Serializer => $serializer );
If Serializer is omitted or undef, the Reference filter will try to
use Storable, FreezeThaw, and YAML in that order.
POE::Filter::Reference will die if it cannot find one of these
serializers, but this rarely happens now that Storable and YAML are
bundled with Perl.
=head3 Compression
If Compression is true, Compress::Zlib will be called upon to reduce
the size of serialized data. It will also decompress the incoming
stream data.
If NO_FATALS is true, messages will be thawed inside a block eval. By
=head3 MaxBuffer
C<MaxBuffer> sets the maximum amount of data that the filter will hold onto
while trying to build a new reference. Defaults to 512 MB.
=head3 NoFatals
If NoFatals is true, messages will be thawed inside a block eval. By
default, however, thaw() is allowed to die normally. If an error
occurs while NO_FATALS is in effect, POE::Filter::Reference will
occurs while NoFatals is in effect, POE::Filter::Reference will
return a string containing the contents of $@ at the time the eval
failed. So when using NO_FATALS, it's important to check whether
failed. So when using NoFatals, it's important to check whether
input is really a reference:
sub got_reference {
Expand All @@ -317,40 +415,24 @@ input is really a reference:
}
}
Any class that supports nfreeze() (or freeze()) and thaw() may be used
as a SERIALIZER. If a SERIALIZER implements both nfreeze() and
freeze(), then the "network" version will be used.
SERIALIZER may be a class name:
new() will try to load any classes it needs for L</Compression> or L</Serializer>.
# Use Storable explicitly, specified by package name.
my $filter = POE::Filter::Reference->new("Storable");
# Use YAML instead. Compress its output, as it may be verbose.
my $filter = POE::Filter::Reference->new("YAML", 1);
SERIALIZER may also be an object:
# Use an object.
my $serializer = Data::Serializer::Something->new();
my $filter = POE::Filter::Reference->new($serializer);
=head2 new [SERIALIZER [, COMPRESSION [, NO_FATALS]]]
If SERIALIZER is omitted or undef, the Reference filter will try to
use Storable, FreezeThaw, and YAML in that order.
POE::Filter::Reference will die if it cannot find one of these
serializers, but this rarely happens now that Storable and YAML are
bundled with Perl.
This is the old constructor synatx. It does not conform to the normal
POE::Filter constructor parameter syntax. Please use the new syntax
instead.
# A choose-your-own-serializer adventure!
# We'll still deal with compressed data, however.
my $filter = POE::Filter::Reference->new(undef, 1);
Calling C<new> like this is equivalent to
POE::Filter::Reference will try to compress frozen strings and
uncompress them before thawing if COMPRESSION is true. It uses
Compress::Zlib for this. POE::Filter::Reference doesn't need
Compress::Zlib if COMPRESSION is false.
POE::Filter::Reference->new( Serializer => SERIALIZER,
Compression => COMPRESSION,
NoFatals => NO_FATALS );
new() will try to load any classes it needs.
Please note that if you have a custom serializer class called C<Serializer>
you will have to update your code to the new syntax.
=head1 SERIALIZER API
Expand Down
53 changes: 51 additions & 2 deletions t/10_units/05_filters/07_reference.t
Expand Up @@ -34,7 +34,7 @@ BEGIN {
}

BEGIN {
plan tests => 11 + $COUNT_FILTER_INTERFACE;
plan tests => 26 + $COUNT_FILTER_INTERFACE;
}

test_filter_interface('POE::Filter::Reference');
Expand Down Expand Up @@ -73,7 +73,7 @@ sub test_freeze_and_thaw {
eval {
# Hide warnings.
local $SIG{__WARN__} = sub { };
$filter = POE::Filter::Reference->new( $freezer, $compression );
$filter = POE::Filter::Reference->new( Serializer=>$freezer, Compession=>$compression );
die "filter not created with freezer=$freezer" unless $filter;
};

Expand Down Expand Up @@ -138,4 +138,53 @@ die if $@;
test_freeze_and_thaw('MyOtherFreezer', undef);
test_freeze_and_thaw('MyOtherFreezer', 9 );

# Test old constructor syntax
{
my $F1 = POE::Filter::Reference->new( 'Storable' );
isa_ok( $F1, "POE::Filter::Reference" );
my $F2 = POE::Filter::Reference->new( 'Storable', 1 );
isa_ok( $F2, "POE::Filter::Reference" );

my $d1 = $F1->put( [ ['honk honk honk honk'] ] )->[0];
my $d2 = $F2->put( [ ['honk honk honk honk'] ] )->[0];
isnt( $d1, $d2, "Different outputs with Compression on" );
ok( length( $d1 ) > length( $d2 ), "Compressed is (obviously) shorter" );

$F1 = POE::Filter::Reference->new( undef );
isa_ok( $F1, "POE::Filter::Reference" );
$F2 = POE::Filter::Reference->new( undef, undef, undef );
isa_ok( $F2, "POE::Filter::Reference" );

$d1 = $F1->put( [ ['honk honk honk honk'] ] )->[0];
$d2 = $F2->put( [ ['honk honk honk honk'] ] )->[0];
is( $d1, $d2, "Outputs are the same" );

$F1 = POE::Filter::Reference->new( undef, undef );
isa_ok( $F1, "POE::Filter::Reference" );
$F2 = POE::Filter::Reference->new( undef, undef, 1 );
isa_ok( $F2, "POE::Filter::Reference" );

$d1 = $F1->put( [ ['honk honk honk honk'] ] )->[0];
$d2 = $F2->put( [ ['honk honk honk honk'] ] )->[0];
is( $d1, $d2, "Outputs are the same" );
}

# Test NoFatal
{
my $F1 = POE::Filter::Reference->new( NoFatals => 1 );
isa_ok( $F1, "POE::Filter::Reference" );

my $raw = "12\x00123456789012";
my $d = eval { $F1->get( [ $raw ] )->[0] };
ok( !$@, "Obvious error didn't explode" );
ok( !ref $d, "Instead it returned an error string" );


$F1 = POE::Filter::Reference->new( NoFatals => 1, MaxBuffer => 10 );
$d = eval { $F1->get( [ $raw ] )->[0] };
ok( !$@, "Buffer error didn't explode" );
like( $d, qr/buffer exceeds/, "Instead it returned an error string" );

}

exit;

0 comments on commit b49ccba

Please sign in to comment.