polyphony 0.33 → 0.34
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 +8 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +93 -68
- data/bin/polyphony-debug +87 -0
- data/docs/_includes/nav.html +5 -1
- data/docs/_sass/overrides.scss +4 -1
- data/docs/api-reference.md +11 -0
- data/docs/api-reference/exception.md +27 -0
- data/docs/api-reference/fiber.md +407 -0
- data/docs/api-reference/io.md +36 -0
- data/docs/api-reference/object.md +99 -0
- data/docs/api-reference/polyphony-baseexception.md +33 -0
- data/docs/api-reference/polyphony-cancel.md +26 -0
- data/docs/api-reference/polyphony-moveon.md +24 -0
- data/docs/api-reference/polyphony-net.md +20 -0
- data/docs/api-reference/polyphony-process.md +28 -0
- data/docs/api-reference/polyphony-resourcepool.md +59 -0
- data/docs/api-reference/polyphony-restart.md +18 -0
- data/docs/api-reference/polyphony-terminate.md +18 -0
- data/docs/api-reference/polyphony-threadpool.md +67 -0
- data/docs/api-reference/polyphony-throttler.md +77 -0
- data/docs/api-reference/polyphony.md +36 -0
- data/docs/api-reference/thread.md +88 -0
- data/docs/getting-started/tutorial.md +59 -156
- data/docs/index.md +2 -0
- data/examples/core/forever_sleep.rb +19 -0
- data/examples/core/xx-caller.rb +12 -0
- data/examples/core/xx-exception-backtrace.rb +40 -0
- data/examples/core/xx-fork-spin.rb +42 -0
- data/examples/core/xx-spin-fork.rb +49 -0
- data/examples/core/xx-supervise-process.rb +30 -0
- data/ext/gyro/gyro.h +1 -0
- data/ext/gyro/selector.c +8 -0
- data/ext/gyro/thread.c +8 -2
- data/lib/polyphony.rb +64 -17
- data/lib/polyphony/adapters/process.rb +29 -0
- data/lib/polyphony/adapters/trace.rb +6 -4
- data/lib/polyphony/core/exceptions.rb +5 -0
- data/lib/polyphony/core/global_api.rb +15 -0
- data/lib/polyphony/extensions/fiber.rb +89 -59
- data/lib/polyphony/version.rb +1 -1
- data/test/test_fiber.rb +23 -75
- data/test/test_global_api.rb +39 -0
- data/test/test_kernel.rb +5 -7
- data/test/test_process_supervision.rb +46 -0
- data/test/test_signal.rb +2 -3
- data/test/test_supervise.rb +103 -0
- metadata +29 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea4d90a517ed25294e677a9d3298ee2f4f79ad269e222c2a6e3a786fed605b50
|
4
|
+
data.tar.gz: 19581d1dea73db08a6b9b0816fd4500c7ca84b2692150585133b7e3f99ee0522
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36291ddf1dedbed0fbdcb347811e657bad19661981cacff2fa9736246e883fa2f56fa4362f266721177559e295adefe3f92c1797e4773dfc03b45857d2ebb3fb
|
7
|
+
data.tar.gz: 8d9a5f0d368167a300e9926c9ad2d57221a423b60dc18d8a395c114feb18e3fa96546ac7709bf8acbabca500ec56d373bcd8f38479183c00077f0ccf513fb039
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 0.34 2020-03-25
|
2
|
+
|
3
|
+
* Implement process supervisor (`Polyphony::ProcessSupervisor`)
|
4
|
+
* Improve fiber supervision
|
5
|
+
* Fix forking behaviour
|
6
|
+
* Use correct backtrace for fiber control exceptions
|
7
|
+
* Allow calling `move_on_after` and `cancel_after` without block
|
8
|
+
|
1
9
|
## 0.33 2020-03-08
|
2
10
|
|
3
11
|
* Implement `Fiber#supervise` (WIP)
|
data/Gemfile.lock
CHANGED
data/TODO.md
CHANGED
@@ -1,12 +1,78 @@
|
|
1
|
+
- Would it be possible to spin up a fiber on another thread?
|
2
|
+
|
3
|
+
The use case is being able to supervise fibers that run on separate threads.
|
4
|
+
This might be useful for distributing jobs (such as handling HTTP connections)
|
5
|
+
over multiple threads.
|
6
|
+
|
7
|
+
For this we need:
|
8
|
+
|
9
|
+
- A way to communicate to a thread that it needs to spin up a fiber, the
|
10
|
+
simplest solution is to start a fiber accepting spin requests for each
|
11
|
+
thread (in `Thread#initialize`).
|
12
|
+
- An API:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
spin(on_thread: thread) { do_something_important }
|
16
|
+
```
|
17
|
+
|
18
|
+
An alternative is to turn the main fiber of spawned threads into a child of
|
19
|
+
the spawning fiber. But since a lot of people might start threads without any
|
20
|
+
regard to fibers, it might be better to implement this in a new API. An
|
21
|
+
example of the top of my head for threads that shouldn't be children of the
|
22
|
+
spawning fiber is our own test helper, which kills all child fibers after each
|
23
|
+
test. MiniTest has some threads it spawns for running tests in parallel, and
|
24
|
+
we don't want to stop them after each test!
|
25
|
+
|
26
|
+
So, a good solution would be:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
t = Thread.new { do_stuff }
|
30
|
+
t.parent_fiber = Fiber.current
|
31
|
+
# or otherwise:
|
32
|
+
Fiber.current.add_child_fiber(t.main_fiber)
|
33
|
+
```
|
34
|
+
|
35
|
+
## 0.35 Some more API work, more docs
|
36
|
+
|
1
37
|
- Debugging
|
2
38
|
- Eat your own dogfood: need a good tool to check what's going on when some
|
3
39
|
test fails
|
4
40
|
- Needs to work with Pry (can write perhaps an extension for pry)
|
5
41
|
- First impl in Ruby using `TracePoint` API
|
6
42
|
- Mode of operation:
|
7
|
-
-
|
8
|
-
|
9
|
-
|
43
|
+
- Two parts: tracer and controller
|
44
|
+
- The tracer keeps state
|
45
|
+
- The controller interacts with the user and tells the tracer what to do
|
46
|
+
- Tracer and controller interact using fiber message passing
|
47
|
+
- The controller lives on a separate thread
|
48
|
+
- The tracer invokes the controller at the appropriate point in time
|
49
|
+
according to the state. For example, when doing a `next` command, the
|
50
|
+
tracer will wait for a `:line` event to occur within the same stack
|
51
|
+
frame, or for the frame to be popped on a `:return` event, and only then
|
52
|
+
will it invoke the controller.
|
53
|
+
- While invoking the controller and waiting for its reply, the tracer
|
54
|
+
optionally performs a fiber lock in order to prevent other fibers from
|
55
|
+
advancing (the fiber lock is the default mode).
|
56
|
+
- The tracer's state is completely inspectable
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
PolyTrace.state
|
60
|
+
PolyTrace.current_fiber
|
61
|
+
PolyTrace.call_stack
|
62
|
+
```
|
63
|
+
|
64
|
+
- Modes can be changed using an API, e.g.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
PolyTrace.fiber_lock = false
|
68
|
+
```
|
69
|
+
|
70
|
+
- Fibers can be interrogated using an API, or perhaps using some kind of
|
71
|
+
Pry command...
|
72
|
+
|
73
|
+
- Normal mode of operation is fiber modal, that is, trace only the currently
|
74
|
+
selected fiber. The currently selected fiber may be changed upon breakpoint
|
75
|
+
|
10
76
|
- Step over should return on the next line *for the same fiber*
|
11
77
|
- The event loop (all event loops?) should be suspended so timers are adjusted
|
12
78
|
accordingly, so on control passing to debugger we:
|
@@ -42,77 +108,36 @@
|
|
42
108
|
- from an expression: `Fiber.current.children`
|
43
109
|
- maybe just `select f1` (where f1 is a local var)
|
44
110
|
|
45
|
-
-
|
46
|
-
-
|
47
|
-
|
48
|
-
|
49
|
-
- (restart) strategy (taken from Erlang):
|
50
|
-
- `:one_for_one`: if a child terminates, it is restarted
|
51
|
-
- `:one_for_all`: if a child terminates, all other children are terminated,
|
52
|
-
then all are restarted
|
53
|
-
- `:simple_on_for_one`: same as `:one_for_one` except all children are
|
54
|
-
identical (run the same block)
|
55
|
-
- I'm not so sure this kind of supervision is what we need. We can envision
|
56
|
-
an API such as the following:
|
111
|
+
- Allow locking the scheduler on to one fiber
|
112
|
+
- Add instance var `@fiber_lock`
|
113
|
+
- API is `Thread#fiber_lock` which sets the fiber_lock instance varwhile
|
114
|
+
running the block:
|
57
115
|
|
58
116
|
```ruby
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
}
|
64
|
-
```
|
65
|
-
|
66
|
-
- The default for `#supervise` should be:
|
67
|
-
- wait for all fibers to terminate
|
68
|
-
- propagate exceptions
|
69
|
-
- no restart
|
70
|
-
|
71
|
-
- `#supervise` could also take a block:
|
72
|
-
|
73
|
-
```ruby
|
74
|
-
supervise do |fiber, error|
|
75
|
-
# this block is called when a fiber is terminated, letting the developer
|
76
|
-
# decide how to react.
|
77
|
-
|
78
|
-
# We can propagate the error
|
79
|
-
raise error if error
|
80
|
-
|
81
|
-
# We can restart the fiber (the respun fiber will have the same parent,
|
82
|
-
# the same caller location, the same tag)
|
83
|
-
fiber.respin # TODO: implement Fiber#respin
|
117
|
+
def debug_prompt
|
118
|
+
Thread.current.fiber_lock do
|
119
|
+
...
|
120
|
+
end
|
84
121
|
end
|
85
122
|
```
|
123
|
+
- When `@fiber_lock` is set, it is considered as the only one in the run
|
124
|
+
queue:
|
86
125
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
spin(supervise: true) do
|
126
|
+
```c
|
127
|
+
VALUE fiber_lock = rb_ivar_get(self, ID_ivar_fiber_lock);
|
128
|
+
int locked = fiber_lock != Qnil;
|
91
129
|
|
92
|
-
|
130
|
+
while (1) {
|
131
|
+
next_fiber = locked ? fiber_lock : rb_ary_shift(queue);
|
132
|
+
...
|
133
|
+
}
|
93
134
|
```
|
94
135
|
|
95
|
-
- Or maybe:
|
96
136
|
|
97
|
-
```ruby
|
98
|
-
spin_supervisor do
|
99
|
-
|
100
|
-
end
|
101
|
-
```
|
102
137
|
|
103
|
-
## 0.32 Some more API work, more docs
|
104
138
|
|
105
|
-
- Allow calling `cancel_after` and `move_on_after` without blocks, just return
|
106
|
-
the cancelling fiber:
|
107
139
|
|
108
|
-
|
109
|
-
def foo
|
110
|
-
f = cancel_after(10)
|
111
|
-
do_something_slow
|
112
|
-
ensure
|
113
|
-
f.stop
|
114
|
-
end
|
115
|
-
```
|
140
|
+
|
116
141
|
|
117
142
|
- Docs
|
118
143
|
- landing page:
|
@@ -127,7 +152,7 @@
|
|
127
152
|
- Check why first call to `#sleep` returns too early in tests. Check the
|
128
153
|
sleep behaviour in a spawned thread.
|
129
154
|
|
130
|
-
## 0.
|
155
|
+
## 0.36 Sinatra / Sidekiq
|
131
156
|
|
132
157
|
- sintra app with database access (postgresql)
|
133
158
|
|
@@ -137,13 +162,13 @@
|
|
137
162
|
- test performance
|
138
163
|
- proceed from there
|
139
164
|
|
140
|
-
## 0.
|
165
|
+
## 0.37 Testing && Docs
|
141
166
|
|
142
167
|
- Pull out redis/postgres code, put into new `polyphony-xxx` gems
|
143
168
|
|
144
|
-
## 0.
|
169
|
+
## 0.38 Integration
|
145
170
|
|
146
|
-
## 0.
|
171
|
+
## 0.39 Real IO#gets and IO#read
|
147
172
|
|
148
173
|
- More tests
|
149
174
|
- Implement some basic stuff missing:
|
@@ -153,11 +178,11 @@
|
|
153
178
|
- `IO.foreach`
|
154
179
|
- `Process.waitpid`
|
155
180
|
|
156
|
-
## 0.
|
181
|
+
## 0.40 Rails
|
157
182
|
|
158
183
|
- Rails?
|
159
184
|
|
160
|
-
## 0.
|
185
|
+
## 0.41 DNS
|
161
186
|
|
162
187
|
### DNS client
|
163
188
|
|
data/bin/polyphony-debug
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'polyphony'
|
7
|
+
|
8
|
+
Gyro.trace(true)
|
9
|
+
|
10
|
+
FILE_CACHE = {}
|
11
|
+
@ready = nil
|
12
|
+
@last_location = nil
|
13
|
+
@mode = :step
|
14
|
+
@current_fiber = nil
|
15
|
+
@stacks = Hash.new([])
|
16
|
+
|
17
|
+
def debug_prompt
|
18
|
+
loop do
|
19
|
+
STDOUT << '(debug) '
|
20
|
+
case (cmd = STDIN.gets.chomp)
|
21
|
+
when '.q', '.quit'
|
22
|
+
exit!
|
23
|
+
when 's', 'step'
|
24
|
+
@mode = :step
|
25
|
+
return
|
26
|
+
else
|
27
|
+
begin
|
28
|
+
result = binding.eval(cmd)
|
29
|
+
p result
|
30
|
+
rescue Exception => e
|
31
|
+
p e
|
32
|
+
puts e.backtrace.join("\n")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_snippet(path, lineno)
|
39
|
+
lines = FILE_CACHE[path] ||= IO.read(path).lines
|
40
|
+
start_idx = lineno - 5
|
41
|
+
stop_idx = lineno + 3
|
42
|
+
stop_idx = lines.size - 1 if stop_idx >= lines.size
|
43
|
+
start_idx = 0 if start_idx < 0
|
44
|
+
(start_idx..stop_idx).map { |idx| [idx + 1, lines[idx]]}
|
45
|
+
end
|
46
|
+
|
47
|
+
def print_snippet(snippet, cur_line)
|
48
|
+
snippet.each do |(lineno, line)|
|
49
|
+
is_cur = lineno == cur_line
|
50
|
+
formatted = format("%s%03d %s", is_cur ? '*=> ' : ' ', lineno, line)
|
51
|
+
puts formatted
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
tp = Polyphony::Trace.new(*Polyphony::Trace::STOCK_EVENTS, :fiber_all) do |r|
|
56
|
+
unless @ready
|
57
|
+
@ready = true if r[:event] == :return
|
58
|
+
@current_fiber = r[:fiber]
|
59
|
+
next
|
60
|
+
end
|
61
|
+
|
62
|
+
case r[:event]
|
63
|
+
when :call, :b_call, :c_call
|
64
|
+
@stacks[r[:fiber]] << r
|
65
|
+
when :return, :b_return, :c_return
|
66
|
+
@stacks[r[:fiber]].pop
|
67
|
+
when :line
|
68
|
+
case @mode
|
69
|
+
when :step
|
70
|
+
if r[:location] != @last_location && r[:fiber] == @current_fiber
|
71
|
+
@last_location = r[:location]
|
72
|
+
puts "in #{r[:location]}"
|
73
|
+
puts
|
74
|
+
snippet = get_snippet(r[:path], r[:lineno])
|
75
|
+
print_snippet(snippet, r[:lineno])
|
76
|
+
puts
|
77
|
+
debug_prompt
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
rescue Exception => e
|
82
|
+
p e
|
83
|
+
exit!
|
84
|
+
end
|
85
|
+
tp.enable
|
86
|
+
|
87
|
+
require File.expand_path(ARGV[0])
|
data/docs/_includes/nav.html
CHANGED
@@ -17,7 +17,11 @@
|
|
17
17
|
<a href="{{ node.url | absolute_url }}" class="navigation-list-link{% if page.url == node.url %} active{% endif %}">{{ node.title }}</a>
|
18
18
|
{%- endif -%}
|
19
19
|
{%- if node.has_children -%}
|
20
|
-
{%-
|
20
|
+
{%- if node.alphabetical_order -%}
|
21
|
+
{%- assign children_list = site.html_pages | where: "parent", node.title | sort:"title" -%}
|
22
|
+
{%- else -%}
|
23
|
+
{%- assign children_list = site.html_pages | where: "parent", node.title | sort:"nav_order" -%}
|
24
|
+
{%- endif -%}
|
21
25
|
<ul class="navigation-list-child-list ">
|
22
26
|
{%- for child in children_list -%}
|
23
27
|
<li class="navigation-list-item {% if page.url == child.url or page.parent == child.title %} active{% endif %}">
|
data/docs/_sass/overrides.scss
CHANGED
@@ -30,12 +30,15 @@ span.section-title {
|
|
30
30
|
}
|
31
31
|
|
32
32
|
a.navigation-list-link {
|
33
|
+
margin-left: 4px;
|
34
|
+
padding-left: 4px;
|
33
35
|
color: $nav-child-link-color;
|
34
36
|
font-weight: 600;
|
35
37
|
}
|
36
38
|
|
37
39
|
a.navigation-list-link.active {
|
38
|
-
|
40
|
+
margin-left: 0;
|
41
|
+
border-left: 4px #6ae solid;
|
39
42
|
}
|
40
43
|
|
41
44
|
a.navigation-list-link:hover {
|
@@ -0,0 +1,27 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: ::Exception
|
4
|
+
parent: API Reference
|
5
|
+
permalink: /api-reference/exception/
|
6
|
+
---
|
7
|
+
# ::Exception
|
8
|
+
|
9
|
+
[Ruby core Exception documentation](https://ruby-doc.org/core-2.7.0/Exception.html)
|
10
|
+
|
11
|
+
The core `Exception` class is enhanced to provide a better backtrace that takes
|
12
|
+
into account the fiber hierarchy. In addition, a `source_fiber` attribute allows
|
13
|
+
tracking the fiber from which an uncaught exception was propagated.
|
14
|
+
|
15
|
+
## Class Methods
|
16
|
+
|
17
|
+
## Instance methods
|
18
|
+
|
19
|
+
### #source_fiber → fiber
|
20
|
+
|
21
|
+
Returns the fiber in which the exception occurred. Polyphony sets this attribute
|
22
|
+
only for uncaught exceptions. Currently this attribute is only used in a
|
23
|
+
meaningful way for supervising fibers.
|
24
|
+
|
25
|
+
### #source_fiber=(fiber) → fiber
|
26
|
+
|
27
|
+
Sets the fiber in which the exception occurred.
|
@@ -0,0 +1,407 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: ::Fiber
|
4
|
+
parent: API Reference
|
5
|
+
permalink: /api-reference/fiber/
|
6
|
+
# prev_title: Tutorial
|
7
|
+
# next_title: How Fibers are Scheduled
|
8
|
+
---
|
9
|
+
# ::Fiber
|
10
|
+
|
11
|
+
[Ruby core Fiber documentation](https://ruby-doc.org/core-2.7.0/Fiber.html)
|
12
|
+
|
13
|
+
Polyphony enhances the core `Fiber` class with APIs for scheduling, exception
|
14
|
+
handling, message passing, and more. Normally, fibers should be created using
|
15
|
+
`Object#spin` or `Fiber#spin`, but some fibers might be created implicitly when
|
16
|
+
using lazy enumerators or third party gems. For fibers created implicitly,
|
17
|
+
Polyphony provides `Fiber#setup_raw`, which enables scheduling and message
|
18
|
+
passing for such fibers.
|
19
|
+
|
20
|
+
While most work on fibers since their introduction into MRI Ruby has
|
21
|
+
concentrated on `Fiber#resume` and `Fiber.yield` for transferring control
|
22
|
+
between fibers, Polyphony uses `Fiber#transfer` exclusively, which allows
|
23
|
+
fully symmetric transfer of control between fibers.
|
24
|
+
|
25
|
+
## Class Methods
|
26
|
+
|
27
|
+
### ::await(\*fibers) → [\*result]
|
28
|
+
|
29
|
+
Awaits all given fibers, returning when all fibers have terminated. If one of
|
30
|
+
the given fibers terminates with an uncaught exception, `::await` will terminate
|
31
|
+
and await all fibers that are still running, then reraise the exception. All the
|
32
|
+
given fibers are guaranteed to have terminated when `::await` returns. If no
|
33
|
+
exception is raised, `::await` returns an array containing the results of all
|
34
|
+
given fibers, in the same order.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
f1 = spin { sleep 1 }
|
38
|
+
f2 = spin { sleep 2 }
|
39
|
+
Fiber.await(f1, f2)
|
40
|
+
```
|
41
|
+
|
42
|
+
### ::select(\*fibers) → [\*result]
|
43
|
+
|
44
|
+
Selects the first fiber to have terminated among the given fibers. If any of the
|
45
|
+
given fibers terminates with an uncaught exception, `::select` will reraise the
|
46
|
+
exception. If no exception is raised, `::select` returns an array containing the
|
47
|
+
fiber and its result. All given fibers are guaranteed to have terminated when
|
48
|
+
`::select` returns.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# get the fastest reply of a bunch of URLs
|
52
|
+
fibers = urls.map { |u| spin { [u, HTTParty.get(u)] } }
|
53
|
+
fiber, (url, result) = Fiber.select(*fibers)
|
54
|
+
```
|
55
|
+
|
56
|
+
## Instance methods
|
57
|
+
|
58
|
+
### #<<(object) → fiber<br>#send(object) → fiber
|
59
|
+
|
60
|
+
Adds a message to the fiber's mailbox. The message can be any object. This
|
61
|
+
method is complemented by `Fiber#receive`.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
f = spin do
|
65
|
+
loop do
|
66
|
+
receiver, x = receive
|
67
|
+
receiver << x * 10
|
68
|
+
end
|
69
|
+
end
|
70
|
+
f << 2
|
71
|
+
result = receive #=> 20
|
72
|
+
```
|
73
|
+
|
74
|
+
### #await → object<br>#join → object
|
75
|
+
|
76
|
+
Awaits the termination of the fiber. If the fiber terminates with an uncaught
|
77
|
+
exception, `#await` will reraise the exception. Otherwise `#await` returns the
|
78
|
+
result of the fiber.
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
f = spin { 'foo' }
|
82
|
+
f.await #=> 'foo'
|
83
|
+
```
|
84
|
+
|
85
|
+
### #await_all_children → fiber
|
86
|
+
|
87
|
+
Waits for all the fiber's child fibers to terminate. This method is normally
|
88
|
+
coupled with `Fiber#terminate_all_children`. See also
|
89
|
+
`Fiber#shutdown_all_children`.
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
jobs.each { |j| spin { process(j) } }
|
93
|
+
sleep 1
|
94
|
+
terminate_all_children
|
95
|
+
await_all_children
|
96
|
+
```
|
97
|
+
|
98
|
+
### #caller → [*location]
|
99
|
+
|
100
|
+
Return the execution stack of the fiber, including that of the fiber from which
|
101
|
+
it was spun.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
spin {
|
105
|
+
spin {
|
106
|
+
spin {
|
107
|
+
pp Fiber.current.caller
|
108
|
+
}.await
|
109
|
+
}.await
|
110
|
+
}.await
|
111
|
+
|
112
|
+
#=> ["examples/core/xx-caller.rb:3:in `block (2 levels) in <main>'",
|
113
|
+
#=> "examples/core/xx-caller.rb:2:in `block in <main>'",
|
114
|
+
#=> "examples/core/xx-caller.rb:1:in `<main>'"]
|
115
|
+
```
|
116
|
+
|
117
|
+
### #cancel! → fiber
|
118
|
+
|
119
|
+
Terminates the fiber by raising a `Polyphony::Cancel` exception. If uncaught,
|
120
|
+
the exception will be propagated.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
f = spin { sleep 1 }
|
124
|
+
f.cancel! #=> exception: Polyphony::Cancel
|
125
|
+
```
|
126
|
+
|
127
|
+
### #children → [\*fiber]
|
128
|
+
|
129
|
+
Returns an array containing all of the fiber's child fibers. A child fiber's
|
130
|
+
lifetime is limited to that of its immediate parent.
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
f1 = spin { sleep 1 }
|
134
|
+
f2 = spin { sleep 1 }
|
135
|
+
f3 = spin { sleep 1 }
|
136
|
+
Fiber.current.children #=> [f1, f2, f3]
|
137
|
+
```
|
138
|
+
|
139
|
+
### #interrupt(object = nil) → fiber<br>#stop(object = nil) → fiber
|
140
|
+
|
141
|
+
Terminates the fiber by raising a `Polyphony::MoveOn` exception in its context.
|
142
|
+
The given object will be set as the fiber's result. Note that a `MoveOn`
|
143
|
+
exception is never propagated.
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
f = spin { do_something_slow }
|
147
|
+
f.interrupt('never mind')
|
148
|
+
f.await #=> 'never mind'
|
149
|
+
```
|
150
|
+
|
151
|
+
### #location → string
|
152
|
+
|
153
|
+
Returns the location of the fiber's associated block, or `(root)` for the main
|
154
|
+
fiber.
|
155
|
+
|
156
|
+
### #main? → true or false
|
157
|
+
|
158
|
+
Returns true if the fiber is the main fiber for its thread.
|
159
|
+
|
160
|
+
### #parent → fiber
|
161
|
+
|
162
|
+
Returns the fiber's parent fiber. The main fiber's parent is nil.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
f = spin { sleep 1 }
|
166
|
+
f.parent #=> Fiber.current
|
167
|
+
```
|
168
|
+
|
169
|
+
### #raise(\*args) → fiber
|
170
|
+
|
171
|
+
Raises an error in the context of the fiber. The given exception can be a string
|
172
|
+
(for raising `RuntimeError` exceptions with a given message), an exception
|
173
|
+
class, or an exception instance. If no argument is given, a `RuntimeError` will
|
174
|
+
be raised. Uncaught exceptions will be propagated.
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
f = spin { sleep 1 }
|
178
|
+
f.raise('foo') # raises a RuntimeError
|
179
|
+
f.raise(MyException, 'my error message') # exception class with message
|
180
|
+
f.raise(MyException.new('my error message')) # exception instance
|
181
|
+
# or simply
|
182
|
+
f.raise
|
183
|
+
```
|
184
|
+
|
185
|
+
### #receive → object
|
186
|
+
|
187
|
+
Pops the first message from the fiber's mailbox. If no message is available,
|
188
|
+
`#receive` will block until a message is pushed to the mailbox. The received
|
189
|
+
message can be any kind of object. This method is complemented by
|
190
|
+
`Fiber#<<`/`Fiber#send`.
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
spin { Fiber.current.parent << 'hello from child' }
|
194
|
+
message = receive #=> 'hello from child'
|
195
|
+
```
|
196
|
+
|
197
|
+
### #receive_pending → [*object]
|
198
|
+
|
199
|
+
Returns all messages currently in the mailbox, emptying the mailbox. This method
|
200
|
+
does not block if no the mailbox is already empty. This method may be used to
|
201
|
+
process any pending messages upon fiber termination:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
worker = spin do
|
205
|
+
loop do
|
206
|
+
job = receive
|
207
|
+
handle_job(job)
|
208
|
+
end
|
209
|
+
rescue Polyphony::Terminate => e
|
210
|
+
receive_pending.each { |job| handle_job(job) }
|
211
|
+
end
|
212
|
+
```
|
213
|
+
|
214
|
+
### #restart(object = nil) → fiber<br>#reset(object = nil) → fiber
|
215
|
+
|
216
|
+
Restarts the fiber, essentially rerunning the fiber's associated block,
|
217
|
+
restoring it to its primary state. If the fiber is already terminated, a new
|
218
|
+
fiber will be created and returned. If the fiber's block takes an argument, its
|
219
|
+
value can be set by passing it to `#restart`.
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
counter = 0
|
223
|
+
f = spin { sleep 1; counter += 1 }
|
224
|
+
f.await #=> 1
|
225
|
+
f.restart
|
226
|
+
f.await #=> 2
|
227
|
+
```
|
228
|
+
|
229
|
+
### #result → object
|
230
|
+
|
231
|
+
Returns the result of the fiber. If the fiber has not yet terminated, `nil` is
|
232
|
+
returned. If the fiber terminated with an uncaught exception, the exception is
|
233
|
+
returned.
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
f = spin { sleep 1; 'foo' }
|
237
|
+
f.await
|
238
|
+
f.result #=> 'foo'
|
239
|
+
```
|
240
|
+
|
241
|
+
### #running? → true or false
|
242
|
+
|
243
|
+
Returns true if the fiber is not yet terminated.
|
244
|
+
|
245
|
+
### #schedule(object = nil) → fiber
|
246
|
+
|
247
|
+
Adds the fiber to its thread's run queue. The fiber will be eventually resumed
|
248
|
+
with the given value, which can be any object. If an exception is given, it will
|
249
|
+
be raised in the context of the fiber upon resuming. If the fiber is already on
|
250
|
+
the run queue, the resume value will be updated.
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
f = spin do
|
254
|
+
result = suspend
|
255
|
+
p result
|
256
|
+
end
|
257
|
+
|
258
|
+
sleep 0.1
|
259
|
+
f.schedule 'foo'
|
260
|
+
f.await
|
261
|
+
#=> 'foo'
|
262
|
+
```
|
263
|
+
|
264
|
+
### #shutdown_all_children → fiber
|
265
|
+
|
266
|
+
Terminates all of the fiber's child fibers and blocks until all are terminated.
|
267
|
+
This method is can be used to replace calls to `#terminate_all_children`
|
268
|
+
followed by `#await_all_children`.
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
jobs.each { |j| spin { process(j) } }
|
272
|
+
sleep 1
|
273
|
+
shutdown_all_children
|
274
|
+
```
|
275
|
+
|
276
|
+
### #spin(tag = nil, { block }) → fiber
|
277
|
+
|
278
|
+
Creates a new fiber with self as its parent. The returned fiber is put on the
|
279
|
+
run queue of the parent fiber's associated thread. A tag of any object type can
|
280
|
+
be associated with the fiber. Note that `Object#spin` is a shortcut for
|
281
|
+
`Fiber.current.spin`.
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
f = Fiber.current.spin { |x|'foo' }
|
285
|
+
f.await #=> 'foo'
|
286
|
+
```
|
287
|
+
|
288
|
+
If the block takes an argument, its value can be controlled by explicitly
|
289
|
+
calling `Fiber#schedule`. The result of the given block (the value of the last
|
290
|
+
statement in the block) can be retrieved using `Fiber#result` or by otherwise
|
291
|
+
using fiber control APIs such as `Fiber#await`.
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
f = spin { |x| x * 10 }
|
295
|
+
f.schedule(2)
|
296
|
+
f.await #=> 20
|
297
|
+
```
|
298
|
+
|
299
|
+
### #state → symbol
|
300
|
+
|
301
|
+
Returns the fiber's current state, which can be any of the following:
|
302
|
+
|
303
|
+
- `:waiting` - the fiber is currently waiting for an operation to complete.
|
304
|
+
- `:runnable` - the fiber is scheduled to be resumed (put on the run queue).
|
305
|
+
- `:running` - the fiber is currently running.
|
306
|
+
- `:dead` - the fiber has terminated.
|
307
|
+
|
308
|
+
### #supervise(opts = {}) → fiber
|
309
|
+
|
310
|
+
Supervises all child fibers, optionally restarting any fiber that terminates.
|
311
|
+
|
312
|
+
The given `opts` argument controls the behaviour of the supervision. The
|
313
|
+
following options are currently supported:
|
314
|
+
|
315
|
+
- `:restart`: restart options
|
316
|
+
- `nil` - Child fibers are not restarted (default behaviour).
|
317
|
+
- `true` - Any child fiber that terminates is restarted.
|
318
|
+
- `:on_error` - Any child fiber that terminates with an uncaught exception is
|
319
|
+
restarted.
|
320
|
+
- `:watcher`: a fiber watching supervision events.
|
321
|
+
|
322
|
+
If a watcher fiber is specified, it will receive supervision events to its
|
323
|
+
mailbox. The events are of the form `[<event_type>, <fiber>]`, for example
|
324
|
+
`[:restart, child_fiber_1]`. Here's an example of using a watcher fiber:
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
watcher = spin_loop do
|
328
|
+
kind, fiber = receive
|
329
|
+
case kind
|
330
|
+
when :restart
|
331
|
+
puts "fiber #{fiber.inspect} restarted"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
...
|
335
|
+
supervise(restart: true, watcher: watcher)
|
336
|
+
```
|
337
|
+
|
338
|
+
### #tag → object
|
339
|
+
|
340
|
+
Returns the tag associated with the fiber, normally passed to `Fiber#spin`. The
|
341
|
+
tag can be any kind of object. The default tag is `nil`.
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
f = spin(:worker) { do_some_work }
|
345
|
+
f.tag #=> :worker
|
346
|
+
```
|
347
|
+
|
348
|
+
### #tag=(object) → object
|
349
|
+
|
350
|
+
Sets the tag associated with the fiber. The tag can be any kind of object.
|
351
|
+
|
352
|
+
```ruby
|
353
|
+
f = spin { do_some_work }
|
354
|
+
f.tag = :worker
|
355
|
+
```
|
356
|
+
|
357
|
+
### #terminate → fiber
|
358
|
+
|
359
|
+
Terminates the fiber by raising a `Polyphony::Terminate` exception. The
|
360
|
+
exception is not propagated. Note that the fiber is not guaranteed to terminate
|
361
|
+
before `#terminate` returns. The fiber will need to run first in order to raise
|
362
|
+
the `Terminate` exception and terminate. This method is normally coupled with
|
363
|
+
`Fiber#await`:
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
f1 = spin { sleep 1 }
|
367
|
+
f2 = spin { sleep 2 }
|
368
|
+
|
369
|
+
f1.await
|
370
|
+
|
371
|
+
f2.terminate
|
372
|
+
f2.await
|
373
|
+
```
|
374
|
+
|
375
|
+
### #terminate_all_children → fiber
|
376
|
+
|
377
|
+
Terminates all of the fiber's child fibers. Note that `#terminate_all_children`
|
378
|
+
does not acutally wait for all child fibers to terminate. This method is
|
379
|
+
normally coupled with `Fiber#await_all_children`. See also `Fiber#shutdown_all_children`.
|
380
|
+
|
381
|
+
```ruby
|
382
|
+
jobs.each { |j| spin { process(j) } }
|
383
|
+
sleep 1
|
384
|
+
terminate_all_children
|
385
|
+
await_all_children
|
386
|
+
```
|
387
|
+
|
388
|
+
### #thread → thread
|
389
|
+
|
390
|
+
Returns the thread to which the fiber belongs.
|
391
|
+
|
392
|
+
```ruby
|
393
|
+
f = spin(:worker) { do_some_work }
|
394
|
+
f.thread #=> Thread.current
|
395
|
+
```
|
396
|
+
|
397
|
+
### #when_done({ block }) → fiber
|
398
|
+
|
399
|
+
Installs a hook to be called when the fiber is terminated. The block will be
|
400
|
+
called with the fiber's result. If the fiber terminates with an uncaught
|
401
|
+
exception, the exception will be passed to the block.
|
402
|
+
|
403
|
+
```ruby
|
404
|
+
f = spin { 'foo' }
|
405
|
+
f.when_done { |r| puts "got #{r} from fiber" }
|
406
|
+
f.await #=> STDOUT: 'got foo from fiber'
|
407
|
+
```
|