Sterling has too many projects Blogging about programming, microcontrollers & electronics, 3D printing, and whatever else...

Raku Schedulers

| 593 words | 3 minutes | raku advent-2019
People walking on a calendar

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:

  • ThreadPoolScheduler is the usual default $*SCHEDULER. When it is constructed, you can set the number of threads it is permitted to use simultaneously. It then manages a pool of threads and will schedule cued tasks on those threads. As tasks complete, freeing up threads, the next tasks will be scheduled to run. Tasks may run concurrently with this scheduler. When .cue returns, the tasks may not have started yet. The .cancel method of the returned object may be used to request cancelling the work of a given task.

  • CurrentThreadScheduler is an alternate scheduler. It basically just executes the task immediately and returns after the task is complete. The returned cancellation object has a .cancel method, but it is a no op as the work will always have completed by the time the scheduler returns.

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.

The content of this site is licensed under Attribution 4.0 International (CC BY 4.0).