12

I want to print the current time every second, and also want to sleep 10 seconds very 5 seconds:

react {
    whenever Supply.interval(1) {
        say DateTime.now.posix;
    }

    whenever Supply.interval(5) {
        sleep 10;
        say 'Sleep Done';
    }

    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}

the output is not what i wanted:

1542371045
Sleep Done
1542371055
Sleep Done
1542371065
Sleep Done
1542371075
Done.
...

what i want is this:

1542371045
1542371046
1542371047
1542371048
1542371049 
Sleep Done
1542371059
1542371060
1542371061  
1542371062 
1542371063         
Sleep Done
Done.

Don't know much about Promise, Supply... about Raku, is this possible?

chenyf
  • 5,048
  • 1
  • 12
  • 35
  • 1
    Should the time reports each second continue to be made during that 10 seconds break? Should the 10 seconds of sleep be triggered every 5 seconds, so it reports it's slept once per 5 seconds? – Jonathan Worthington Nov 16 '18 at 10:35
  • 1
    I think the basic misunderstanding is that only **one** `whenever` block will be executing at a time. This is actually underdocumented, so I will have a stab at that. – Elizabeth Mattijsen Nov 16 '18 at 11:33
  • @JonathanWorthington thanks for your attention. 1,Should the time reports each second continue to be made during that 10 seconds break? No, duration the 10 seconds break, I don't want it to continue report the time. 2,Should the 10 seconds of sleep be triggered every 5 seconds, so it reports it's slept once per 5 seconds? yes. – chenyf Nov 16 '18 at 12:23
  • if you don't want it to report while it's sleeping, it sleeps for 10 seconds each time, and you want it to report that it finished sleeping every 5 seconds, that means that at any time two 10-second sleeps are active, and the time should never be reported at all; am i misunderstanding? – timotimo Nov 16 '18 at 12:57
  • @timotimo yes, you are right. when sleep, it doesn't report time. – chenyf Nov 16 '18 at 14:27

5 Answers5

11

Depending on exactly what else was needed, I'd probably write it something like this:

react {
    sub sequence() {
        whenever Supply.interval(1).head(5) {
            say DateTime.now.posix;
            LAST whenever Promise.in(10) {
                say "Sleep done";
                sequence();
            }
        }
    }
    sequence();
}   

Which gives output like this:

1542395158
1542395159
1542395160
1542395161
1542395162
Sleep done
1542395172
1542395173
1542395174
1542395175
1542395176
Sleep done
1542395186
1542395187
1542395188
...

This will make absolutely sure you get 5 ticks out between the 10s pauses; doing it with two separate interval supplies - as in many solutions here - will not give any strict guarantees of that, and could miss a tick now and then. (One that doesn't is the cute one with rotor, which is a good bet if you don't need to actually print the "sleep done" thing). It's also free of state (variables) and conditions, which is rather nice.

While this looks like it might be recursive, since whenever is an asynchronous looping construct, it will not actually build up a call stack at all.

It's also fully built of asynchronous constructs, and so in Perl 6.d will not - if the react is triggered on the thread pool - ever block a real OS thread. So you could have thousands of these active. By contrast, sleep will block a real thread, which is what sleep traditionally would be expected to do, but isn't such a good fit if otherwise dealing with asynchronous constructs.

Jonathan Worthington
  • 29,104
  • 2
  • 97
  • 136
  • I might have tried a recursive implementation, except that I wanted to keep using the original `.interval(1)` Supply. – Brad Gilbert Nov 16 '18 at 19:40
  • thanks very much for your answer! Although concurrence is hard to understand, but Perl 6 make hard things possible. – chenyf Nov 17 '18 at 14:21
9

One mistake you are making is that you are assuming that supplies will lose values, or you are assuming they will stop generating values while the react is blocked. They won't.
They keep generating values.

You should also try to have the code in a whenever run for as short of a time as possible.
(Pretend it is a CPU interrupt handler.)
There may be some exceptions to this rule, particularly for supply blocks.

Using the structure that you provided, this is one way to achieve what you want:

 react {
    # Are we ignoring the interval(1) values?
    my Bool:D $ignore = False;

    # The sleeping status of interval(5).
    my Promise:D $sleep .= kept;

    whenever Supply.interval(1) {
        # Skip if it is supposed to be blocked.
        next if $ignore;

        say DateTime.now.posix;
    }

    # First one runs immediately, so skip it.
    whenever Supply.interval(5).skip {
        # Don't run while the “sleep” is pending.
        next unless $sleep.status; # Planned

        if $ignore {
            $ignore = False;
            say 'Sleep Done';
        } else {
            $ignore = True;
            # Must be less than the multiple of 5 we want
            # otherwise there may be a race condition.
            $sleep = Promise.in(9);
        }
    }

    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}

That isn't very clear.
How about we just use .rotor instead, to skip every third interval of 5?

react {
    my Bool:D $ignore = True;

    # Note that first one runs immediately. (no .skip)
    # We also want it to always be a few milliseconds before
    # the other Supply, so we put it first.
    # (Should have done that with the previous example as well.)
    whenever Supply.interval(5).rotor(1, 1 => 1) {
        $ignore = !$ignore;
    }

    whenever Supply.interval(1) {
        next if $ignore;

        say DateTime.now.posix;
    }

    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}

While we are at it, why not just use .rotor on the .interval(1) Supply?

react {
    whenever Supply.interval(1).rotor(1 xx 4, 1 => 10) {
        say DateTime.now.posix;
    }

    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}

Note that we can't just use 5 => 10 because that batches them up, and we want them to be run singly.


Note that .grep also works on Supplys, so we could have used that instead to check the $ignored value.

react {
    my Bool:D $ignore = True;

    whenever Supply.interval(5).rotor(1, 1 => 1) {
        $ignore = !$ignore;
    }

    whenever Supply.interval(1).grep({ !$ignore }) {
        say DateTime.now.posix;
    }

    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}
Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
5

Maybe this can work:

loop {
    react {
        whenever Supply.interval(1) {
            say DateTime.now.posix;
        }

        whenever Promise.in(5) {
            done;
        }

        whenever signal(SIGINT) {
            say "Done.";
            done;
        }
    }
    sleep 10;
}

The output is:

1542347961
1542347962
1542347963
1542347964
1542347965
1542347976 # <- 10s
1542347977
1542347978
1542347979
1542347980
1542347991 # <- 10s
jjmerelo
  • 22,578
  • 8
  • 40
  • 86
chenyf
  • 5,048
  • 1
  • 12
  • 35
4

The thing is the two Supplies are effectively running in different threads so don't interact with each other. Your sleep only puts the thread it's in to sleep (and then the fact it's a 5 second interval creates another sleep anyway).

To achieve the result you're looking for I went with this which uses the single 1 second interval and a couple of flags.

react { 
    whenever Supply.interval(1) { 
        state $slept = False;  
        state $count = 0;
        if $count >= 0 { 
            if $slept { 
                say "Sleep Done"; 
                $slept = False 
            } 
            say DateTime.now.posix; 
        } 
        $count++; 
        if ( $count == 5 ) { 
            $count = -9; 
            $slept = True
        }
    }
    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}

Note that we have to use state variables because the whenever block is effectively executed in it's own thread each second. The state variables allow us to keep track of the current situation.

If it was running on a smaller interval I would maybe think about using atomic ints instead of normal ones (in case the code was executed while it was still running) but that block should never take more than a second to execute so I don't think it's a problem.

Scimon Proctor
  • 4,558
  • 23
  • 22
2

Because only one whenever will be executing at any time, the sleep in there will be halting all handling of things to react to. The easiest way to achieve what you want, is to do the sleep as an asynchronous job by wrapping the code of that whenever into a start block.

react {
    whenever Supply.interval(1) {
        say DateTime.now.posix;
    }

    whenever Supply.interval(5) {
        start {
            sleep 10;
            say 'Sleep Done';
        }
    }

    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}

This gives the desired output, as far as I can see.

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
  • thanks for your attention. but the time reports are continuous. maybe it's hard to describe this question in my english.so i write a answer that report discontinuous times. – chenyf Nov 16 '18 at 12:46