polyphony 0.34 → 0.36
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|