Perl: pipe a serialized hash to a forked process

675 Views Asked by At

I don't know whats wrong with my code. I'm trying to serialize a hash inside the parent and pipe it to the fork, where it should be deserialized.

#!/usr/bin/perl
use strict;
use warnings;
use Storable qw(freeze thaw);
use IO::Pipe;

my $pipe_to_fork = IO::Pipe->new();

my $fork = fork;
if ($fork == 0) { # actual fork scope
  $pipe_to_fork->reader();
  my $hash_serialized = <$pipe_to_fork>; # wait and retrieve the serialized hash from parent
  chomp $hash_serialized;
  my %hash_rebuild = %{thaw($hash_serialized)}; # deserialize the retrieved serialized hash
  exit;
}

my %hash = ('key1' => "val1", 'key2' => "val2");

$pipe_to_fork->writer();
$pipe_to_fork->autoflush(1);

my $hash_serialized = freeze(\%hash); # serialize the hash
print $pipe_to_fork $hash_serialized."\n";
sleep 5;

exit;

... produces the following error:

Can't use an undefined value as a HASH reference at ./fork_serialize.pl line 14, <GEN0> line 1.

Is there something wrong with the pipe? It seems that thaw doesn't deserialize the retrieved scalar value. Maybe the retrieved scalar value isn't correct.

I've tried to do some semilar things without forking or piping, and its working:

#!/usr/bin/perl
use strict;
use warnings;
use Storable qw(freeze thaw);

my %hash = ('key1' => "value1", 'key2' => "value2");
my $hash_serialized = freeze(\%hash);
my %hash_rebuild = %{thaw($hash_serialized)};

print $hash_rebuild{'key2'}."\n";

Not much of a logical difference, he? It would be nice if anyone can explain me more of this behavior.

2

There are 2 best solutions below

3
On BEST ANSWER

The trouble is that you're trying to use a line-based protocol (appending the "\n" on the write side, using <> and chomp on the read side) but your data is not text and can contain its own "\n"s so your reader stops at the first one and chops it off.

You need to use some other method of signaling the end of the serialized data, for example you could close the pipe on the write end and keep going until EOF on the read end. In fact, Storable has a pair of functions designed for this exact situation: store_fd and fd_retrieve. They'll do the transfer in a way that detects the end without an EOF, so you can keep the pipe open for more transfers.

Here's a version of the guts of your program using the fd functions from Storable:

if ($fork == 0) { # actual fork scope
  $pipe_to_fork->reader();
  my %hash_rebuild = %{fd_retrieve($pipe_to_fork)}; # deserialize the retrieved serialized hash
  use Data::Dumper;$Data::Dumper::Useqq=1;print Dumper \%hash_rebuild;
  exit;
}

my %hash = ('key1' => "val1", 'key2' => "val2");

$pipe_to_fork->writer();
$pipe_to_fork->autoflush(1);

store_fd(\%hash, $pipe_to_fork);
1
On

The problem is that you're assuming the hash is freezed into one single line. But that's not always the case, since $hash_serialized could be a string containing multiple \ns.

So instead of reading only one line in child side, you should read until the EOF and concatenate all the lines.

#!/usr/bin/perl
use strict;
use warnings;
use Storable qw(freeze thaw);
use IO::Pipe;

my $pipe_to_fork = IO::Pipe->new();

my $fork = fork;
if ($fork == 0) { # actual fork scope
  $pipe_to_fork->reader();
  my $hash_serialized;
  $hash_serialized .= $_ while (<$pipe_to_fork>);
  my %hash_rebuild = %{thaw($hash_serialized)}; # deserialize the retrieved serialized hash
  print $hash_rebuild{key1};
  exit;
}

my %hash = ('key1' => "val1", 'key2' => "val2");

$pipe_to_fork->writer();
$pipe_to_fork->autoflush(1);

my $hash_serialized = freeze(\%hash); # serialize the hash
print $pipe_to_fork $hash_serialized;

exit;

Output: val1