polyphony 0.43.8

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 (221) hide show
  1. checksums.yaml +7 -0
  2. data/.gitbook.yaml +4 -0
  3. data/.github/workflows/test.yml +29 -0
  4. data/.gitignore +59 -0
  5. data/.rubocop.yml +175 -0
  6. data/CHANGELOG.md +393 -0
  7. data/Gemfile +3 -0
  8. data/Gemfile.lock +141 -0
  9. data/LICENSE +21 -0
  10. data/README.md +51 -0
  11. data/Rakefile +26 -0
  12. data/TODO.md +201 -0
  13. data/bin/polyphony-debug +87 -0
  14. data/docs/_config.yml +64 -0
  15. data/docs/_includes/head.html +40 -0
  16. data/docs/_includes/title.html +1 -0
  17. data/docs/_sass/custom/custom.scss +10 -0
  18. data/docs/_sass/overrides.scss +0 -0
  19. data/docs/_user-guide/all-about-timers.md +126 -0
  20. data/docs/_user-guide/index.md +9 -0
  21. data/docs/_user-guide/web-server.md +136 -0
  22. data/docs/api-reference/exception.md +27 -0
  23. data/docs/api-reference/fiber.md +425 -0
  24. data/docs/api-reference/index.md +9 -0
  25. data/docs/api-reference/io.md +36 -0
  26. data/docs/api-reference/object.md +99 -0
  27. data/docs/api-reference/polyphony-baseexception.md +33 -0
  28. data/docs/api-reference/polyphony-cancel.md +26 -0
  29. data/docs/api-reference/polyphony-moveon.md +24 -0
  30. data/docs/api-reference/polyphony-net.md +20 -0
  31. data/docs/api-reference/polyphony-process.md +28 -0
  32. data/docs/api-reference/polyphony-resourcepool.md +59 -0
  33. data/docs/api-reference/polyphony-restart.md +18 -0
  34. data/docs/api-reference/polyphony-terminate.md +18 -0
  35. data/docs/api-reference/polyphony-threadpool.md +67 -0
  36. data/docs/api-reference/polyphony-throttler.md +77 -0
  37. data/docs/api-reference/polyphony.md +36 -0
  38. data/docs/api-reference/thread.md +88 -0
  39. data/docs/assets/img/echo-fibers.svg +1 -0
  40. data/docs/assets/img/sleeping-fiber.svg +1 -0
  41. data/docs/faq.md +195 -0
  42. data/docs/favicon.ico +0 -0
  43. data/docs/getting-started/index.md +10 -0
  44. data/docs/getting-started/installing.md +34 -0
  45. data/docs/getting-started/overview.md +486 -0
  46. data/docs/getting-started/tutorial.md +359 -0
  47. data/docs/index.md +94 -0
  48. data/docs/main-concepts/concurrency.md +151 -0
  49. data/docs/main-concepts/design-principles.md +161 -0
  50. data/docs/main-concepts/exception-handling.md +291 -0
  51. data/docs/main-concepts/extending.md +89 -0
  52. data/docs/main-concepts/fiber-scheduling.md +197 -0
  53. data/docs/main-concepts/index.md +9 -0
  54. data/docs/polyphony-logo.png +0 -0
  55. data/examples/adapters/concurrent-ruby.rb +9 -0
  56. data/examples/adapters/pg_client.rb +36 -0
  57. data/examples/adapters/pg_notify.rb +35 -0
  58. data/examples/adapters/pg_pool.rb +43 -0
  59. data/examples/adapters/pg_transaction.rb +31 -0
  60. data/examples/adapters/redis_blpop.rb +12 -0
  61. data/examples/adapters/redis_channels.rb +122 -0
  62. data/examples/adapters/redis_client.rb +19 -0
  63. data/examples/adapters/redis_pubsub.rb +26 -0
  64. data/examples/adapters/redis_pubsub_perf.rb +68 -0
  65. data/examples/core/01-spinning-up-fibers.rb +18 -0
  66. data/examples/core/02-awaiting-fibers.rb +20 -0
  67. data/examples/core/03-interrupting.rb +39 -0
  68. data/examples/core/04-handling-signals.rb +19 -0
  69. data/examples/core/xx-agent.rb +102 -0
  70. data/examples/core/xx-at_exit.rb +29 -0
  71. data/examples/core/xx-caller.rb +12 -0
  72. data/examples/core/xx-channels.rb +45 -0
  73. data/examples/core/xx-daemon.rb +14 -0
  74. data/examples/core/xx-deadlock.rb +8 -0
  75. data/examples/core/xx-deferring-an-operation.rb +14 -0
  76. data/examples/core/xx-erlang-style-genserver.rb +81 -0
  77. data/examples/core/xx-exception-backtrace.rb +40 -0
  78. data/examples/core/xx-fork-cleanup.rb +22 -0
  79. data/examples/core/xx-fork-spin.rb +42 -0
  80. data/examples/core/xx-fork-terminate.rb +27 -0
  81. data/examples/core/xx-forking.rb +24 -0
  82. data/examples/core/xx-move_on.rb +23 -0
  83. data/examples/core/xx-pingpong.rb +18 -0
  84. data/examples/core/xx-queue-async.rb +120 -0
  85. data/examples/core/xx-readpartial.rb +18 -0
  86. data/examples/core/xx-recurrent-timer.rb +12 -0
  87. data/examples/core/xx-resource_delegate.rb +31 -0
  88. data/examples/core/xx-signals.rb +16 -0
  89. data/examples/core/xx-sleep-forever.rb +9 -0
  90. data/examples/core/xx-sleeping.rb +25 -0
  91. data/examples/core/xx-snooze-starve.rb +16 -0
  92. data/examples/core/xx-spin-fork.rb +49 -0
  93. data/examples/core/xx-spin_error_backtrace.rb +33 -0
  94. data/examples/core/xx-state-machine.rb +51 -0
  95. data/examples/core/xx-stop.rb +20 -0
  96. data/examples/core/xx-supervise-process.rb +30 -0
  97. data/examples/core/xx-supervisors.rb +21 -0
  98. data/examples/core/xx-thread-selector-sleep.rb +51 -0
  99. data/examples/core/xx-thread-selector-snooze.rb +46 -0
  100. data/examples/core/xx-thread-sleep.rb +17 -0
  101. data/examples/core/xx-thread-snooze.rb +34 -0
  102. data/examples/core/xx-thread_pool.rb +17 -0
  103. data/examples/core/xx-throttling.rb +18 -0
  104. data/examples/core/xx-timeout.rb +10 -0
  105. data/examples/core/xx-timer-gc.rb +17 -0
  106. data/examples/core/xx-trace.rb +79 -0
  107. data/examples/core/xx-using-a-mutex.rb +21 -0
  108. data/examples/core/xx-worker-thread.rb +30 -0
  109. data/examples/io/tunnel.rb +48 -0
  110. data/examples/io/xx-backticks.rb +11 -0
  111. data/examples/io/xx-echo_client.rb +25 -0
  112. data/examples/io/xx-echo_client_from_stdin.rb +21 -0
  113. data/examples/io/xx-echo_pipe.rb +16 -0
  114. data/examples/io/xx-echo_server.rb +17 -0
  115. data/examples/io/xx-echo_server_with_timeout.rb +34 -0
  116. data/examples/io/xx-echo_stdin.rb +14 -0
  117. data/examples/io/xx-happy-eyeballs.rb +36 -0
  118. data/examples/io/xx-httparty.rb +38 -0
  119. data/examples/io/xx-irb.rb +17 -0
  120. data/examples/io/xx-net-http.rb +15 -0
  121. data/examples/io/xx-open.rb +16 -0
  122. data/examples/io/xx-switch.rb +15 -0
  123. data/examples/io/xx-system.rb +11 -0
  124. data/examples/io/xx-tcpserver.rb +15 -0
  125. data/examples/io/xx-tcpsocket.rb +18 -0
  126. data/examples/io/xx-zip.rb +19 -0
  127. data/examples/performance/fiber_transfer.rb +47 -0
  128. data/examples/performance/fs_read.rb +38 -0
  129. data/examples/performance/mem-usage.rb +56 -0
  130. data/examples/performance/messaging.rb +29 -0
  131. data/examples/performance/multi_snooze.rb +33 -0
  132. data/examples/performance/snooze.rb +39 -0
  133. data/examples/performance/snooze_raw.rb +39 -0
  134. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +74 -0
  135. data/examples/performance/thread-vs-fiber/polyphony_server.rb +45 -0
  136. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
  137. data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
  138. data/examples/performance/thread-vs-fiber/xx-httparty_multi.rb +36 -0
  139. data/examples/performance/thread-vs-fiber/xx-httparty_threaded.rb +29 -0
  140. data/examples/performance/thread_pool_perf.rb +63 -0
  141. data/examples/performance/xx-array.rb +11 -0
  142. data/examples/performance/xx-fiber-switch.rb +9 -0
  143. data/examples/performance/xx-snooze.rb +15 -0
  144. data/examples/xx-spin.rb +32 -0
  145. data/ext/libev/Changes +548 -0
  146. data/ext/libev/LICENSE +37 -0
  147. data/ext/libev/README +59 -0
  148. data/ext/libev/README.embed +3 -0
  149. data/ext/libev/ev.c +5279 -0
  150. data/ext/libev/ev.h +856 -0
  151. data/ext/libev/ev_epoll.c +296 -0
  152. data/ext/libev/ev_kqueue.c +224 -0
  153. data/ext/libev/ev_linuxaio.c +642 -0
  154. data/ext/libev/ev_poll.c +156 -0
  155. data/ext/libev/ev_port.c +192 -0
  156. data/ext/libev/ev_select.c +316 -0
  157. data/ext/libev/ev_vars.h +215 -0
  158. data/ext/libev/ev_win32.c +162 -0
  159. data/ext/libev/ev_wrap.h +216 -0
  160. data/ext/libev/test_libev_win32.c +123 -0
  161. data/ext/polyphony/extconf.rb +20 -0
  162. data/ext/polyphony/fiber.c +109 -0
  163. data/ext/polyphony/libev.c +2 -0
  164. data/ext/polyphony/libev.h +9 -0
  165. data/ext/polyphony/libev_agent.c +882 -0
  166. data/ext/polyphony/polyphony.c +71 -0
  167. data/ext/polyphony/polyphony.h +97 -0
  168. data/ext/polyphony/polyphony_ext.c +21 -0
  169. data/ext/polyphony/queue.c +168 -0
  170. data/ext/polyphony/ring_buffer.c +96 -0
  171. data/ext/polyphony/ring_buffer.h +28 -0
  172. data/ext/polyphony/thread.c +208 -0
  173. data/ext/polyphony/tracing.c +11 -0
  174. data/lib/polyphony.rb +136 -0
  175. data/lib/polyphony/adapters/fs.rb +19 -0
  176. data/lib/polyphony/adapters/irb.rb +52 -0
  177. data/lib/polyphony/adapters/postgres.rb +110 -0
  178. data/lib/polyphony/adapters/process.rb +33 -0
  179. data/lib/polyphony/adapters/redis.rb +67 -0
  180. data/lib/polyphony/adapters/trace.rb +138 -0
  181. data/lib/polyphony/core/channel.rb +46 -0
  182. data/lib/polyphony/core/exceptions.rb +36 -0
  183. data/lib/polyphony/core/global_api.rb +124 -0
  184. data/lib/polyphony/core/resource_pool.rb +117 -0
  185. data/lib/polyphony/core/sync.rb +21 -0
  186. data/lib/polyphony/core/thread_pool.rb +64 -0
  187. data/lib/polyphony/core/throttler.rb +41 -0
  188. data/lib/polyphony/event.rb +17 -0
  189. data/lib/polyphony/extensions/core.rb +174 -0
  190. data/lib/polyphony/extensions/fiber.rb +379 -0
  191. data/lib/polyphony/extensions/io.rb +221 -0
  192. data/lib/polyphony/extensions/openssl.rb +81 -0
  193. data/lib/polyphony/extensions/socket.rb +150 -0
  194. data/lib/polyphony/extensions/thread.rb +108 -0
  195. data/lib/polyphony/net.rb +77 -0
  196. data/lib/polyphony/version.rb +5 -0
  197. data/polyphony.gemspec +40 -0
  198. data/test/coverage.rb +54 -0
  199. data/test/eg.rb +27 -0
  200. data/test/helper.rb +56 -0
  201. data/test/q.rb +24 -0
  202. data/test/run.rb +5 -0
  203. data/test/stress.rb +25 -0
  204. data/test/test_agent.rb +130 -0
  205. data/test/test_event.rb +59 -0
  206. data/test/test_ext.rb +196 -0
  207. data/test/test_fiber.rb +988 -0
  208. data/test/test_global_api.rb +352 -0
  209. data/test/test_io.rb +249 -0
  210. data/test/test_kernel.rb +57 -0
  211. data/test/test_process_supervision.rb +46 -0
  212. data/test/test_queue.rb +112 -0
  213. data/test/test_resource_pool.rb +138 -0
  214. data/test/test_signal.rb +100 -0
  215. data/test/test_socket.rb +34 -0
  216. data/test/test_supervise.rb +103 -0
  217. data/test/test_thread.rb +170 -0
  218. data/test/test_thread_pool.rb +101 -0
  219. data/test/test_throttler.rb +50 -0
  220. data/test/test_trace.rb +68 -0
  221. metadata +482 -0
@@ -0,0 +1,89 @@
1
+ ---
2
+ layout: page
3
+ title: Extending Polyphony
4
+ nav_order: 4
5
+ parent: Main Concepts
6
+ permalink: /main-concepts/extending/
7
+ prev_title: Exception Handling
8
+ next_title: The Design of Polyphony
9
+ ---
10
+ # Extending Polyphony
11
+
12
+ Polyphony was designed to ease the transition from blocking APIs and
13
+ callback-based API to non-blocking, fiber-based ones. It is important to
14
+ understand that not all blocking calls can be easily converted into
15
+ non-blocking calls. That might be the case with Ruby gems based on C-extensions,
16
+ such as database libraries. In that case, Polyphony's built-in
17
+ [thread pool](#threadpool) might be used for offloading such blocking calls.
18
+
19
+ ### Adapting callback-based APIs
20
+
21
+ Some of the most common patterns in Ruby APIs is the callback pattern, in which
22
+ the API takes a block as a callback to be called upon completion of a task. One
23
+ such example can be found in the excellent
24
+ [http_parser.rb](https://github.com/tmm1/http_parser.rb/) gem, which is used by
25
+ Polyphony itself to provide HTTP 1 functionality. The `HTTP:Parser` provides
26
+ multiple hooks, or callbacks, for being notified when an HTTP request is
27
+ complete. The typical callback-based setup is as follows:
28
+
29
+ ```ruby
30
+ require 'http/parser'
31
+ @parser = Http::Parser.new
32
+
33
+ def on_receive(data)
34
+ @parser < data
35
+ end
36
+
37
+ @parser.on_message_complete do |env|
38
+ process_request(env)
39
+ end
40
+ ```
41
+
42
+ A program using `http_parser.rb` in conjunction with Polyphony might do the
43
+ following:
44
+
45
+ ```ruby
46
+ require 'http/parser'
47
+ require 'polyphony'
48
+
49
+ def handle_client(client)
50
+ parser = Http::Parser.new
51
+ req = nil
52
+ parser.on_message_complete { |env| req = env }
53
+ loop do
54
+ parser << client.read
55
+ if req
56
+ handle_request(req)
57
+ req = nil
58
+ end
59
+ end
60
+ end
61
+ ```
62
+
63
+ Another possibility would be to monkey-patch `Http::Parser` in order to
64
+ encapsulate the state of the request:
65
+
66
+ ```ruby
67
+ class Http::Parser
68
+ def setup
69
+ self.on_message_complete = proc { @request_complete = true }
70
+ end
71
+
72
+ def parser(data)
73
+ self << data
74
+ return nil unless @request_complete
75
+
76
+ @request_complete = nil
77
+ self
78
+ end
79
+ end
80
+
81
+ def handle_client(client)
82
+ parser = Http::Parser.new
83
+ loop do
84
+ if req == parser.parse(client.read)
85
+ handle_request(req)
86
+ end
87
+ end
88
+ end
89
+ ```
@@ -0,0 +1,197 @@
1
+ ---
2
+ layout: page
3
+ title: How Fibers are Scheduled
4
+ nav_order: 2
5
+ parent: Main Concepts
6
+ permalink: /main-concepts/fiber-scheduling/
7
+ prev_title: Concurrency the Easy Way
8
+ next_title: Exception Handling
9
+ ---
10
+
11
+ # How Fibers are Scheduled
12
+
13
+ Before we discuss how fibers are scheduled in Polyphony, let's examine how
14
+ switching between fibers works in Ruby.
15
+
16
+ Ruby provides two mechanisms for transferring control between fibers:
17
+ `Fiber#resume` /`Fiber.yield` and `Fiber#transfer`. The first is inherently
18
+ asymmetric and is mostly used for implementing generators and [resumable
19
+ enumerators](https://blog.appsignal.com/2018/11/27/ruby-magic-fibers-and-enumerators-in-ruby.html).
20
+ Here's an example:
21
+
22
+ ```ruby
23
+ fib = Fiber.new do
24
+ x, y = 0, 1
25
+ loop do
26
+ Fiber.yield y
27
+ x, y = y, x + y
28
+ end
29
+ end
30
+
31
+ 10.times { puts fib.resume }
32
+ ```
33
+
34
+ An implication of using resume / yield is that the main fiber can't yield
35
+ away, meaning we cannot pause the main fiber using `Fiber.yield`.
36
+
37
+ The other fiber control mechanism, using `Fiber#transfer`, is fully symmetric:
38
+
39
+ ```ruby
40
+ require 'fiber'
41
+
42
+ ping = Fiber.new { loop { puts "ping"; pong.transfer } }
43
+ pong = Fiber.new { loop { puts "pong"; ping.transfer } }
44
+ ping.transfer
45
+ ```
46
+
47
+ `Fiber#transfer` also allows using the main fiber as a general purpose
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.
51
+
52
+ ## The Different Fiber states
53
+
54
+ In Polyphony, each fiber has one of four possible states:
55
+
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
+
66
+ ## Switchpoints
67
+
68
+ A switchpoint is any point in time at which control *might* switch from the
69
+ currently running fiber to another fiber that is `:runnable`. This usually
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.
74
+
75
+ ## Scheduler-less scheduling
76
+
77
+ Polyphony relies on [libev](http://software.schmorp.de/pkg/libev.html) for
78
+ handling events such as I/O readiness, timers and signals. In most event
79
+ reactor-based libraries and frameworks, such as `nio4r`, `EventMachine` or
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.
86
+
87
+ Instead, Polyphony maintains for each thread a run queue, a list of `:runnable`
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
90
+ fibers onto the run queue. Finally, control is transferred to the first fiber on
91
+ the run queue, which will run until it blocks or terminates, at which point
92
+ control is transferred to the next runnable fiber.
93
+
94
+ This approach has numerous benefits:
95
+
96
+ - No separate reactor fiber that needs to be resumed on each blocking operation,
97
+ leading to less context switches, and less bookkeeping.
98
+ - Clear separation between the reactor code (the `libev` code) and the fiber
99
+ scheduling code.
100
+ - Much less time is spent in event loop callbacks, letting the event loop run
101
+ more efficiently.
102
+ - Fibers are switched outside of the event reactor code, making it easier to
103
+ avoid race conditions and unexpected behaviours.
104
+
105
+ ## Fiber scheduling and fiber switching
106
+
107
+ The Polyphony scheduling model makes a clear separation between the scheduling
108
+ of fibers and the switching of fibers. The scheduling of fibers is the act of
109
+ marking the fiber as `:runnable`, to be run at the earliest opportunity, but not
110
+ immediately. The switching of fibers is the act of actually transferring control
111
+ to another fiber, namely the first fiber in the run queue.
112
+
113
+ The scheduling of fibers can occur at any time, either as a result of an event
114
+ occuring, an exception being raised, or using `Fiber#schedule`. The switching of
115
+ fibers will occur only when the currently running fiber has reached a
116
+ switchpoint, e.g. when a blocking operation is started, or upon calling
117
+ `Fiber#suspend` or `Fiber#snooze`. As mentioned earlier, in order to switch to a
118
+ scheduled fiber, Polyphony uses `Fiber#transfer`.
119
+
120
+ When a fiber terminates, any other runnable fibers will be run. If no fibers
121
+ are waiting and the main fiber is done running, the Ruby process will terminate.
122
+
123
+ ## Interrupting blocking operations
124
+
125
+ Sometimes it is desirable to be able to interrupt a blocking operation, such as
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.
129
+
130
+ Polyphony provides the ability to interrupt a blocking operation by harnessing
131
+ the ability to transfer values back and forth between fibers using
132
+ `Fiber#transfer`. Whenever a waiting fiber yields control to the next scheduled
133
+ fiber, the value received upon being resumed is checked. If the value is an
134
+ exception, it will be raised in the context of the waiting fiber, effectively
135
+ signalling that the blocking operation has been unsuccessful and allowing
136
+ exception handling using the builtin mechanisms offered by Ruby, namely `rescue`
137
+ and `ensure` (see also [exception handling](exception-handling.md)).
138
+
139
+ This mode of operation makes implementing timeouts almost trivial:
140
+
141
+ ```ruby
142
+ def with_timeout(duration)
143
+ interruptible_fiber = Fiber.current
144
+ timeout_fiber = spin do
145
+ sleep duration
146
+ interruptible_fiber.raise 'timeout'
147
+ end
148
+
149
+ # do work
150
+ yield
151
+ ensure
152
+ timeout_fiber.terminate
153
+ end
154
+
155
+ with_timeout(10) do
156
+ HTTParty.get 'https://acme.com/'
157
+ end
158
+ ```
159
+
160
+ ## Fiber Scheduling in a Multithreaded Program
161
+
162
+ Polyphony performs fiber scheduling separately for each thread. Each thread,
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.
173
+
174
+ ## The fiber scheduling algorithm in full
175
+
176
+ Here is the summary of the Polyphony scheduling algorithm:
177
+
178
+ - loop
179
+ - pull first runnable fiber from run queue
180
+ - if runnable fiber is not nil
181
+ - if the ref count greater than 0
182
+ - increment the run_no_wait counter
183
+ - if the run_no_wait counter is greater than 10 and greater than the run
184
+ queue length
185
+ - reset the run_no_wait counter
186
+ - run the event loop once without waiting for events (using
187
+ `EVRUN_NOWAIT`)
188
+ - break out of the loop
189
+ - if the ref count is 0
190
+ - break out of the loop
191
+ - run the event loop until one or more events are generated (using
192
+ `EVRUN_ONCE`)
193
+ - if next runnable fiber is nil
194
+ - return
195
+ - get scheduled resume value for next runnable fiber
196
+ - mark the next runnable fiber as not runnable
197
+ - switch to the next runnable fiber using `Fiber#transfer`
@@ -0,0 +1,9 @@
1
+ ---
2
+ layout: page
3
+ title: Main Concepts
4
+ has_children: true
5
+ nav_order: 3
6
+ ---
7
+
8
+ # Main Concepts
9
+ {: .no_toc }
Binary file
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'concurrent'
6
+
7
+ puts "Hello, concurrent-ruby"
8
+
9
+ # this program should not hang with concurrent-ruby 1.1.6 (see issue #22)
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/adapters/postgres'
5
+
6
+ def get_records
7
+ $db.query('select 1 as test')
8
+ # puts "got #{res.ntuples} records: #{res.to_a}"
9
+ rescue StandardError => e
10
+ puts "got error: #{e.inspect}"
11
+ puts e.backtrace.join("\n")
12
+ end
13
+
14
+ time_printer = spin do
15
+ last = Time.now
16
+ throttled_loop(10) do
17
+ now = Time.now
18
+ puts now - last
19
+ last = now
20
+ end
21
+ end
22
+
23
+ $db = PG.connect(
24
+ host: '/tmp',
25
+ user: 'reality',
26
+ password: nil,
27
+ dbname: 'reality',
28
+ sslmode: 'require'
29
+ )
30
+
31
+ X = 10_000
32
+ t0 = Time.now
33
+ X.times { get_records }
34
+ puts "query rate: #{X / (Time.now - t0)} reqs/s"
35
+
36
+ time_printer.stop
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/adapters/postgres'
5
+
6
+ opts = {
7
+ host: '/tmp',
8
+ user: 'reality',
9
+ password: nil,
10
+ dbname: 'reality',
11
+ sslmode: 'require'
12
+ }
13
+
14
+ db1 = PG.connect(opts)
15
+ db2 = PG.connect(opts)
16
+
17
+ spin_loop {
18
+ STDOUT << '.'
19
+ sleep 0.1
20
+ }
21
+
22
+ db1.query('listen foo')
23
+ spin_loop {
24
+ db1.wait_for_notify(1) { |channel, _, msg| puts "\n#{msg}" }
25
+ STDOUT << '?'
26
+ }
27
+
28
+ spin_loop {
29
+ sql = format("notify foo, %s", db2.escape_literal(Time.now.to_s))
30
+ db2.query(sql)
31
+ STDOUT << '!'
32
+ sleep rand(1.5..3)
33
+ }
34
+
35
+ suspend
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/adapters/postgres'
5
+
6
+ PGOPTS = {
7
+ host: '/tmp',
8
+ user: 'reality',
9
+ password: nil,
10
+ dbname: 'reality',
11
+ sslmode: 'require'
12
+ }.freeze
13
+
14
+ DBPOOL = Polyphony::ResourcePool.new(limit: 16) { PG.connect(PGOPTS) }
15
+
16
+ def get_records(db)
17
+ db.query('select pg_sleep(0.001) as test')
18
+ # puts "got #{res.ntuples} records: #{res.to_a}"
19
+ rescue StandardError => e
20
+ puts "got error: #{e.inspect}"
21
+ puts e.backtrace.join("\n")
22
+ end
23
+
24
+ CONCURRENCY = ARGV.first ? ARGV.first.to_i : 10
25
+ puts "concurrency: #{CONCURRENCY}"
26
+
27
+ DBPOOL.preheat!
28
+ t0 = Time.now
29
+ count = 0
30
+
31
+ fibers = CONCURRENCY.times.map do
32
+ spin do
33
+ loop do
34
+ DBPOOL.acquire do |db|
35
+ get_records(db)
36
+ count += 1
37
+ end
38
+ end
39
+ end
40
+ end
41
+ sleep 5
42
+ puts "count: #{count} query rate: #{count / (Time.now - t0)} queries/s"
43
+ fibers.each(&:interrupt)
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/adapters/postgres'
5
+
6
+ DB = PG.connect(
7
+ host: '/tmp',
8
+ user: 'reality',
9
+ password: nil,
10
+ dbname: 'reality',
11
+ sslmode: 'require'
12
+ )
13
+
14
+ def perform(error)
15
+ puts '*' * 40
16
+ DB.transaction do
17
+ res = DB.query('select 1 as test')
18
+ puts "result: #{res.to_a}"
19
+ raise 'hello' if error
20
+
21
+ DB.transaction do
22
+ res = DB.query('select 2 as test')
23
+ puts "result: #{res.to_a}"
24
+ end
25
+ end
26
+ rescue StandardError => e
27
+ puts "error: #{e.inspect}"
28
+ end
29
+
30
+ perform(true)
31
+ perform(false)