How to share an object which contains a filehandle?
I don't have access to threaded Perl at the moment, so can't guarantee that this will work.
But a somewhat simplistic approach would be to use a level of abstraction and store a key/index into a global filehandle hash/array into the object, something similar to the following:
my @filehandles = (); # Stores all the filehandles ### CHANGEDmy $stdout; # Store the index into @filehandles, NOT filehandle. # Should really be renamed "$stdout_id" instead.sub stdout { my $self = shift; return $stdout if defined $stdout; $stdout = scalar(@filehandles); ### CHANGED my $stdout_fh = $self->dup_filehandle(\*STDOUT); ### CHANGED push @filehandles, $stdout_fh; ### CHANGED $self->autoflush($stdout_fh); ### CHANGED $self->autoflush(\*STDOUT); return $stdout;}sub safe_print { my $self = shift; my $fh_id = shift; ### CHANGED my $fh = $filehandles[$fh_id]; ### CHANGED local( $\, $, ) = ( undef, '' ); print $fh @_; }
I have a strong feeling that you would need to somehow also thread-safe the list of IDs, so perhaps an shared index counter would be needed instead of $stdout = scalar(@filehandles);
As an alternative to my other answer with global array, here's another approach from Perlmonks:
http://perlmonks.org/?node_id=395513
It works by actually storing fileno
(file descriptor) of the filehandle. Here's his sample code based on what BrowserUk posted:
my $stdout; # Store the fileno, NOT filehandle. # Should really be renamed "$stdout_fileno" instead.sub stdout { my $self = shift; return $stdout if defined $stdout; my $stdout_fh = $self->dup_filehandle(\*STDOUT); ### CHANGED $stdout = fileno $stdout_fh; ### CHANGED $self->autoflush($stdout_fh); ### CHANGED $self->autoflush(\*STDOUT); return $stdout;}sub safe_print { my $self = shift; my $fh_id = shift; ### CHANGED open(my $fh, ">>&=$fh_id") ### CHANGED || die "Error opening filehandle: $fh_id: $!\n"; ### CHANGED local( $\, $, ) = ( undef, '' ); print $fh @_; }
CAVEAT - as of 2004, this had a bug where you couldn't read from the shared filehandle from >1 thread. I am guessing that writing is OK. More specifics on how to do synchronised writes on a shared filehandle (from the same Monk): http://www.perlmonks.org/?node_id=807540
It just occurred to me there's two possible solutions:
- Put the filehandle outside the Streamer object.
- Put the Streamer object outside the Formatter.
@DVK's suggestions are all about doing 1.
But 2 is in some ways simpler than 1. Instead of holding the Streamer object itself, the Formatter can hold an identifier to the Streamer object. If the Streamer is implemented inside-out, that happens naturally!
Unfortunately, reference addresses change between threads, even shared ones. This can be solved with Hash::Util::FieldHash, but that's a 5.10 thing and I have to support 5.8. It's possible something could be put together using CLONE.