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