polyphony 0.34 → 0.41

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +11 -2
  3. data/.gitignore +2 -2
  4. data/.rubocop.yml +30 -0
  5. data/CHANGELOG.md +34 -0
  6. data/Gemfile +0 -11
  7. data/Gemfile.lock +11 -10
  8. data/README.md +2 -1
  9. data/Rakefile +6 -2
  10. data/TODO.md +18 -95
  11. data/docs/_includes/head.html +40 -0
  12. data/docs/_includes/nav.html +5 -5
  13. data/docs/api-reference.md +1 -1
  14. data/docs/api-reference/fiber.md +18 -0
  15. data/docs/api-reference/gyro-async.md +57 -0
  16. data/docs/api-reference/gyro-child.md +29 -0
  17. data/docs/api-reference/gyro-queue.md +44 -0
  18. data/docs/api-reference/gyro-timer.md +51 -0
  19. data/docs/api-reference/gyro.md +25 -0
  20. data/docs/index.md +10 -7
  21. data/docs/main-concepts/design-principles.md +67 -9
  22. data/docs/main-concepts/extending.md +1 -1
  23. data/docs/main-concepts/fiber-scheduling.md +55 -72
  24. data/examples/core/xx-agent.rb +102 -0
  25. data/examples/core/xx-fork-cleanup.rb +22 -0
  26. data/examples/core/xx-sleeping.rb +14 -6
  27. data/examples/core/xx-timer-gc.rb +17 -0
  28. data/examples/io/tunnel.rb +48 -0
  29. data/examples/io/xx-irb.rb +1 -1
  30. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
  31. data/examples/performance/thread-vs-fiber/polyphony_server.rb +14 -25
  32. data/ext/{gyro → polyphony}/extconf.rb +2 -2
  33. data/ext/polyphony/fiber.c +112 -0
  34. data/ext/{gyro → polyphony}/libev.c +0 -0
  35. data/ext/{gyro → polyphony}/libev.h +0 -0
  36. data/ext/polyphony/libev_agent.c +503 -0
  37. data/ext/polyphony/libev_queue.c +214 -0
  38. data/ext/polyphony/polyphony.c +89 -0
  39. data/ext/{gyro/gyro.h → polyphony/polyphony.h} +49 -59
  40. data/ext/polyphony/polyphony_ext.c +23 -0
  41. data/ext/{gyro → polyphony}/socket.c +21 -19
  42. data/ext/{gyro → polyphony}/thread.c +55 -119
  43. data/ext/{gyro → polyphony}/tracing.c +1 -1
  44. data/lib/polyphony.rb +37 -44
  45. data/lib/polyphony/adapters/fs.rb +1 -4
  46. data/lib/polyphony/adapters/irb.rb +2 -2
  47. data/lib/polyphony/adapters/postgres.rb +6 -5
  48. data/lib/polyphony/adapters/process.rb +27 -23
  49. data/lib/polyphony/adapters/trace.rb +110 -105
  50. data/lib/polyphony/core/channel.rb +35 -35
  51. data/lib/polyphony/core/exceptions.rb +29 -29
  52. data/lib/polyphony/core/global_api.rb +94 -91
  53. data/lib/polyphony/core/resource_pool.rb +83 -83
  54. data/lib/polyphony/core/sync.rb +16 -16
  55. data/lib/polyphony/core/thread_pool.rb +49 -37
  56. data/lib/polyphony/core/throttler.rb +30 -23
  57. data/lib/polyphony/event.rb +27 -0
  58. data/lib/polyphony/extensions/core.rb +23 -14
  59. data/lib/polyphony/extensions/fiber.rb +269 -267
  60. data/lib/polyphony/extensions/io.rb +56 -26
  61. data/lib/polyphony/extensions/openssl.rb +5 -9
  62. data/lib/polyphony/extensions/socket.rb +29 -10
  63. data/lib/polyphony/extensions/thread.rb +19 -12
  64. data/lib/polyphony/net.rb +64 -60
  65. data/lib/polyphony/version.rb +1 -1
  66. data/polyphony.gemspec +3 -6
  67. data/test/helper.rb +14 -1
  68. data/test/stress.rb +17 -12
  69. data/test/test_agent.rb +77 -0
  70. data/test/{test_async.rb → test_event.rb} +17 -9
  71. data/test/test_ext.rb +25 -4
  72. data/test/test_fiber.rb +23 -14
  73. data/test/test_global_api.rb +5 -5
  74. data/test/test_io.rb +46 -24
  75. data/test/test_queue.rb +74 -0
  76. data/test/test_signal.rb +3 -40
  77. data/test/test_socket.rb +33 -0
  78. data/test/test_thread.rb +38 -16
  79. data/test/test_thread_pool.rb +3 -3
  80. data/test/test_throttler.rb +0 -1
  81. data/test/test_trace.rb +6 -5
  82. metadata +34 -39
  83. data/ext/gyro/async.c +0 -158
  84. data/ext/gyro/child.c +0 -117
  85. data/ext/gyro/gyro.c +0 -203
  86. data/ext/gyro/gyro_ext.c +0 -31
  87. data/ext/gyro/io.c +0 -447
  88. data/ext/gyro/queue.c +0 -142
  89. data/ext/gyro/selector.c +0 -183
  90. data/ext/gyro/signal.c +0 -108
  91. data/ext/gyro/timer.c +0 -154
  92. data/test/test_timer.rb +0 -56
@@ -7,5 +7,5 @@ alphabetical_order: true
7
7
  section: true
8
8
  has_toc: false
9
9
  nav_order: 5
10
- section_link: /api-reference/fiber
10
+ section_link: /api-reference/exception
11
11
  ---
@@ -71,6 +71,24 @@ f << 2
71
71
  result = receive #=> 20
72
72
  ```
73
73
 
74
+ ### #auto_watcher → async
75
+
76
+ Returns a reusable `Gyro::Async` watcher instance associated with the fiber.
77
+ This method provides a way to minimize watcher allocation. Instead of allocating
78
+ a new async watcher every time one is needed, the same watcher associated with
79
+ the fiber is reused.
80
+
81
+ ```ruby
82
+ def work(async)
83
+ do_something
84
+ async.signal
85
+ end
86
+
87
+ async = Fiber.current.auto_watcher
88
+ spin { work(async) }
89
+ async.await
90
+ ```
91
+
74
92
  ### #await → object<br>#join → object
75
93
 
76
94
  Awaits the termination of the fiber. If the fiber terminates with an uncaught
@@ -0,0 +1,57 @@
1
+ ---
2
+ layout: page
3
+ title: Gyro::Async
4
+ parent: API Reference
5
+ permalink: /api-reference/gyro-async/
6
+ ---
7
+ # Gyro::Async
8
+
9
+ `Gyro::Async` encapsulates a libev [async
10
+ watcher](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#code_ev_async_code_how_to_wake_up_an),
11
+ allowing thread-safe synchronisation and signalling. `Gyro::Async` watchers are
12
+ used both directly and indirectly in Polyphony to implement
13
+ [queues](../gyro-queue/), await fibers and threads, and auxiliary features such
14
+ as [thread pools](../polyphony-threadpool/).
15
+
16
+ A `Gyro::Async` watcher instance is shared across two or more fibers (across one
17
+ or more threads), where one fiber waits to be signalled by calling
18
+ `Gyro::Async#await`, and one or more other fibers do the signalling by calling
19
+ `Gyro::Async#signal`:
20
+
21
+ ```ruby
22
+ async = Gyro::Async.new
23
+ spin do
24
+ sleep 1
25
+ async.signal
26
+ end
27
+
28
+ async.await
29
+ ```
30
+
31
+ The signalling of async watchers is compressed, which means that multiple
32
+ invocations of `Gyro::Async#signal` before the event loop can continue will
33
+ result the watcher being signalled just a single time.
34
+
35
+ In addition to signalling, the async watcher can also be used to transfer an
36
+ arbitrary value to the awaitng fiber. See `#signal` for an example.
37
+
38
+ ## Instance methods
39
+
40
+ ### #await → object
41
+
42
+ Blocks the current thread until the watcher is signalled.
43
+
44
+ ### #initialize
45
+
46
+ Initializes the watcher instance.
47
+
48
+ ### #signal(value = nil) → async
49
+
50
+ Signals the watcher, causing the fiber awaiting the watcher to become runnable
51
+ and be eventually resumed with the given value.
52
+
53
+ ```ruby
54
+ async = Gyro::Async.new
55
+ spin { async.signal('foo') }
56
+ async.await #=> 'foo'
57
+ ```
@@ -0,0 +1,29 @@
1
+ ---
2
+ layout: page
3
+ title: Gyro::Child
4
+ parent: API Reference
5
+ permalink: /api-reference/gyro-child/
6
+ ---
7
+ # Gyro::Child
8
+
9
+ `Gyro::Child` encapsulates a libev [child
10
+ watcher](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#code_ev_child_code_watch_out_for_pro),
11
+ used for waiting for a child process to terminate. A `Gyro::Child` watcher
12
+ instance can be used for low-level control of child processes, instead of using
13
+ more high-level APIs such `Process.wait` etc.
14
+
15
+ ## Instance methods
16
+
17
+ ### #await → [pid, exitcode]
18
+
19
+ Blocks the current thread until the watcher is signalled. The return value is an
20
+ array containing the child's pid and the exit code.
21
+
22
+ ```ruby
23
+ pid = Polyphony.fork { sleep 1 }
24
+ Gyro::Child.new(pid).await #=> [pid, 0]
25
+ ```
26
+
27
+ ### #initialize(pid)
28
+
29
+ Initializes the watcher instance with the given pid
@@ -0,0 +1,44 @@
1
+ ---
2
+ layout: page
3
+ title: Gyro::Queue
4
+ parent: API Reference
5
+ permalink: /api-reference/gyro-queue/
6
+ ---
7
+ # Gyro::Queue
8
+
9
+ `Gyro::Queue` implements a polyphonic (fiber-aware) queue that can store 0 or
10
+ more items of any data types. Adding an item to the queue never blocks.
11
+ Retrieving an item from the queue will block if the queue is empty.
12
+ `Gyro::Queue` is both fiber-safe and thread-safe. This means multiple fibers
13
+ from multiple threads can concurrently interact with the same queue.
14
+ `Gyro::Queue` is used pervasively across the Polyphony code base for
15
+ synchronisation and fiber control.
16
+
17
+ ## Instance methods
18
+
19
+ ### #&lt;&lt;(object) → queue<br>#push(object) → queue
20
+
21
+ Adds an item to the queue.
22
+
23
+ ### #clear → queue
24
+
25
+ Removes all items currently in the queue.
26
+
27
+ ### #empty? → true or false
28
+
29
+ Returns true if the queue is empty. Otherwise returns false.
30
+
31
+ ### #initialize
32
+
33
+ Initializes an empty queue.
34
+
35
+ ### #shift → object<br>#pop → object
36
+
37
+ Retrieves an item from the queue. If the queue is empty, `#shift` blocks until
38
+ an item is added to the queue or until interrupted. Multiple fibers calling
39
+ `#shift` are served in a first-ordered first-served manner.
40
+
41
+ ### #shift_each → [*object]<br>#shift_each({ block }) → queue
42
+
43
+ Removes and returns all items currently in the queue. If a block is given, it
44
+ will be invoked for each item.
@@ -0,0 +1,51 @@
1
+ ---
2
+ layout: page
3
+ title: Gyro::Timer
4
+ parent: API Reference
5
+ permalink: /api-reference/gyro-timer/
6
+ ---
7
+ # Gyro::Timer
8
+
9
+ `Gyro::Timer` encapsulates a libev [timer
10
+ watcher](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#code_ev_timer_code_relative_and_opti),
11
+ allowing waiting a certain amount of time before proceeding with an operation.
12
+ Watchers can be either one-time timers or recurring timers. The Polyphony API
13
+ provides various APIs that use timer watchers for timeouts, throttled
14
+ operations, and sleeping.
15
+
16
+ ## Instance methods
17
+
18
+ ### #await → object
19
+
20
+ Blocks the current thread until the timer has elapsed. For recurrent timers,
21
+ `#await` will block until the next timer period has elapsed, as specified by the
22
+ `repeat` argument given to `#initialize`.
23
+
24
+ ### #initialize(after, repeat)
25
+
26
+ Initializes the watcher instance. The `after` argument gives the time duration
27
+ in seconds before the timer has elapsed. The `repeat` argument gives the time
28
+ period for recurring timers, or `0` for non-recurring timers.
29
+
30
+ ### #stop
31
+
32
+ Stops an active recurring timer. Recurring timers stay active (from the point of
33
+ view of the event loop) even after the timer period has elapsed. Calling `#stop`
34
+ marks the timer as inactive and cleans up associated resources. This should
35
+ normally be done inside an `ensure` block:
36
+
37
+ ```ruby
38
+ def repeat(period)
39
+ timer = Gyro::Timer.new(period, period)
40
+ loop do
41
+ timer.await
42
+ yield
43
+ end
44
+ ensure
45
+ timer.stop
46
+ end
47
+
48
+ repeat(10) { puts Time.now }
49
+ ```
50
+
51
+ There's no need to call `#stop` for non-recurring timers.
@@ -0,0 +1,25 @@
1
+ ---
2
+ layout: page
3
+ title: Gyro
4
+ parent: API Reference
5
+ permalink: /api-reference/gyro/
6
+ ---
7
+ # Gyro
8
+
9
+ `Gyro` is the subsystem in charge of the low-level functionality in Polyphony.
10
+ It contains all of the different event watcher classes, as well as other
11
+ low-level constructs such as `Gyro::Queue`, a fiber-aware queue implementation,
12
+ used pervasively across the Polyphony code base.
13
+
14
+ While most Polyphony-based applications do not normally need to interact
15
+ directly with the `Gyro` classes, more advanced applications and libraries may
16
+ use those classes to enhance Polyphony and create custom concurrency patterns.
17
+
18
+ ## Classes
19
+
20
+ - [`Gyro::Async`](../gyro-async/) - async event watcher
21
+ - [`Gyro::Child`](../gyro-child/) - child process event watcher
22
+ - [`Gyro::IO`](../gyro-io/) - IO event watcher
23
+ - [`Gyro::Queue`](../gyro-queue/) - fiber-aware queue
24
+ - [`Gyro::Signal`](../gyro-signal/) - signal event watcher
25
+ - [`Gyro::Timer`](../gyro-timer/) - timer event watcher
@@ -14,8 +14,9 @@ implements a comprehensive
14
14
  using [libev](https://github.com/enki/libev) as a high-performance event reactor
15
15
  for I/O, timers, and other asynchronous events.
16
16
 
17
- [FAQ](faq){: .btn .btn-green .text-gamma }
18
17
  [Take the tutorial](getting-started/tutorial){: .btn .btn-blue .text-gamma }
18
+ [Main Concepts](main-concepts/concurrency/){: .btn .btn-green .text-gamma }
19
+ [FAQ](faq){: .btn .btn-green .text-gamma }
19
20
  [Source code](https://github.com/digital-fabric/polyphony){: .btn .btn-purple .text-gamma target="_blank" }
20
21
  {: .mt-6 .h-align-center }
21
22
 
@@ -64,8 +65,8 @@ adapters are being developed.
64
65
  Polyphony draws inspiration from the following, in no particular order:
65
66
 
66
67
  * [nio4r](https://github.com/socketry/nio4r/) and
67
- [async](https://github.com/socketry/async) (Polyphony's C-extension code is
68
- largely a spinoff of
68
+ [async](https://github.com/socketry/async) (Polyphony's C-extension code
69
+ started as a spinoff of
69
70
  [nio4r's](https://github.com/socketry/nio4r/tree/master/ext))
70
71
  * The [go scheduler](https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html)
71
72
  * [EventMachine](https://github.com/eventmachine/eventmachine)
@@ -73,11 +74,13 @@ Polyphony draws inspiration from the following, in no particular order:
73
74
  * [Erlang supervisors](http://erlang.org/doc/man/supervisor.html) (and actually,
74
75
  Erlang in general)
75
76
 
76
- ## Going further
77
+ ## Developer Resources
77
78
 
78
- To learn more about using Polyphony to build concurrent applications, continue reading, or look at the [bundled
79
- examples](https://github.com/digital-fabric/polyphony/tree/9e0f3b09213156bdf376ef33684ef267517f06e8/examples/README.md).
80
- A thorough API reference is forthcoming.
79
+ * [Tutorial](getting-started/tutorial)
80
+ * [Main Concepts](main-concepts/concurrency/)
81
+ * [User Guide](user-guide/all-about-timers/)
82
+ * [API Reference](api-reference/exception/)
83
+ * [Examples](https://github.com/digital-fabric/polyphony/tree/9e0f3b09213156bdf376ef33684ef267517f06e8/examples/README.md)
81
84
 
82
85
  ## Contributing to Polyphony
83
86
 
@@ -1,18 +1,76 @@
1
1
  ---
2
2
  layout: page
3
- title: Design Principles
3
+ title: The Design of Polyphony
4
4
  nav_order: 5
5
5
  parent: Main Concepts
6
6
  permalink: /main-concepts/design-principles/
7
7
  prev_title: Extending Polyphony
8
8
  ---
9
- # Design Principles
9
+ # The Design of Polyphony
10
+
11
+ Polyphony is a new gem that aims to enable developing high-performance
12
+ concurrent applications in Ruby using a fluent, compact syntax and API.
13
+ Polyphony enables fine-grained concurrency - the splitting up of operations into
14
+ a large number of concurrent tasks, each concerned with small part of the whole
15
+ and advancing at its own pace. Polyphony aims to solve some of the problems
16
+ associated with concurrent Ruby programs using a novel design that sets it apart
17
+ from other approaches currently being used in Ruby.
18
+
19
+ ## Origins
20
+
21
+ The Ruby core language (at least in its MRI implementation) currently provides
22
+ two main constructs for performing concurrent work: threads and fibers. While
23
+ Ruby threads are basically wrappers for OS threads, fibers are essentially
24
+ continuations, allowing pausing and resuming distinct computations. Fibers have
25
+ been traditionally used mostly for implementing enumerators and generators.
26
+
27
+ In addition to the core Ruby concurrency primitives, some Ruby gems have been
28
+ offering an alternative solution to writing concurrent Ruby apps, most notably
29
+ [EventMachine](https://github.com/eventmachine/eventmachine/), which implements
30
+ an event reactor and offers an asynchronous callback-based API for writing
31
+ concurrent code.
32
+
33
+ In the last couple of years, however, fibers have been receiving more attention
34
+ as a possible constructs for writing concurrent programs. In particular, the
35
+ [Async](https://github.com/socketry/async) framework, created by [Samuel
36
+ Williams](https://github.com/ioquatix), offering a comprehensive set of
37
+ libraries, employs fibers in conjunction with an event reactor provided by the
38
+ [nio4r](https://github.com/socketry/nio4r) gem, which wraps the C
39
+ library [libev](http://software.schmorp.de/pkg/libev.html).
40
+
41
+ In addition, recently some effort was undertaken to provide a way to
42
+ [automatically switch between fibers](https://bugs.ruby-lang.org/issues/13618)
43
+ whenever a blocking operation is performed, or to [integrate a fiber
44
+ scheduler](https://bugs.ruby-lang.org/issues/16786) into the core Ruby code.
45
+
46
+ Nevertheless, while work is being done to harness fibers for providing a better
47
+ way to do concurrency in Ruby, fibers remain a mistery for most Ruby
48
+ programmers, a perplexing unfamiliar corner right at the heart of Ruby.
49
+
50
+ ## Design Principles
51
+
52
+ Polyphony started as an experiment, but over about two years of slow, jerky
53
+ evolution turned into something I'm really excited to share with the Ruby
54
+ community. Polyphony's design is both similar and different than the projects
55
+ mentioned above.
56
+
57
+ Polyphony today as nothing like the way it began. A careful examination of the
58
+ [CHANGELOG](https://github.com/digital-fabric/polyphony/blob/master/CHANGELOG.md)
59
+ would show how Polyphony explored not only different event reactor designs, but
60
+ also different API designs incorporating various concurrent paradigms such as
61
+ promises, async/await, fibers, and finally structured concurrency.
62
+
63
+ While Polyphony, like nio4r or EventMachine, uses an event reactor to turn
64
+ blocking operations into non-blocking ones, it completely embraces fibers and in
65
+ fact does not provide any callback-based APIs. Furthermore, Polyphony provides
66
+ fullblown fiber-aware implementations of blocking operations, such as
67
+ `read/write`, `sleep` or `waitpid`, instead of just event watching primitives.
68
+
69
+ Throughout the development process, it was my intention to create a programming
70
+ interface that would make highly-concurrent
71
+
72
+
10
73
 
11
- Polyphony was created in order to enable developing high-performance concurrent
12
- applications in Ruby using a fluent, compact syntax and API. Polyphony enables
13
- fine-grained concurrency - the splitting up of operations into a large number of
14
- concurrent tasks, each concerned with small part of the whole and advancing at
15
- its own pace.
16
74
 
17
75
 
18
76
 
@@ -46,8 +104,8 @@ library. Polyphony's design is based on the following principles:
46
104
  async callback-style APIs.
47
105
 
48
106
  ```ruby
49
- # in Polyphony, I/O ops block the current fiber, but implicitly yield to other
50
- # concurrent fibers:
107
+ # in Polyphony, I/O ops might block the current fiber, but implicitly yield to
108
+ # other concurrent fibers:
51
109
  clients.each { |client|
52
110
  spin { client.puts 'Elvis has left the chatroom' }
53
111
  }
@@ -5,7 +5,7 @@ nav_order: 4
5
5
  parent: Main Concepts
6
6
  permalink: /main-concepts/extending/
7
7
  prev_title: Exception Handling
8
- next_title: Design Principles
8
+ next_title: The Design of Polyphony
9
9
  ---
10
10
  # Extending Polyphony
11
11
 
@@ -15,9 +15,9 @@ switching between fibers works in Ruby.
15
15
 
16
16
  Ruby provides two mechanisms for transferring control between fibers:
17
17
  `Fiber#resume` /`Fiber.yield` and `Fiber#transfer`. The first is inherently
18
- asymmetric and is famously used for implementing generators and [resumable
18
+ asymmetric and is mostly used for implementing generators and [resumable
19
19
  enumerators](https://blog.appsignal.com/2018/11/27/ruby-magic-fibers-and-enumerators-in-ruby.html).
20
- Here's a small example:
20
+ Here's an example:
21
21
 
22
22
  ```ruby
23
23
  fib = Fiber.new do
@@ -31,7 +31,7 @@ end
31
31
  10.times { puts fib.resume }
32
32
  ```
33
33
 
34
- Another implication of using resume / yield is that the main fiber can't yield
34
+ An implication of using resume / yield is that the main fiber can't yield
35
35
  away, meaning we cannot pause the main fiber using `Fiber.yield`.
36
36
 
37
37
  The other fiber control mechanism, using `Fiber#transfer`, is fully symmetric:
@@ -45,46 +45,48 @@ ping.transfer
45
45
  ```
46
46
 
47
47
  `Fiber#transform` also allows using the main fiber as a general purpose
48
- resumable execution context. Polyphony uses `Fiber#transfer` exclusively for
49
- scheduling fibers.
48
+ resumable execution context. For that reason, Polyphony uses `Fiber#transfer`
49
+ exclusively for scheduling fibers. Normally, however, applications based on
50
+ Polyphony will not use this API directly.
50
51
 
51
52
  ## The Different Fiber states
52
53
 
53
- In Polyphony, each fiber has one four possible states:
54
+ In Polyphony, each fiber has one of four possible states:
54
55
 
55
- A new fiber will start in a `:waiting` state. The `#spin` global method will
56
- create the fiber and schedule it for execution, marking it as `:runnable`. When
57
- the fiber is run, it is in a `:running` state. Finally, when the fiber has
58
- terminated, it transitions to the `:dead` state.
59
-
60
- Whenever a fiber performs a blocking operation - such as waiting for a timer to
61
- elapse, or for a socket to become readable, or for a child process to terminate -
62
- it transitions to a `:waiting` state. Once the timer has elapsed, the socket
63
- has become readable, or the child process has terminated, the fiber is marked as
64
- `:runnable`. It then waits its turn to run.
56
+ - `:runnable` - a new fiber will start in the runnable state. This means it is
57
+ placed on the thread's run queue and is now waiting its turn to be resumed.
58
+ - `:running` - once the fiber is resumed, it transitions to the running state.
59
+ `Fiber.current.state` always returns `:running`.
60
+ - `:wait` - whenever the fiber performs a blocking operation—such as waiting for
61
+ a timer to elapse, or for a socket to become readable—the fiber transitions to
62
+ a waiting state. When the corresponding event occurs the fiber will transition
63
+ to a `:runnable` state, and will be eventually resumed (`:running`).
64
+ - `:dead` - once the fiber has terminated, it transitions to the dead state.
65
65
 
66
66
  ## Switchpoints
67
67
 
68
- A switchpoint is any point in time at which control might switch from the
68
+ A switchpoint is any point in time at which control *might* switch from the
69
69
  currently running fiber to another fiber that is `:runnable`. This usually
70
- occurs when the currently running fiber starts a blocking operation. It also
71
- occurs when the running fiber has yielded control using `#snooze` or `#suspend`.
72
- A Switchpoint will also occur when the currently running fiber has terminated.
70
+ occurs when the currently running fiber starts a blocking operation, such as
71
+ reading from a socket or waiting for a timer. It also occurs when the running
72
+ fiber has explicitly yielded control using `#snooze` or `#suspend`. A
73
+ Switchpoint will also occur when the currently running fiber has terminated.
73
74
 
74
75
  ## Scheduler-less scheduling
75
76
 
76
77
  Polyphony relies on [libev](http://software.schmorp.de/pkg/libev.html) for
77
78
  handling events such as I/O readiness, timers and signals. In most event
78
79
  reactor-based libraries and frameworks, such as `nio4r`, `EventMachine` or
79
- `node.js`, the reactor loop is run, and event callbacks are used to schedule
80
- user-supplied code *from inside the loop*. In Polyphony, however, we have chosen
81
- a programming model that does not use a loop to schedule fibers. In fact, in
82
- Polyphony there's no such thing as a reactor loop, and there's no *scheduler*
83
- per se running on a separate execution context.
80
+ `node.js`, the entire application is run inside of a reactor loop, and event
81
+ callbacks are used to schedule user-supplied code *from inside the loop*.
82
+
83
+ In Polyphony, however, we have chosen a concurrency model that does not use a
84
+ loop to schedule fibers. In fact, in Polyphony there's no outer reactor loop,
85
+ and there's no *scheduler* per se running on a separate execution context.
84
86
 
85
87
  Instead, Polyphony maintains for each thread a run queue, a list of `:runnable`
86
- fibers. If no fiber is `:runnable`, the libev event reactor will be ran until
87
- one or more events have occurred. Events are handled by adding the corresponding
88
+ fibers. If no fiber is `:runnable`, Polyphony will run the libev event loop until
89
+ at least one event has occurred. Events are handled by adding the corresponding
88
90
  fibers onto the run queue. Finally, control is transferred to the first fiber on
89
91
  the run queue, which will run until it blocks or terminates, at which point
90
92
  control is transferred to the next runnable fiber.
@@ -95,8 +97,8 @@ This approach has numerous benefits:
95
97
  leading to less context switches, and less bookkeeping.
96
98
  - Clear separation between the reactor code (the `libev` code) and the fiber
97
99
  scheduling code.
98
- - Much less time is spent in reactor loop callbacks, letting the reactor loop
99
- run more efficiently.
100
+ - Much less time is spent in event loop callbacks, letting the event loop run
101
+ more efficiently.
100
102
  - Fibers are switched outside of the event reactor code, making it easier to
101
103
  avoid race conditions and unexpected behaviours.
102
104
 
@@ -121,9 +123,9 @@ are waiting and the main fiber is done running, the Ruby process will terminate.
121
123
  ## Interrupting blocking operations
122
124
 
123
125
  Sometimes it is desirable to be able to interrupt a blocking operation, such as
124
- waiting for a socket to be readable, or sleeping for an extended period of time.
125
- This is especially useful when higher-level constructs are needed for
126
- controlling multiple concurrent operations.
126
+ waiting for a socket to be readable, or sleeping. This is especially useful when
127
+ higher-level constructs are needed for controlling multiple concurrent
128
+ operations.
127
129
 
128
130
  Polyphony provides the ability to interrupt a blocking operation by harnessing
129
131
  the ability to transfer values back and forth between fibers using
@@ -134,52 +136,24 @@ signalling that the blocking operation has been unsuccessful and allowing
134
136
  exception handling using the builtin mechanisms offered by Ruby, namely `rescue`
135
137
  and `ensure` (see also [exception handling](exception-handling.md)).
136
138
 
137
- Here's a naive implementation of a yielding I/O read operation in Polyphony (the
138
- actual code for I/O reading in Polyphony is written in C and is a bit more
139
- involved):
139
+ This mode of operation makes implementing timeouts almost trivial:
140
140
 
141
141
  ```ruby
142
- def read_from(io)
143
- loop do
144
- result = IO.readnonblock(8192, exception: false)
145
- if result == :wait_readable
146
- wait_readable(io)
147
- else
148
- return result
149
- end
142
+ def with_timeout(duration)
143
+ interruptible_fiber = Fiber.current
144
+ timeout_fiber = spin do
145
+ sleep duration
146
+ interruptible_fiber.raise 'timeout'
150
147
  end
151
- end
152
-
153
- def wait_readable(io)
154
- fiber = Fiber.current
155
- watcher = Gyro::IO.new(io, :read) { fiber.transfer }
156
-
157
- # run any scheduled fibers or run libev reactor waiting for events
158
- result = GV.run
159
-
160
- # waiting fiber is resumed - check transferred value
161
- raise result if result.is_a?(Exception)
162
- result
148
+
149
+ # do work
150
+ yield
163
151
  ensure
164
- # ensure the I/O watcher is deactivated, even if exception is raised
165
- watcher.active = false
152
+ timeout_fiber.terminate
166
153
  end
167
- ```
168
154
 
169
- In the above example, the `wait_readable` method will normally wait indefinitely
170
- until the IO object has become readable. But we could interrupt it at any time
171
- by scheduling the corresponding fiber with an exception:
172
-
173
- ```ruby
174
- def timeout(duration)
175
- fiber = Fiber.current
176
- interrupter = spin do
177
- Gyro::Timer.new(duration, 0).await
178
- fiber.transfer(TimerException.new)
179
- end
180
- yield
181
- ensure
182
- interrupter.stop
155
+ with_timeout(10) do
156
+ HTTParty.get 'https://acme.com/'
183
157
  end
184
158
  ```
185
159
 
@@ -187,6 +161,15 @@ end
187
161
 
188
162
  Polyphony performs fiber scheduling separately for each thread. Each thread,
189
163
  therefore, will be able to run multiple fibers independently from other threads.
164
+ Multithreading in Ruby has limited benefit, due to the global virtual lock that
165
+ prevents true parallelism. But offloading work to a separate thread might be
166
+ eneficial when a Polyphonic app needs to use APIs that are not fiber-aware, such
167
+ as blocking database calls (SQLite in particular), or system calls that might
168
+ block for an extended duration.
169
+
170
+ For this, you can either spawn a new thread, or use the provided
171
+ `Polyphony::ThreadPool` class that allows you to offload work to a pool of
172
+ threads.
190
173
 
191
174
  ## The fiber scheduling algorithm in full
192
175