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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/Gemfile.lock +1 -1
  4. data/TODO.md +93 -68
  5. data/bin/polyphony-debug +87 -0
  6. data/docs/_includes/nav.html +5 -1
  7. data/docs/_sass/overrides.scss +4 -1
  8. data/docs/api-reference.md +11 -0
  9. data/docs/api-reference/exception.md +27 -0
  10. data/docs/api-reference/fiber.md +407 -0
  11. data/docs/api-reference/io.md +36 -0
  12. data/docs/api-reference/object.md +99 -0
  13. data/docs/api-reference/polyphony-baseexception.md +33 -0
  14. data/docs/api-reference/polyphony-cancel.md +26 -0
  15. data/docs/api-reference/polyphony-moveon.md +24 -0
  16. data/docs/api-reference/polyphony-net.md +20 -0
  17. data/docs/api-reference/polyphony-process.md +28 -0
  18. data/docs/api-reference/polyphony-resourcepool.md +59 -0
  19. data/docs/api-reference/polyphony-restart.md +18 -0
  20. data/docs/api-reference/polyphony-terminate.md +18 -0
  21. data/docs/api-reference/polyphony-threadpool.md +67 -0
  22. data/docs/api-reference/polyphony-throttler.md +77 -0
  23. data/docs/api-reference/polyphony.md +36 -0
  24. data/docs/api-reference/thread.md +88 -0
  25. data/docs/getting-started/tutorial.md +59 -156
  26. data/docs/index.md +2 -0
  27. data/examples/core/forever_sleep.rb +19 -0
  28. data/examples/core/xx-caller.rb +12 -0
  29. data/examples/core/xx-exception-backtrace.rb +40 -0
  30. data/examples/core/xx-fork-spin.rb +42 -0
  31. data/examples/core/xx-spin-fork.rb +49 -0
  32. data/examples/core/xx-supervise-process.rb +30 -0
  33. data/ext/gyro/gyro.h +1 -0
  34. data/ext/gyro/selector.c +8 -0
  35. data/ext/gyro/thread.c +8 -2
  36. data/lib/polyphony.rb +64 -17
  37. data/lib/polyphony/adapters/process.rb +29 -0
  38. data/lib/polyphony/adapters/trace.rb +6 -4
  39. data/lib/polyphony/core/exceptions.rb +5 -0
  40. data/lib/polyphony/core/global_api.rb +15 -0
  41. data/lib/polyphony/extensions/fiber.rb +89 -59
  42. data/lib/polyphony/version.rb +1 -1
  43. data/test/test_fiber.rb +23 -75
  44. data/test/test_global_api.rb +39 -0
  45. data/test/test_kernel.rb +5 -7
  46. data/test/test_process_supervision.rb +46 -0
  47. data/test/test_signal.rb +2 -3
  48. data/test/test_supervise.rb +103 -0
  49. metadata +29 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fde67b1d9cd285b35d265f12648e48e43073fd3b8e27713f981883ab4b080c12
4
- data.tar.gz: a567e1e171b4f61f5fb344b3345b4a9e6f32ec7a810258c99007a17614e5cd5e
3
+ metadata.gz: ea4d90a517ed25294e677a9d3298ee2f4f79ad269e222c2a6e3a786fed605b50
4
+ data.tar.gz: 19581d1dea73db08a6b9b0816fd4500c7ca84b2692150585133b7e3f99ee0522
5
5
  SHA512:
6
- metadata.gz: 3e99f3e9f9acce3b2deadf516dc254ae6a305a64b9bd05a04e6207834f1189c61ae44481e485381fbb08eae3ee2f1da0843441ee518f64448f76c3529e1281a7
7
- data.tar.gz: b6ebe93d0eb812010aafdcc2f9298e1237c14a6b0da6b3da30abe917217fb04fac24671e02792c0c3f790912660ed0b64355849e81e07bb35029dc647f413589
6
+ metadata.gz: 36291ddf1dedbed0fbdcb347811e657bad19661981cacff2fa9736246e883fa2f56fa4362f266721177559e295adefe3f92c1797e4773dfc03b45857d2ebb3fb
7
+ data.tar.gz: 8d9a5f0d368167a300e9926c9ad2d57221a423b60dc18d8a395c114feb18e3fa96546ac7709bf8acbabca500ec56d373bcd8f38479183c00077f0ccf513fb039
@@ -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)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.33)
4
+ polyphony (0.34)
5
5
  modulation (~> 1.0)
6
6
 
7
7
  GEM
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
- - Debugger runs on separate thread
8
- - The `TracePoint` handler passes control to the debugger thread, and waits
9
- for reply (probably using Fiber messages)
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
- - Fiber supervision
46
- - can a fiber be restarted?
47
- - Theoretically, we can take the same block and rerun it under a different
48
- fiber
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
- spin {
60
- spin { do_a }
61
- spin { do_b }
62
- supervise(restart: :never, shutdown: :terminate)
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
- - Another option is to pass supervision parameters to `#spin`:
88
-
89
- ```ruby
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
- end
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
- ```ruby
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.33 Sinatra / Sidekiq
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.34 Testing && Docs
165
+ ## 0.37 Testing && Docs
141
166
 
142
167
  - Pull out redis/postgres code, put into new `polyphony-xxx` gems
143
168
 
144
- ## 0.35 Integration
169
+ ## 0.38 Integration
145
170
 
146
- ## 0.36 Real IO#gets and IO#read
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.37 Rails
181
+ ## 0.40 Rails
157
182
 
158
183
  - Rails?
159
184
 
160
- ## 0.37 DNS
185
+ ## 0.41 DNS
161
186
 
162
187
  ### DNS client
163
188
 
@@ -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])
@@ -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
- {%- assign children_list = site.html_pages | where: "parent", node.title | sort:"nav_order" -%}
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 %}">
@@ -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
- border-right: 4px #6ae solid;
40
+ margin-left: 0;
41
+ border-left: 4px #6ae solid;
39
42
  }
40
43
 
41
44
  a.navigation-list-link:hover {
@@ -0,0 +1,11 @@
1
+ ---
2
+ layout: page
3
+ title: API Reference
4
+ description: Lorem ipsum
5
+ has_children: true
6
+ alphabetical_order: true
7
+ section: true
8
+ has_toc: false
9
+ nav_order: 5
10
+ section_link: /api-reference/fiber
11
+ ---
@@ -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
+ ### #&lt;&lt;(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
+ ```