File Coverage

blib/lib/Async/Simple/Task/ForkTmpFile.pm
Criterion Covered Total %
statement 63 67 94.0
branch 10 18 55.5
condition 5 15 33.3
subroutine 12 12 100.0
pod 4 5 80.0
total 94 117 80.3


line stmt bran cond sub pod time code
1             package Async::Simple::Task::ForkTmpFile;
2              
3             =head1 NAME
4              
5             Async::Simple::Task::ForkTmpFile - Forks child process.
6             It waits for "data" whic will be passed via "put", and executed "sub" with this "data" as an argument.
7             Result of execution will be returned to parent by "get".
8              
9             The behaviour of this class all the same as of Async::Simple::Task::Fork
10             except that it use files as interprocess transport instead of pipes.
11              
12             This class is recommended only as workaround for systems which don't support
13             bidirectional pipes.
14              
15             =head1 SYNOPSIS
16              
17             use Async::Simple::Task::ForkTmpFile;
18              
19             my $sub = sub { sleep 1; return $_[0]{x} + 1 }; # Accepts $data as @_ and returns any type you need
20              
21             my $task = Async::Simple::Task::ForkTmpFile->new( task => &$sub ); # Creates a child process, which waits for data and execute &$sub if data is passed
22              
23             my $data = { x => 123 }; # Any type you wish: scalar, array, hash
24              
25             $task->put( $data ); # Put a task data to sub in the child process
26              
27             # ...do something useful in parent while our data working ...
28              
29             my $result = $task->get; # result = undef because result is not ready yet
30             sleep 2; # or do something else....
31             my $result = $task->get; # result = 2
32              
33             $task->put( $data ); # Put another data to task sub and so on,....
34              
35             Result and data can be of any type and deep which can be translated via Data::Serializer->new( serializer => Storable ) # by default
36              
37             If your "sub" can return undef you should check $task->has_result, as a mark that result is ready.
38              
39              
40             =head1 DESCRIPTION
41              
42             Allows to initialize fork process.
43              
44             After that, executes "sub" for each "data" passed to child process.
45              
46              
47             =head1 METHODS
48              
49             =head2 C<new>
50              
51             Forks a process
52              
53             my $task = Async::Simple::Task::ForkTmpFile->new( task => &$sub, %other_optional_params );
54              
55             Params (all param except "task" are optional):
56              
57             task => coderef, function, called for each "data" passed to child process via $task->put( $data );
58              
59             timeout => timeout in seconds between child checkings for new data passed. default 0.01
60              
61             kill_on_exit => kill (1) or not (0) subprocess on object destroy (1 by default).
62              
63              
64             =head2 C<put>
65              
66             Puts data to task.
67              
68             $self->put( $data );
69              
70             =head2 C<get>
71              
72             Tries to read result from task.
73              
74             Returns undef if it is not ready.
75              
76             In case, your function can return undef, you shoud check $task->has_answer, as a mark of ready result.
77              
78             my $result = $self->get();
79              
80              
81             =head1 SUPPORT AND DOCUMENTATION
82              
83             After installing, you can find documentation for this module with the perldoc command.
84              
85             perldoc Async::Simple::Task::ForkTmpFile
86              
87             You can also look for information at:
88              
89             RT, CPAN's request tracker (report bugs here)
90             http://rt.cpan.org/NoAuth/Bugs.html?Dist=Async-Simple-Task-ForkTmpFile
91              
92             AnnoCPAN, Annotated CPAN documentation
93             http://annocpan.org/dist/Async-Simple-Task-ForkTmpFile
94              
95             CPAN Ratings
96             http://cpanratings.perl.org/d/Async-Simple-Task-ForkTmpFile
97              
98             Search CPAN
99             http://search.cpan.org/dist/Async-Simple-Task-ForkTmpFile/
100              
101              
102             =head1 AUTHOR
103              
104             ANTONC <antonc@cpan.org>
105              
106             =head1 LICENSE
107              
108             This program is free software; you can redistribute it and/or modify it
109             under the terms of the the Artistic License (2.0). You may obtain a
110             copy of the full license at:
111              
112             L<http://www.perlfoundation.org/artistic_license_2_0>
113              
114             =cut
115              
116              
117 2     2   131751 use Modern::Perl;
  2         14  
  2         16  
118 2     2   556 use Moose;
  2         422366  
  2         14  
119 2     2   13404 use namespace::autoclean;
  2         6595  
  2         14  
120 2     2   787 use Data::Serializer;
  2         2387  
  2         59  
121 2     2   14 use Time::HiRes qw/ alarm sleep /;
  2         4  
  2         18  
122 2     2   285 use File::Spec;
  2         4  
  2         1426  
123              
124             our $VERSION = '0.18';
125              
126             extends 'Async::Simple::Task::Fork';
127              
128             =head1 Attributes
129              
130             =head2 task
131              
132             task = sub {
133             my ( $data ) = @_; # source data for task
134             ... your task code ...
135             return( $result );
136             }
137              
138             =cut
139              
140              
141             =head2 answer
142              
143             Result of current task
144              
145             =cut
146              
147              
148             =head2 has_answer
149              
150             has_answer is true, if the task has been finished and result has been ready
151              
152             =cut
153              
154              
155             =head2 timeout
156              
157             timeout - positive numeric value = seconds between checking for result.
158              
159             inherited from Async::Simple::Task.
160              
161             =cut
162              
163              
164             =head2 kill_on_exit
165              
166             Kills process from parent in case of object desctuction
167              
168             =cut
169              
170              
171             =head2 new()
172              
173             my $task = Async::Simple::Task::ForkTmpFile->new( %all_optional_params );
174              
175              
176             Possible keys for %all_optional_params:
177              
178             task => coderef, function, called for each "data" passed to child process via $task->put( $data );
179              
180             timeout => timeout in seconds between child checkings for new data passed. default 0.01
181              
182             kill_on_exit => kill (1) or not (0) subprocess on object destroy (1 by default).
183              
184             =cut
185              
186             =head2 tmp_dir
187              
188             Path, that used for store tomporary files.
189             This path must be writable.
190             It can be empty; in this case ( File::Spec->tmpdir() || $ENV{TEMP} ) will be used
191              
192             By default:
193             During taint -T mode always writes files to current directory ( path = '' )
194             Windows outside taint -T mode writes files by default to C:\TEMP or C:\TMP
195             Unix outside taint -T mode writes files by default to /var/tmp/
196              
197             =cut
198              
199             has tmp_dir => (
200             is => 'ro',
201             isa => 'Str',
202             lazy => 1,
203             builder => 'make_tmp_dir',
204             );
205              
206             sub make_tmp_dir {
207 5     5 0 20 my ( $self ) = @_;
208              
209 5   50     296 my $tmp_dir = File::Spec->tmpdir() || '';
210              
211             # For WIN taint mode calculated path starts with '\'. Remove it and stay at current(empty) dir
212 5 50       31 $tmp_dir = '' if $tmp_dir =~ /^\\$/;
213              
214             # TEMP = C:\Users\XXXXXX~1\AppData\Local\Temp
215 5   0     17 $tmp_dir ||= $ENV{TEMP} // '';
      33        
216              
217             # Untaint ENV: fallback, if File::Spec->tmpdir failed
218 5         244 return [ $tmp_dir =~ /^(.+)$/ ]->[0];
219             };
220              
221              
222             =head2 BUILD
223              
224             internal. Some tricks here:)
225              
226             1. Master process called $task->new with fork() inside
227             2. After forking done we have two processes:
228             2.1. Master gets one side of reader/writer tmp file handlers and pid of child
229             2.2. Child - another side of tmp file handlers and extra logic with everlasting loop
230              
231             =cut
232              
233             # Writable pipe between parent and child.
234             # Each of them has pair of handlers, for duplex communication.
235             has writer => (
236             is => 'rw',
237             isa => 'Str',
238             );
239              
240              
241             # Readable pipe between parent and child.
242             # Each of them has pair of handlers, for duplex communication.
243             has reader => (
244             is => 'rw',
245             isa => 'Str',
246             );
247              
248              
249             =head2 fork_child
250              
251             Makes child process and returns pid of child process to parent or 0 to child process
252              
253             =cut
254              
255             sub fork_child {
256 6     6 1 20 my ( $self ) = @_;
257              
258 6         16 my( $randname, $parent_writer_fname, $parent_reader_fname );
259             $randname = sub {
260 12     12   173 my @x = ( 'a'..'z', 'A'..'Z', 0..9 );
261 12         43 join( "", map { $x[ int( rand @x - 0.01 ) ] } 1 .. 64 )
  768         2582  
262 6         53 };
263              
264 6         32 for ( 1..10 ) {
265 6         324 $parent_writer_fname = File::Spec->catfile( $self->tmp_dir, '_pw_tmp_' . $randname->() );
266 6         277 $parent_reader_fname = File::Spec->catfile( $self->tmp_dir, '_pr_tmp_' . $randname->() );
267              
268 6 50 33     569 next if -f $parent_writer_fname || -f $parent_reader_fname;
269 6         24 last;
270             };
271              
272 6 50 33     75 die 'Can`t obtain unique fname' if -f $parent_writer_fname || -f $parent_reader_fname;
273              
274 6   50     9232 my $pid = fork() // die "fork() failed: $!";
275              
276             # With taint mode we use current directory as temp,
277             # Otherwise - default writable temp directory from File::Spec->tmpdir();
278              
279             # child
280 6 50       62 unless ( $pid ) {
281 0         0 $self->writer( $parent_reader_fname );
282 0         0 $self->reader( $parent_writer_fname );
283              
284             # Important!
285             # Just after that we trap into BUILD
286             # with the infinitive loop for child process (pid=0)
287 0         0 return 0;
288             }
289              
290             # parent
291 6         1330 $self->writer( $parent_writer_fname );
292 6         319 $self->reader( $parent_reader_fname );
293              
294 6         497 return $pid;
295             };
296              
297              
298             =head2 get
299              
300             Reads from task, if something can be readed or returns undef after timeout.
301              
302             my $result = $task->get;
303              
304             Please note! If your function can return an undef value, then you shoud check
305              
306             $task->has_result.
307              
308             =cut
309              
310             sub get {
311 5     5 1 5505412 my ( $self ) = @_;
312              
313             # Try to read "marker" into data within timeout
314             # Each pack starts with an empty line and serialized string of useful data.
315 5 100       353 open( my $fh, '<', $self->reader ) or return;
316              
317 4         81 my $data = <$fh>;
318              
319 4 50       34 return unless defined $data;
320 4 50       43 return unless $data =~ /\n/;
321              
322 4         37 close( $fh );
323              
324             # In case, when reader still opened for writing
325             # We are not allowed to remove file, so we should wait
326 4         27 for ( 1..10 ) {
327 4 50       197 last if unlink $self->reader;
328 0         0 sleep $self->timeout;
329             }
330              
331             my $answer = $data
332 4 50       37 ? eval{ $self->serializer->deserialize( $data )->[0] }
  4         184  
333             : undef;
334              
335 4         1227 $self->answer( $answer );
336             };
337              
338              
339             =head2 put
340              
341             Writes task to task.
342              
343             $task->put( $data );
344              
345             =cut
346              
347             sub put {
348 4     4 1 96 my ( $self, $data ) = @_;
349              
350 4         219 unlink $self->writer;
351 4         247 $self->clear_answer;
352              
353 4         53 my $save_flush = $|;
354 4         20 $| = 1;
355              
356 4         206 open( my $fh, '>', $self->writer );
357              
358             # Each pack starts with an empty line and serialized string of useful data
359 4         12309 say $fh $self->serializer->serialize( [ $data ] );
360              
361 4         1454 close $fh;
362              
363 4         45 $| = $save_flush;
364              
365             };
366              
367              
368             =head2 get_serializer
369              
370             Internal. Returns an object that must have 2 methods: serialize and deserialize.
371             By default returns Data::Serializer with Storable as backend.
372              
373             $self->serializer->serialize( $task_data_ref );
374              
375             $result_ref = $self->serializer->deserialize();
376              
377             =cut
378              
379              
380             =head2 DEMOLISH
381              
382             Destroys object and probably should finish the child process.
383              
384             =cut
385              
386             sub DEMOLISH {
387 6     6 1 33 my ( $self ) = @_;
388              
389 6         232 unlink $self->writer;
390 6         223 unlink $self->reader;
391             }
392              
393              
394              
395             __PACKAGE__->meta->make_immutable;