Sterling has too many projects

Blogging about Raku programming, microcontrollers & electronics, 3D printing, and whatever else...

Raku Schedulers

A large number of concurrency-oriented coding in Raku depends on the use of a Scheduler. Many async operations depend on the default scheduler created by the VM at the start of runtime. You can access this via the dynamic variable named $*SCHEDULER.

The most important feature of a Scheduler is the .cue method. Calling that method with a code reference will schedule the work for execution. The type of scheduler will determine what exactly that means.

That said, this is a low-level interface and you probably shouldn’t be calling .cue in most code. The best practice is to rely on high-level tools like start blocks which due this and construct a Promise for you to monitor the work.

Every scheduler provides three methods:

  1. The .uncaught_handler is an accessor that returns a routine or can be set to a routine which is called whenever an exception is thrown by the scheduler and is not handled by the task code itself. If no handler is provided and a cued task throws an exception, the application will exit on that exception. If you use the high level concurrency tools, such as start blocks, the .uncaught_handler will never be used because they each provide their own exception handling.

  2. The .cue method is used to add a task to the schedule. The scheduler will perform that task as resources allow (depending on how the scheduler operates).

  3. The .loads method returns an integer indicating the current load on the scheduler. This is an indication of the size of the current job queue.

So, you could build a very simple scheduler like this:

class MyScheduler does Scheduler {
    method cue(&code, Instant :$at, :$in, :$every, :$times = 1; :&catch) {
        sleep $at - now if $at && $at > now;
        sleep $in if $in;


        for ^$times {
            code();
            CATCH { 
                default {
                    if &catch {
                        catch($_);
                    }
                    elsif self.uncaught_handler {
                        self.uncaught_handler.($_);
                    }
                    else {
                        .throw;
                    }
                }
            }
            sleep $every if $every;
        }

        class { method cancel() { } }
    }

    # We don't really queue jobs, so always return 0 for the load
    method loads(--> 0) { } 
}

This is somewhat similar to what the CurrentThreadScheduler does.

Rakudo has two schedulers built-in:

Many async methods, such as the .start method on Promise, take a named :scheduler argument where you can pass a custom scheduler. In general, you can stick to the default scheduler. Probably the most common adjustment to a scheduler would be to change the number of threads in the thread pool or switch to using the current thread scheduler under some circumstances. Chances are you will need to do neither of these. And if you need something exotic, it may be reasonable to define your own scheduler as well. Things to consider.

Cheers.