polyphony 0.33 → 0.34

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