polyphony 0.34 → 0.36

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea4d90a517ed25294e677a9d3298ee2f4f79ad269e222c2a6e3a786fed605b50
4
- data.tar.gz: 19581d1dea73db08a6b9b0816fd4500c7ca84b2692150585133b7e3f99ee0522
3
+ metadata.gz: 1baba43b46fa48ea13ec33667390b5b6dafb8323d250f2df5e5ee6930e939680
4
+ data.tar.gz: 465c329841226252b1fe7fc3d48904b912323370083b4925d89531e45a4bd34d
5
5
  SHA512:
6
- metadata.gz: 36291ddf1dedbed0fbdcb347811e657bad19661981cacff2fa9736246e883fa2f56fa4362f266721177559e295adefe3f92c1797e4773dfc03b45857d2ebb3fb
7
- data.tar.gz: 8d9a5f0d368167a300e9926c9ad2d57221a423b60dc18d8a395c114feb18e3fa96546ac7709bf8acbabca500ec56d373bcd8f38479183c00077f0ccf513fb039
6
+ metadata.gz: d9b863d0231a5ccea65aa015d5a1d3432d0b513be1a60b2d20040bd06ed9d253c6be8391176ceba472300beac8059e27c334aca3bb6bafd51c3dabfd1e105301
7
+ data.tar.gz: 29eba3a894084f20d21bab00840cef76a5094f73aee4521947685883dc717811a274ed723deb72433cc0f358715549d79215286aad0ba62d14ee97308ee1415b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
+ ## 0.36 2020-03-31
2
+
3
+ * More docs
4
+ * More C code refactoring
5
+ * Fix freeing for active child, signal watchers
6
+
7
+ ## 0.35 2020-03-29
8
+
9
+ * Rename `Fiber#cancel!` to `Fiber#cancel`
10
+ * Rename `Gyro::Async#signal!` to `Gyro::Async#signal`
11
+ * Use `Fiber#auto_async` in thread pool, thread extension
12
+ * Implement `Fiber#auto_io` for reusing IO watcher instances
13
+ * Refactor C code
14
+
1
15
  ## 0.34 2020-03-25
2
16
 
17
+ * Add `Fiber#auto_async` mainly for use in places like `Gyro::Queue#shift`
18
+ * Refactor C extension
19
+ * Improved GC'ing for watchers
3
20
  * Implement process supervisor (`Polyphony::ProcessSupervisor`)
4
21
  * Improve fiber supervision
5
22
  * Fix forking behaviour
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.34)
4
+ polyphony (0.36)
5
5
  modulation (~> 1.0)
6
6
 
7
7
  GEM
data/TODO.md CHANGED
@@ -32,7 +32,7 @@
32
32
  Fiber.current.add_child_fiber(t.main_fiber)
33
33
  ```
34
34
 
35
- ## 0.35 Some more API work, more docs
35
+ ## 0.36 Some more API work, more docs
36
36
 
37
37
  - Debugging
38
38
  - Eat your own dogfood: need a good tool to check what's going on when some
@@ -152,7 +152,7 @@
152
152
  - Check why first call to `#sleep` returns too early in tests. Check the
153
153
  sleep behaviour in a spawned thread.
154
154
 
155
- ## 0.36 Sinatra / Sidekiq
155
+ ## 0.37 Sinatra / Sidekiq
156
156
 
157
157
  - sintra app with database access (postgresql)
158
158
 
@@ -162,13 +162,13 @@
162
162
  - test performance
163
163
  - proceed from there
164
164
 
165
- ## 0.37 Testing && Docs
165
+ ## 0.38 Testing && Docs
166
166
 
167
167
  - Pull out redis/postgres code, put into new `polyphony-xxx` gems
168
168
 
169
- ## 0.38 Integration
169
+ ## 0.39 Integration
170
170
 
171
- ## 0.39 Real IO#gets and IO#read
171
+ ## 0.40 Real IO#gets and IO#read
172
172
 
173
173
  - More tests
174
174
  - Implement some basic stuff missing:
@@ -178,11 +178,11 @@
178
178
  - `IO.foreach`
179
179
  - `Process.waitpid`
180
180
 
181
- ## 0.40 Rails
181
+ ## 0.41 Rails
182
182
 
183
183
  - Rails?
184
184
 
185
- ## 0.41 DNS
185
+ ## 0.42 DNS
186
186
 
187
187
  ### DNS client
188
188
 
@@ -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_async → 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_async
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
data/docs/index.md CHANGED
@@ -64,8 +64,8 @@ adapters are being developed.
64
64
  Polyphony draws inspiration from the following, in no particular order:
65
65
 
66
66
  * [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
67
+ [async](https://github.com/socketry/async) (Polyphony's C-extension code
68
+ started as a spinoff of
69
69
  [nio4r's](https://github.com/socketry/nio4r/tree/master/ext))
70
70
  * The [go scheduler](https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html)
71
71
  * [EventMachine](https://github.com/eventmachine/eventmachine)
@@ -73,11 +73,13 @@ Polyphony draws inspiration from the following, in no particular order:
73
73
  * [Erlang supervisors](http://erlang.org/doc/man/supervisor.html) (and actually,
74
74
  Erlang in general)
75
75
 
76
- ## Going further
76
+ ## Developer Resources
77
77
 
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.
78
+ * [Tutorial](getting-started/tutorial)
79
+ * [Main Concepts](main-concepts/concurrency/)
80
+ * [User Guide](user-guide/all-about-timers/)
81
+ * [API Reference](api-reference/exception/)
82
+ * [Examples](https://github.com/digital-fabric/polyphony/tree/9e0f3b09213156bdf376ef33684ef267517f06e8/examples/README.md)
81
83
 
82
84
  ## Contributing to Polyphony
83
85
 
@@ -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