Skip to content

Commit eff526c

Browse files
committed
Updated scheduler guide.
1 parent 1c5156d commit eff526c

File tree

3 files changed

+110
-75
lines changed

3 files changed

+110
-75
lines changed

guides/event-loop/readme.md

Lines changed: 0 additions & 74 deletions
This file was deleted.

guides/links.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ getting-started:
22
order: 1
33
compatibility:
44
order: 4
5-
event-loop:
5+
scheduler:
66
order: 3
77
asynchronous-tasks:
88
order: 2

guides/scheduler/readme.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Scheduler
2+
3+
This guide gives an overview of how the scheduler is implemented.
4+
5+
## Overview
6+
7+
The {ruby Async::Scheduler} uses an event loop to execute tasks. When tasks are waiting on blocking operations like IO, the scheduler will use the operating system's native event system to wait for the operation to complete. This allows the scheduler to efficiently handle many tasks.
8+
9+
### Tasks
10+
11+
Tasks are the building blocks of concurrent programs. They are lightweight and can be scheduled by the event loop. Tasks can be nested, and the parent task is used to determine the current reactor. Tasks behave like promises, in the sense you can wait on them to complete, and they might fail with an exception.
12+
13+
~~~ ruby
14+
require 'async'
15+
16+
def sleepy(duration, task: Async::Task.current)
17+
task.async do |subtask|
18+
subtask.annotate "I'm going to sleep #{duration}s..."
19+
sleep duration
20+
puts "I'm done sleeping!"
21+
end
22+
end
23+
24+
def nested_sleepy(task: Async::Task.current)
25+
task.async do |subtask|
26+
subtask.annotate "Invoking sleepy 5 times..."
27+
5.times do |index|
28+
sleepy(index, task: subtask)
29+
end
30+
end
31+
end
32+
33+
Async do |task|
34+
task.annotate "Invoking nested_sleepy..."
35+
subtask = nested_sleepy
36+
37+
# Print out all running tasks in a tree:
38+
task.print_hierarchy($stderr)
39+
40+
# Kill the subtask
41+
subtask.stop
42+
end
43+
~~~
44+
45+
### Thread Safety
46+
47+
Most methods of the reactor and related tasks are not thread-safe, so you'd typically have [one reactor per thread or process](https://github.com/socketry/async-container).
48+
49+
### Embedding Schedulers
50+
51+
{ruby Async::Scheduler#run} will run until the reactor runs out of work to do. To run a single iteration of the reactor, use {ruby Async::Scheduler#run_once}.
52+
53+
~~~ ruby
54+
require 'async'
55+
56+
Console.logger.debug!
57+
reactor = Async::Scheduler.new
58+
59+
# Run the reactor for 1 second:
60+
reactor.async do |task|
61+
task.sleep 1
62+
puts "Finished!"
63+
end
64+
65+
while reactor.run_once
66+
# Round and round we go!
67+
end
68+
~~~
69+
70+
You can use this approach to embed the reactor in another event loop. For some integrations, you may want to specify the maximum time to wait to {ruby Async::Scheduler#run_once}.
71+
72+
### Stopping a Scheduler
73+
74+
{ruby Async::Scheduler#stop} will stop the current scheduler and all children tasks.
75+
76+
### Fiber Scheduler Integration
77+
78+
In order to integrate with native Ruby blocking operations, the {ruby Async::Scheduler} uses a {ruby Fiber::Scheduler} interface.
79+
80+
```ruby
81+
require 'async'
82+
83+
scheduler = Async::Scheduler.new
84+
Fiber.set_scheduler(scheduler)
85+
86+
Fiber.schedule do
87+
puts "Hello World!"
88+
end
89+
```
90+
91+
## Design
92+
93+
### Optimistic vs Pessimistic Scheduling
94+
95+
There are two main strategies for scheduling tasks: optimistic and pessimistic. An optimistic scheduler is usually greedy and will try to execute tasks as soon as they are scheduled using a direct transfer of control flow. A pessimistic scheduler will schedule tasks into the event loop ready list and will only execute them on the next iteration of the event loop.
96+
97+
```ruby
98+
Async do
99+
puts "Hello "
100+
101+
Async do
102+
puts "World"
103+
end
104+
105+
puts "!"
106+
end
107+
```
108+
109+
An optimstic scheduler will print "Hello World!", while a pessimistic scheduler will print "Hello !World". In practice you should not design your code to rely on the order of execution, but it's important to understand the difference. It is an unspecifed implementation detail of the scheduler.

0 commit comments

Comments
 (0)