polyphony 0.34 → 0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +7 -7
- data/docs/api-reference.md +1 -1
- data/docs/api-reference/fiber.md +18 -0
- data/docs/api-reference/gyro-async.md +57 -0
- data/docs/api-reference/gyro-child.md +29 -0
- data/docs/api-reference/gyro-queue.md +44 -0
- data/docs/api-reference/gyro-timer.md +51 -0
- data/docs/api-reference/gyro.md +25 -0
- data/docs/index.md +8 -6
- data/docs/main-concepts/fiber-scheduling.md +55 -72
- data/examples/core/xx-timer-gc.rb +17 -0
- data/ext/gyro/async.c +48 -58
- data/ext/gyro/child.c +48 -38
- data/ext/gyro/fiber.c +113 -0
- data/ext/gyro/gyro.c +12 -106
- data/ext/gyro/gyro.h +54 -50
- data/ext/gyro/gyro_ext.c +2 -0
- data/ext/gyro/io.c +70 -43
- data/ext/gyro/queue.c +5 -5
- data/ext/gyro/selector.c +33 -11
- data/ext/gyro/signal.c +44 -34
- data/ext/gyro/socket.c +6 -7
- data/ext/gyro/thread.c +1 -1
- data/ext/gyro/timer.c +42 -62
- data/lib/polyphony/adapters/irb.rb +1 -1
- data/lib/polyphony/core/thread_pool.rb +3 -3
- data/lib/polyphony/extensions/fiber.rb +1 -1
- data/lib/polyphony/extensions/thread.rb +2 -2
- data/lib/polyphony/version.rb +1 -1
- data/test/test_async.rb +2 -2
- data/test/test_fiber.rb +4 -4
- data/test/test_global_api.rb +1 -1
- data/test/test_thread_pool.rb +1 -1
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1baba43b46fa48ea13ec33667390b5b6dafb8323d250f2df5e5ee6930e939680
|
4
|
+
data.tar.gz: 465c329841226252b1fe7fc3d48904b912323370083b4925d89531e45a4bd34d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9b863d0231a5ccea65aa015d5a1d3432d0b513be1a60b2d20040bd06ed9d253c6be8391176ceba472300beac8059e27c334aca3bb6bafd51c3dabfd1e105301
|
7
|
+
data.tar.gz: 29eba3a894084f20d21bab00840cef76a5094f73aee4521947685883dc717811a274ed723deb72433cc0f358715549d79215286aad0ba62d14ee97308ee1415b
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
|
+
## 0.36 2020-03-31
|
2
|
+
|
3
|
+
* More docs
|
4
|
+
* More C code refactoring
|
5
|
+
* Fix freeing for active child, signal watchers
|
6
|
+
|
7
|
+
## 0.35 2020-03-29
|
8
|
+
|
9
|
+
* Rename `Fiber#cancel!` to `Fiber#cancel`
|
10
|
+
* Rename `Gyro::Async#signal!` to `Gyro::Async#signal`
|
11
|
+
* Use `Fiber#auto_async` in thread pool, thread extension
|
12
|
+
* Implement `Fiber#auto_io` for reusing IO watcher instances
|
13
|
+
* Refactor C code
|
14
|
+
|
1
15
|
## 0.34 2020-03-25
|
2
16
|
|
17
|
+
* Add `Fiber#auto_async` mainly for use in places like `Gyro::Queue#shift`
|
18
|
+
* Refactor C extension
|
19
|
+
* Improved GC'ing for watchers
|
3
20
|
* Implement process supervisor (`Polyphony::ProcessSupervisor`)
|
4
21
|
* Improve fiber supervision
|
5
22
|
* Fix forking behaviour
|
data/Gemfile.lock
CHANGED
data/TODO.md
CHANGED
@@ -32,7 +32,7 @@
|
|
32
32
|
Fiber.current.add_child_fiber(t.main_fiber)
|
33
33
|
```
|
34
34
|
|
35
|
-
## 0.
|
35
|
+
## 0.36 Some more API work, more docs
|
36
36
|
|
37
37
|
- Debugging
|
38
38
|
- Eat your own dogfood: need a good tool to check what's going on when some
|
@@ -152,7 +152,7 @@
|
|
152
152
|
- Check why first call to `#sleep` returns too early in tests. Check the
|
153
153
|
sleep behaviour in a spawned thread.
|
154
154
|
|
155
|
-
## 0.
|
155
|
+
## 0.37 Sinatra / Sidekiq
|
156
156
|
|
157
157
|
- sintra app with database access (postgresql)
|
158
158
|
|
@@ -162,13 +162,13 @@
|
|
162
162
|
- test performance
|
163
163
|
- proceed from there
|
164
164
|
|
165
|
-
## 0.
|
165
|
+
## 0.38 Testing && Docs
|
166
166
|
|
167
167
|
- Pull out redis/postgres code, put into new `polyphony-xxx` gems
|
168
168
|
|
169
|
-
## 0.
|
169
|
+
## 0.39 Integration
|
170
170
|
|
171
|
-
## 0.
|
171
|
+
## 0.40 Real IO#gets and IO#read
|
172
172
|
|
173
173
|
- More tests
|
174
174
|
- Implement some basic stuff missing:
|
@@ -178,11 +178,11 @@
|
|
178
178
|
- `IO.foreach`
|
179
179
|
- `Process.waitpid`
|
180
180
|
|
181
|
-
## 0.
|
181
|
+
## 0.41 Rails
|
182
182
|
|
183
183
|
- Rails?
|
184
184
|
|
185
|
-
## 0.
|
185
|
+
## 0.42 DNS
|
186
186
|
|
187
187
|
### DNS client
|
188
188
|
|
data/docs/api-reference.md
CHANGED
data/docs/api-reference/fiber.md
CHANGED
@@ -71,6 +71,24 @@ f << 2
|
|
71
71
|
result = receive #=> 20
|
72
72
|
```
|
73
73
|
|
74
|
+
### #auto_async → async
|
75
|
+
|
76
|
+
Returns a reusable `Gyro::Async` watcher instance associated with the fiber.
|
77
|
+
This method provides a way to minimize watcher allocation. Instead of allocating
|
78
|
+
a new async watcher every time one is needed, the same watcher associated with
|
79
|
+
the fiber is reused.
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
def work(async)
|
83
|
+
do_something
|
84
|
+
async.signal
|
85
|
+
end
|
86
|
+
|
87
|
+
async = Fiber.current.auto_async
|
88
|
+
spin { work(async) }
|
89
|
+
async.await
|
90
|
+
```
|
91
|
+
|
74
92
|
### #await → object<br>#join → object
|
75
93
|
|
76
94
|
Awaits the termination of the fiber. If the fiber terminates with an uncaught
|
@@ -0,0 +1,57 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: Gyro::Async
|
4
|
+
parent: API Reference
|
5
|
+
permalink: /api-reference/gyro-async/
|
6
|
+
---
|
7
|
+
# Gyro::Async
|
8
|
+
|
9
|
+
`Gyro::Async` encapsulates a libev [async
|
10
|
+
watcher](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#code_ev_async_code_how_to_wake_up_an),
|
11
|
+
allowing thread-safe synchronisation and signalling. `Gyro::Async` watchers are
|
12
|
+
used both directly and indirectly in Polyphony to implement
|
13
|
+
[queues](../gyro-queue/), await fibers and threads, and auxiliary features such
|
14
|
+
as [thread pools](../polyphony-threadpool/).
|
15
|
+
|
16
|
+
A `Gyro::Async` watcher instance is shared across two or more fibers (across one
|
17
|
+
or more threads), where one fiber waits to be signalled by calling
|
18
|
+
`Gyro::Async#await`, and one or more other fibers do the signalling by calling
|
19
|
+
`Gyro::Async#signal`:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
async = Gyro::Async.new
|
23
|
+
spin do
|
24
|
+
sleep 1
|
25
|
+
async.signal
|
26
|
+
end
|
27
|
+
|
28
|
+
async.await
|
29
|
+
```
|
30
|
+
|
31
|
+
The signalling of async watchers is compressed, which means that multiple
|
32
|
+
invocations of `Gyro::Async#signal` before the event loop can continue will
|
33
|
+
result the watcher being signalled just a single time.
|
34
|
+
|
35
|
+
In addition to signalling, the async watcher can also be used to transfer an
|
36
|
+
arbitrary value to the awaitng fiber. See `#signal` for an example.
|
37
|
+
|
38
|
+
## Instance methods
|
39
|
+
|
40
|
+
### #await → object
|
41
|
+
|
42
|
+
Blocks the current thread until the watcher is signalled.
|
43
|
+
|
44
|
+
### #initialize
|
45
|
+
|
46
|
+
Initializes the watcher instance.
|
47
|
+
|
48
|
+
### #signal(value = nil) → async
|
49
|
+
|
50
|
+
Signals the watcher, causing the fiber awaiting the watcher to become runnable
|
51
|
+
and be eventually resumed with the given value.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
async = Gyro::Async.new
|
55
|
+
spin { async.signal('foo') }
|
56
|
+
async.await #=> 'foo'
|
57
|
+
```
|
@@ -0,0 +1,29 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: Gyro::Child
|
4
|
+
parent: API Reference
|
5
|
+
permalink: /api-reference/gyro-child/
|
6
|
+
---
|
7
|
+
# Gyro::Child
|
8
|
+
|
9
|
+
`Gyro::Child` encapsulates a libev [child
|
10
|
+
watcher](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#code_ev_child_code_watch_out_for_pro),
|
11
|
+
used for waiting for a child process to terminate. A `Gyro::Child` watcher
|
12
|
+
instance can be used for low-level control of child processes, instead of using
|
13
|
+
more high-level APIs such `Process.wait` etc.
|
14
|
+
|
15
|
+
## Instance methods
|
16
|
+
|
17
|
+
### #await → [pid, exitcode]
|
18
|
+
|
19
|
+
Blocks the current thread until the watcher is signalled. The return value is an
|
20
|
+
array containing the child's pid and the exit code.
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
pid = Polyphony.fork { sleep 1 }
|
24
|
+
Gyro::Child.new(pid).await #=> [pid, 0]
|
25
|
+
```
|
26
|
+
|
27
|
+
### #initialize(pid)
|
28
|
+
|
29
|
+
Initializes the watcher instance with the given pid
|
@@ -0,0 +1,44 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: Gyro::Queue
|
4
|
+
parent: API Reference
|
5
|
+
permalink: /api-reference/gyro-queue/
|
6
|
+
---
|
7
|
+
# Gyro::Queue
|
8
|
+
|
9
|
+
`Gyro::Queue` implements a polyphonic (fiber-aware) queue that can store 0 or
|
10
|
+
more items of any data types. Adding an item to the queue never blocks.
|
11
|
+
Retrieving an item from the queue will block if the queue is empty.
|
12
|
+
`Gyro::Queue` is both fiber-safe and thread-safe. This means multiple fibers
|
13
|
+
from multiple threads can concurrently interact with the same queue.
|
14
|
+
`Gyro::Queue` is used pervasively across the Polyphony code base for
|
15
|
+
synchronisation and fiber control.
|
16
|
+
|
17
|
+
## Instance methods
|
18
|
+
|
19
|
+
### #<<(object) → queue<br>#push(object) → queue
|
20
|
+
|
21
|
+
Adds an item to the queue.
|
22
|
+
|
23
|
+
### #clear → queue
|
24
|
+
|
25
|
+
Removes all items currently in the queue.
|
26
|
+
|
27
|
+
### #empty? → true or false
|
28
|
+
|
29
|
+
Returns true if the queue is empty. Otherwise returns false.
|
30
|
+
|
31
|
+
### #initialize
|
32
|
+
|
33
|
+
Initializes an empty queue.
|
34
|
+
|
35
|
+
### #shift → object<br>#pop → object
|
36
|
+
|
37
|
+
Retrieves an item from the queue. If the queue is empty, `#shift` blocks until
|
38
|
+
an item is added to the queue or until interrupted. Multiple fibers calling
|
39
|
+
`#shift` are served in a first-ordered first-served manner.
|
40
|
+
|
41
|
+
### #shift_each → [*object]<br>#shift_each({ block }) → queue
|
42
|
+
|
43
|
+
Removes and returns all items currently in the queue. If a block is given, it
|
44
|
+
will be invoked for each item.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: Gyro::Timer
|
4
|
+
parent: API Reference
|
5
|
+
permalink: /api-reference/gyro-timer/
|
6
|
+
---
|
7
|
+
# Gyro::Timer
|
8
|
+
|
9
|
+
`Gyro::Timer` encapsulates a libev [timer
|
10
|
+
watcher](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#code_ev_timer_code_relative_and_opti),
|
11
|
+
allowing waiting a certain amount of time before proceeding with an operation.
|
12
|
+
Watchers can be either one-time timers or recurring timers. The Polyphony API
|
13
|
+
provides various APIs that use timer watchers for timeouts, throttled
|
14
|
+
operations, and sleeping.
|
15
|
+
|
16
|
+
## Instance methods
|
17
|
+
|
18
|
+
### #await → object
|
19
|
+
|
20
|
+
Blocks the current thread until the timer has elapsed. For recurrent timers,
|
21
|
+
`#await` will block until the next timer period has elapsed, as specified by the
|
22
|
+
`repeat` argument given to `#initialize`.
|
23
|
+
|
24
|
+
### #initialize(after, repeat)
|
25
|
+
|
26
|
+
Initializes the watcher instance. The `after` argument gives the time duration
|
27
|
+
in seconds before the timer has elapsed. The `repeat` argument gives the time
|
28
|
+
period for recurring timers, or `0` for non-recurring timers.
|
29
|
+
|
30
|
+
### #stop
|
31
|
+
|
32
|
+
Stops an active recurring timer. Recurring timers stay active (from the point of
|
33
|
+
view of the event loop) even after the timer period has elapsed. Calling `#stop`
|
34
|
+
marks the timer as inactive and cleans up associated resources. This should
|
35
|
+
normally be done inside an `ensure` block:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
def repeat(period)
|
39
|
+
timer = Gyro::Timer.new(period, period)
|
40
|
+
loop do
|
41
|
+
timer.await
|
42
|
+
yield
|
43
|
+
end
|
44
|
+
ensure
|
45
|
+
timer.stop
|
46
|
+
end
|
47
|
+
|
48
|
+
repeat(10) { puts Time.now }
|
49
|
+
```
|
50
|
+
|
51
|
+
There's no need to call `#stop` for non-recurring timers.
|
@@ -0,0 +1,25 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: Gyro
|
4
|
+
parent: API Reference
|
5
|
+
permalink: /api-reference/gyro/
|
6
|
+
---
|
7
|
+
# Gyro
|
8
|
+
|
9
|
+
`Gyro` is the subsystem in charge of the low-level functionality in Polyphony.
|
10
|
+
It contains all of the different event watcher classes, as well as other
|
11
|
+
low-level constructs such as `Gyro::Queue`, a fiber-aware queue implementation,
|
12
|
+
used pervasively across the Polyphony code base.
|
13
|
+
|
14
|
+
While most Polyphony-based applications do not normally need to interact
|
15
|
+
directly with the `Gyro` classes, more advanced applications and libraries may
|
16
|
+
use those classes to enhance Polyphony and create custom concurrency patterns.
|
17
|
+
|
18
|
+
## Classes
|
19
|
+
|
20
|
+
- [`Gyro::Async`](../gyro-async/) - async event watcher
|
21
|
+
- [`Gyro::Child`](../gyro-child/) - child process event watcher
|
22
|
+
- [`Gyro::IO`](../gyro-io/) - IO event watcher
|
23
|
+
- [`Gyro::Queue`](../gyro-queue/) - fiber-aware queue
|
24
|
+
- [`Gyro::Signal`](../gyro-signal/) - signal event watcher
|
25
|
+
- [`Gyro::Timer`](../gyro-timer/) - timer event watcher
|
data/docs/index.md
CHANGED
@@ -64,8 +64,8 @@ adapters are being developed.
|
|
64
64
|
Polyphony draws inspiration from the following, in no particular order:
|
65
65
|
|
66
66
|
* [nio4r](https://github.com/socketry/nio4r/) and
|
67
|
-
[async](https://github.com/socketry/async) (Polyphony's C-extension code
|
68
|
-
|
67
|
+
[async](https://github.com/socketry/async) (Polyphony's C-extension code
|
68
|
+
started as a spinoff of
|
69
69
|
[nio4r's](https://github.com/socketry/nio4r/tree/master/ext))
|
70
70
|
* The [go scheduler](https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html)
|
71
71
|
* [EventMachine](https://github.com/eventmachine/eventmachine)
|
@@ -73,11 +73,13 @@ Polyphony draws inspiration from the following, in no particular order:
|
|
73
73
|
* [Erlang supervisors](http://erlang.org/doc/man/supervisor.html) (and actually,
|
74
74
|
Erlang in general)
|
75
75
|
|
76
|
-
##
|
76
|
+
## Developer Resources
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
78
|
+
* [Tutorial](getting-started/tutorial)
|
79
|
+
* [Main Concepts](main-concepts/concurrency/)
|
80
|
+
* [User Guide](user-guide/all-about-timers/)
|
81
|
+
* [API Reference](api-reference/exception/)
|
82
|
+
* [Examples](https://github.com/digital-fabric/polyphony/tree/9e0f3b09213156bdf376ef33684ef267517f06e8/examples/README.md)
|
81
83
|
|
82
84
|
## Contributing to Polyphony
|
83
85
|
|
@@ -15,9 +15,9 @@ switching between fibers works in Ruby.
|
|
15
15
|
|
16
16
|
Ruby provides two mechanisms for transferring control between fibers:
|
17
17
|
`Fiber#resume` /`Fiber.yield` and `Fiber#transfer`. The first is inherently
|
18
|
-
asymmetric and is
|
18
|
+
asymmetric and is mostly used for implementing generators and [resumable
|
19
19
|
enumerators](https://blog.appsignal.com/2018/11/27/ruby-magic-fibers-and-enumerators-in-ruby.html).
|
20
|
-
Here's
|
20
|
+
Here's an example:
|
21
21
|
|
22
22
|
```ruby
|
23
23
|
fib = Fiber.new do
|
@@ -31,7 +31,7 @@ end
|
|
31
31
|
10.times { puts fib.resume }
|
32
32
|
```
|
33
33
|
|
34
|
-
|
34
|
+
An implication of using resume / yield is that the main fiber can't yield
|
35
35
|
away, meaning we cannot pause the main fiber using `Fiber.yield`.
|
36
36
|
|
37
37
|
The other fiber control mechanism, using `Fiber#transfer`, is fully symmetric:
|
@@ -45,46 +45,48 @@ ping.transfer
|
|
45
45
|
```
|
46
46
|
|
47
47
|
`Fiber#transform` also allows using the main fiber as a general purpose
|
48
|
-
resumable execution context. Polyphony uses `Fiber#transfer`
|
49
|
-
scheduling fibers.
|
48
|
+
resumable execution context. For that reason, Polyphony uses `Fiber#transfer`
|
49
|
+
exclusively for scheduling fibers. Normally, however, applications based on
|
50
|
+
Polyphony will not use this API directly.
|
50
51
|
|
51
52
|
## The Different Fiber states
|
52
53
|
|
53
|
-
In Polyphony, each fiber has one four possible states:
|
54
|
+
In Polyphony, each fiber has one of four possible states:
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
the fiber is
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
`:runnable`. It then waits its turn to run.
|
56
|
+
- `:runnable` - a new fiber will start in the runnable state. This means it is
|
57
|
+
placed on the thread's run queue and is now waiting its turn to be resumed.
|
58
|
+
- `:running` - once the fiber is resumed, it transitions to the running state.
|
59
|
+
`Fiber.current.state` always returns `:running`.
|
60
|
+
- `:wait` - whenever the fiber performs a blocking operation—such as waiting for
|
61
|
+
a timer to elapse, or for a socket to become readable—the fiber transitions to
|
62
|
+
a waiting state. When the corresponding event occurs the fiber will transition
|
63
|
+
to a `:runnable` state, and will be eventually resumed (`:running`).
|
64
|
+
- `:dead` - once the fiber has terminated, it transitions to the dead state.
|
65
65
|
|
66
66
|
## Switchpoints
|
67
67
|
|
68
|
-
A switchpoint is any point in time at which control might switch from the
|
68
|
+
A switchpoint is any point in time at which control *might* switch from the
|
69
69
|
currently running fiber to another fiber that is `:runnable`. This usually
|
70
|
-
occurs when the currently running fiber starts a blocking operation
|
71
|
-
|
72
|
-
|
70
|
+
occurs when the currently running fiber starts a blocking operation, such as
|
71
|
+
reading from a socket or waiting for a timer. It also occurs when the running
|
72
|
+
fiber has explicitly yielded control using `#snooze` or `#suspend`. A
|
73
|
+
Switchpoint will also occur when the currently running fiber has terminated.
|
73
74
|
|
74
75
|
## Scheduler-less scheduling
|
75
76
|
|
76
77
|
Polyphony relies on [libev](http://software.schmorp.de/pkg/libev.html) for
|
77
78
|
handling events such as I/O readiness, timers and signals. In most event
|
78
79
|
reactor-based libraries and frameworks, such as `nio4r`, `EventMachine` or
|
79
|
-
`node.js`, the
|
80
|
-
user-supplied code *from inside the loop*.
|
81
|
-
|
82
|
-
Polyphony
|
83
|
-
|
80
|
+
`node.js`, the entire application is run inside of a reactor loop, and event
|
81
|
+
callbacks are used to schedule user-supplied code *from inside the loop*.
|
82
|
+
|
83
|
+
In Polyphony, however, we have chosen a concurrency model that does not use a
|
84
|
+
loop to schedule fibers. In fact, in Polyphony there's no outer reactor loop,
|
85
|
+
and there's no *scheduler* per se running on a separate execution context.
|
84
86
|
|
85
87
|
Instead, Polyphony maintains for each thread a run queue, a list of `:runnable`
|
86
|
-
fibers. If no fiber is `:runnable`, the libev event
|
87
|
-
|
88
|
+
fibers. If no fiber is `:runnable`, Polyphony will run the libev event loop until
|
89
|
+
at least one event has occurred. Events are handled by adding the corresponding
|
88
90
|
fibers onto the run queue. Finally, control is transferred to the first fiber on
|
89
91
|
the run queue, which will run until it blocks or terminates, at which point
|
90
92
|
control is transferred to the next runnable fiber.
|
@@ -95,8 +97,8 @@ This approach has numerous benefits:
|
|
95
97
|
leading to less context switches, and less bookkeeping.
|
96
98
|
- Clear separation between the reactor code (the `libev` code) and the fiber
|
97
99
|
scheduling code.
|
98
|
-
- Much less time is spent in
|
99
|
-
|
100
|
+
- Much less time is spent in event loop callbacks, letting the event loop run
|
101
|
+
more efficiently.
|
100
102
|
- Fibers are switched outside of the event reactor code, making it easier to
|
101
103
|
avoid race conditions and unexpected behaviours.
|
102
104
|
|
@@ -121,9 +123,9 @@ are waiting and the main fiber is done running, the Ruby process will terminate.
|
|
121
123
|
## Interrupting blocking operations
|
122
124
|
|
123
125
|
Sometimes it is desirable to be able to interrupt a blocking operation, such as
|
124
|
-
waiting for a socket to be readable, or sleeping
|
125
|
-
|
126
|
-
|
126
|
+
waiting for a socket to be readable, or sleeping. This is especially useful when
|
127
|
+
higher-level constructs are needed for controlling multiple concurrent
|
128
|
+
operations.
|
127
129
|
|
128
130
|
Polyphony provides the ability to interrupt a blocking operation by harnessing
|
129
131
|
the ability to transfer values back and forth between fibers using
|
@@ -134,52 +136,24 @@ signalling that the blocking operation has been unsuccessful and allowing
|
|
134
136
|
exception handling using the builtin mechanisms offered by Ruby, namely `rescue`
|
135
137
|
and `ensure` (see also [exception handling](exception-handling.md)).
|
136
138
|
|
137
|
-
|
138
|
-
actual code for I/O reading in Polyphony is written in C and is a bit more
|
139
|
-
involved):
|
139
|
+
This mode of operation makes implementing timeouts almost trivial:
|
140
140
|
|
141
141
|
```ruby
|
142
|
-
def
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
else
|
148
|
-
return result
|
149
|
-
end
|
142
|
+
def with_timeout(duration)
|
143
|
+
interruptible_fiber = Fiber.current
|
144
|
+
timeout_fiber = spin do
|
145
|
+
sleep duration
|
146
|
+
interruptible_fiber.raise 'timeout'
|
150
147
|
end
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
fiber = Fiber.current
|
155
|
-
watcher = Gyro::IO.new(io, :read) { fiber.transfer }
|
156
|
-
|
157
|
-
# run any scheduled fibers or run libev reactor waiting for events
|
158
|
-
result = GV.run
|
159
|
-
|
160
|
-
# waiting fiber is resumed - check transferred value
|
161
|
-
raise result if result.is_a?(Exception)
|
162
|
-
result
|
148
|
+
|
149
|
+
# do work
|
150
|
+
yield
|
163
151
|
ensure
|
164
|
-
|
165
|
-
watcher.active = false
|
152
|
+
timeout_fiber.terminate
|
166
153
|
end
|
167
|
-
```
|
168
154
|
|
169
|
-
|
170
|
-
|
171
|
-
by scheduling the corresponding fiber with an exception:
|
172
|
-
|
173
|
-
```ruby
|
174
|
-
def timeout(duration)
|
175
|
-
fiber = Fiber.current
|
176
|
-
interrupter = spin do
|
177
|
-
Gyro::Timer.new(duration, 0).await
|
178
|
-
fiber.transfer(TimerException.new)
|
179
|
-
end
|
180
|
-
yield
|
181
|
-
ensure
|
182
|
-
interrupter.stop
|
155
|
+
with_timeout(10) do
|
156
|
+
HTTParty.get 'https://acme.com/'
|
183
157
|
end
|
184
158
|
```
|
185
159
|
|
@@ -187,6 +161,15 @@ end
|
|
187
161
|
|
188
162
|
Polyphony performs fiber scheduling separately for each thread. Each thread,
|
189
163
|
therefore, will be able to run multiple fibers independently from other threads.
|
164
|
+
Multithreading in Ruby has limited benefit, due to the global virtual lock that
|
165
|
+
prevents true parallelism. But offloading work to a separate thread might be
|
166
|
+
eneficial when a Polyphonic app needs to use APIs that are not fiber-aware, such
|
167
|
+
as blocking database calls (SQLite in particular), or system calls that might
|
168
|
+
block for an extended duration.
|
169
|
+
|
170
|
+
For this, you can either spawn a new thread, or use the provided
|
171
|
+
`Polyphony::ThreadPool` class that allows you to offload work to a pool of
|
172
|
+
threads.
|
190
173
|
|
191
174
|
## The fiber scheduling algorithm in full
|
192
175
|
|