This program creates a thread to read a directory with dir() and place the files on a channel. $N worker threads read that channel and "process" (prints) the files.
However I'm getting this "An operation first awaited:" error.
I've read the traps page about this error several times, but still can't make sense of it. Can some explain what's going on here?
The directory contents:
$ ls a b c traverse-dir0.p6
Running the program:
$ ./traverse-dir0.p6 traverse-dir0.p6 a b c An operation first awaited: in sub MAIN at ./traverse-dir0.p6 line 24 in block at ./traverse-dir0.p6 line 5 Died with the exception: Cannot find method 'path': no method cache and no .^find_method in block at ./traverse-dir0.p6 line 16
The program traverse-dir0.p6:
#!/usr/bin/env perl6 # There is a thread to populate $dir-channel by reading filenames in a directory with dir() # and $N worker threads to read the filenames from the $dir-channel. sub MAIN( Str $dir = ".", Int :$N = 4 ) { my $dir-channel = Channel.new(); my $dir-read = start { $dir-channel.send( $_ ) for dir $dir; $dir-channel.close; } my @workers = (^$N).map: { start { while my $file = $dir-channel.receive() { say $file.path; } CATCH { when X::Channel::ReceiveOnClosed { .resume } } } } await $dir-read, @workers; }
First, about the output from an exception thrown from an await
. There are two interesting pieces of information when an asynchronous operation fails:
The first piece of information is indicating the location of the await
, and the stack trace relates to that. The second part is about why the exception was re-thrown by the await
, and indicates the problem that needs to be fixed.
The problem in this case is that the path
method is called on an object that doesn't have one. This is thanks to the .resume
, which makes no sense. The exception is thrown to say that it was not possible to receive a value from the channel. Resuming it just means the loop body runs with an undefined value in $file
, which lacks a path
method and leads to the error. (As an aside: it's very, very rare that .resume
is the right answer.)
The smallest fix to the code is to replace the .resume
with a last
, which terminates the iteration when the channel is closed:
my @workers = (^$N).map: {
start {
while my $file = $dir-channel.receive() {
say $file.path;
CATCH {
when X::Channel::ReceiveOnClosed { last }
}
}
}
}
However, it's far simpler to coerce the Channel
into an iterable Seq
. This automatically handles terminating the iteration when the Channel
is closed, so there's no messing around with exceptions:
my @workers = (^$N).map: {
start {
for $dir-channel.Seq -> $file {
say $file.path;
}
}
}
And since start
is a statement prefix, that further shortens to:
my @workers = (^$N).map: {
start for $dir-channel.Seq -> $file {
say $file.path;
}
}
I appreciate this was probably a simplified version of a more interesting problem, or perhaps done to explore various Perl 6 concurrency concepts, but the whole lot could be replaced with:
sub MAIN( Str $dir = ".", Int :$N = 4 ) {
race for dir($dir).race(batch => 1, degree => $N) -> $file {
say $file.path;
}
}
Which has the same semantics, but saves starting and managing the workers, while still controlling the number of workers and making sure the files are distributed in the same way among the workers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With