Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

perl6 "An operation first awaited"

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;
}
like image 903
Norman Gaywood Avatar asked May 28 '18 21:05

Norman Gaywood


1 Answers

First, about the output from an exception thrown from an await. There are two interesting pieces of information when an asynchronous operation fails:

  • Where in the program we wanted the result of the operation
  • Where in the program the problem occurred that meant the operation could not be performed

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.

like image 159
Jonathan Worthington Avatar answered Sep 18 '22 12:09

Jonathan Worthington