polyphony 0.27 → 0.28

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/CHANGELOG.md +13 -0
  4. data/Gemfile +12 -1
  5. data/Gemfile.lock +83 -5
  6. data/Rakefile +4 -0
  7. data/TODO.md +11 -20
  8. data/docs/_config.yml +15 -0
  9. data/docs/_includes/nav.html +47 -0
  10. data/docs/_sass/custom/custom.scss +5 -0
  11. data/docs/_sass/overrides.scss +45 -0
  12. data/docs/assets/img/echo-fibers.svg +1 -0
  13. data/docs/assets/img/sleeping-fiber.svg +1 -0
  14. data/docs/faq.md +182 -0
  15. data/docs/getting-started/installing.md +10 -2
  16. data/docs/getting-started/tutorial.md +333 -26
  17. data/docs/getting-started.md +10 -0
  18. data/docs/index.md +91 -0
  19. data/docs/technical-overview/concurrency.md +78 -16
  20. data/docs/technical-overview/design-principles.md +7 -0
  21. data/docs/technical-overview/exception-handling.md +57 -9
  22. data/docs/technical-overview/extending.md +7 -0
  23. data/docs/technical-overview/fiber-scheduling.md +128 -18
  24. data/docs/technical-overview.md +10 -0
  25. data/docs/user-guide/web-server.md +7 -0
  26. data/docs/user-guide.md +10 -0
  27. data/examples/core/xx-deadlock.rb +8 -0
  28. data/examples/core/xx-state-machine.rb +51 -0
  29. data/examples/core/xx-trace.rb +80 -0
  30. data/examples/interfaces/pg_notify.rb +35 -0
  31. data/examples/io/xx-httparty.rb +31 -6
  32. data/examples/io/xx-irb.rb +1 -11
  33. data/examples/io/xx-switch.rb +15 -0
  34. data/ext/gyro/gyro.c +77 -38
  35. data/ext/gyro/gyro.h +15 -5
  36. data/ext/gyro/gyro_ext.c +3 -0
  37. data/ext/gyro/thread.c +47 -32
  38. data/ext/gyro/tracing.c +11 -0
  39. data/lib/polyphony/core/global_api.rb +11 -4
  40. data/lib/polyphony/core/supervisor.rb +1 -0
  41. data/lib/polyphony/core/thread_pool.rb +44 -35
  42. data/lib/polyphony/extensions/fiber.rb +19 -9
  43. data/lib/polyphony/extensions/io.rb +14 -14
  44. data/lib/polyphony/extensions/socket.rb +3 -3
  45. data/lib/polyphony/irb.rb +13 -0
  46. data/lib/polyphony/postgres.rb +15 -0
  47. data/lib/polyphony/trace.rb +98 -0
  48. data/lib/polyphony/version.rb +1 -1
  49. data/lib/polyphony.rb +1 -0
  50. data/polyphony.gemspec +21 -12
  51. data/test/helper.rb +3 -2
  52. data/test/test_fiber.rb +53 -3
  53. data/test/test_global_api.rb +12 -0
  54. data/test/test_gyro.rb +2 -2
  55. data/test/test_supervisor.rb +12 -0
  56. data/test/test_thread.rb +12 -0
  57. data/test/test_thread_pool.rb +75 -0
  58. data/test/test_throttler.rb +6 -0
  59. data/test/test_trace.rb +66 -0
  60. metadata +99 -9
  61. data/docs/README.md +0 -36
  62. data/docs/summary.md +0 -60
  63. data/docs/technical-overview/faq.md +0 -97
@@ -1,6 +1,19 @@
1
+ ---
2
+ layout: page
3
+ title: Exception Handling
4
+ nav_order: 4
5
+ parent: Technical Overview
6
+ permalink: /technical-overview/exception-handling/
7
+ ---
1
8
  # Exception Handling
2
9
 
3
- Ruby employs a pretty robust exception handling mechanism. An raised exception will bubble up the call stack until a suitable exception handler is found, based on the exception's class. In addition, the exception will include a stack trace showing the execution path from the exception's locus back to the program's entry point. Unfortunately, when exceptions are raised while switching between fibers, stack traces will only include partial information. Here's a simple demonstration:
10
+ Ruby employs a pretty robust exception handling mechanism. An raised exception
11
+ will bubble up the call stack until a suitable exception handler is found, based
12
+ on the exception's class. In addition, the exception will include a stack trace
13
+ showing the execution path from the exception's locus back to the program's
14
+ entry point. Unfortunately, when exceptions are raised while switching between
15
+ fibers, stack traces will only include partial information. Here's a simple
16
+ demonstration:
4
17
 
5
18
  _fiber\_exception.rb_
6
19
 
@@ -28,7 +41,11 @@ Traceback (most recent call last):
28
41
  fiber_exception.rb:4:in `fail!': foobar (RuntimeError)
29
42
  ```
30
43
 
31
- So, the stack trace includes two frames: the exception's locus on line 4 and the call site at line 9. But we have no information on how we got to line 9. Let's imagine if we had more complete information about the sequence of execution. In fact, what is missing is information about how the different fibers were created. If we had that, our stack trace would have looked something like this:
44
+ So, the stack trace includes two frames: the exception's locus on line 4 and the
45
+ call site at line 9. But we have no information on how we got to line 9. Let's
46
+ imagine if we had more complete information about the sequence of execution. In
47
+ fact, what is missing is information about how the different fibers were
48
+ created. If we had that, our stack trace would have looked something like this:
32
49
 
33
50
  ```text
34
51
  Traceback (most recent call last):
@@ -39,9 +56,19 @@ Traceback (most recent call last):
39
56
  fiber_exception.rb:4:in `fail!': foobar (RuntimeError)
40
57
  ```
41
58
 
42
- In order to achieve this, Polyphony patches `Fiber.new` to keep track of the call stack at the moment the fiber was created, as well as the fiber from which the call happened. In addition, Polyphony patches `Exception#backtrace` in order to synthesize a complete stack trace based on the call stack information stored for the current fiber. This is done recursively through the chain of fibers leading up to the current location. What we end up with is a record of the entire sequence of \(possibly intermittent\) execution leading up to the point where the exception was raised.
59
+ In order to achieve this, Polyphony patches `Fiber.new` to keep track of the
60
+ call stack at the moment the fiber was created, as well as the fiber from which
61
+ the call happened. In addition, Polyphony patches `Exception#backtrace` in order
62
+ to synthesize a complete stack trace based on the call stack information stored
63
+ for the current fiber. This is done recursively through the chain of fibers
64
+ leading up to the current location. What we end up with is a record of the
65
+ entire sequence of \(possibly intermittent\) execution leading up to the point
66
+ where the exception was raised.
43
67
 
44
- In addition, the backtrace is sanitized to remove stack frames originating from the Polyphony code itself, which hides away the Polyphony plumbing and lets developers concentrate on their own code. The sanitizing of exception backtraces can be disabled by setting the `Exception.__disable_sanitized_backtrace__` flag:
68
+ In addition, the backtrace is sanitized to remove stack frames originating from
69
+ the Polyphony code itself, which hides away the Polyphony plumbing and lets
70
+ developers concentrate on their own code. The sanitizing of exception backtraces
71
+ can be disabled by setting the `Exception.__disable_sanitized_backtrace__` flag:
45
72
 
46
73
  ```ruby
47
74
  Exception.__disable_sanitized_backtrace__ = true
@@ -50,7 +77,10 @@ Exception.__disable_sanitized_backtrace__ = true
50
77
 
51
78
  ## Cleaning up after exceptions
52
79
 
53
- A major issue when handling exceptions is cleaning up - freeing up resources that have been allocated, cancelling ongoing operations, etc. Polyphony allows using the normal `ensure` statement for cleaning up. Have a look at Polyphony's implementation of `Kernel#sleep`:
80
+ A major issue when handling exceptions is cleaning up - freeing up resources
81
+ that have been allocated, cancelling ongoing operations, etc. Polyphony allows
82
+ using the normal `ensure` statement for cleaning up. Have a look at Polyphony's
83
+ implementation of `Kernel#sleep`:
54
84
 
55
85
  ```ruby
56
86
  def sleep(duration)
@@ -61,13 +91,27 @@ ensure
61
91
  end
62
92
  ```
63
93
 
64
- This method creates a one-shot timer with the given duration and then suspends the current fiber, waiting for the timer to fire and then resume the fiber. While the awaiting fiber is suspended, other operations might be going on, which might interrupt the `sleep` operation by scheduling the awaiting fiber with an exception, for example a `MoveOn` or a `Cancel` exception. For this reason, we need to _ensure_ that the timer will be stopped, regardless of whether it has fired or not. We call `timer.stop` inside an ensure block, thus ensuring that the timer will have stopped once the awaiting fiber has resumed, even if it has not fired.
94
+ This method creates a one-shot timer with the given duration and then suspends
95
+ the current fiber, waiting for the timer to fire and then resume the fiber.
96
+ While the awaiting fiber is suspended, other operations might be going on, which
97
+ might interrupt the `sleep` operation by scheduling the awaiting fiber with an
98
+ exception, for example a `MoveOn` or a `Cancel` exception. For this reason, we
99
+ need to _ensure_ that the timer will be stopped, regardless of whether it has
100
+ fired or not. We call `timer.stop` inside an ensure block, thus ensuring that
101
+ the timer will have stopped once the awaiting fiber has resumed, even if it has
102
+ not fired.
65
103
 
66
104
  ## Bubbling Up - A Robust Solution for Uncaught Exceptions
67
105
 
68
- One of the "annoying" things about exceptions is that for them to be useful, you have to intercept them \(using `rescue`\). If you forget to do that, you'll end up with uncaught exceptions that can wreak havoc. For example, by default a Ruby `Thread` in which an exception was raised without being caught, will simply terminate with the exception silently swallowed.
106
+ One of the "annoying" things about exceptions is that for them to be useful, you
107
+ have to intercept them \(using `rescue`\). If you forget to do that, you'll end
108
+ up with uncaught exceptions that can wreak havoc. For example, by default a Ruby
109
+ `Thread` in which an exception was raised without being caught, will simply
110
+ terminate with the exception silently swallowed.
69
111
 
70
- To prevent the same from happening with fibers, Polyphony provides a mechanism that lets uncaught exceptions bubble up through the chain of calling fibers. Let's discuss the following example:
112
+ To prevent the same from happening with fibers, Polyphony provides a mechanism
113
+ that lets uncaught exceptions bubble up through the chain of calling fibers.
114
+ Let's discuss the following example:
71
115
 
72
116
  ```ruby
73
117
  require 'polyphony'
@@ -83,5 +127,9 @@ spin do
83
127
  end.await
84
128
  ```
85
129
 
86
- In this example, there are four fibers, nested one within the other. An exception is raised in the inner most fiber, and having no exception handler, will bubble up through the different enclosing fibers, until reaching the top-most level, that of the root fiber, at which point the exception will cause the program to halt and print an error message.
130
+ In this example, there are four fibers, nested one within the other. An
131
+ exception is raised in the inner most fiber, and having no exception handler,
132
+ will bubble up through the different enclosing fibers, until reaching the
133
+ top-most level, that of the root fiber, at which point the exception will cause
134
+ the program to halt and print an error message.
87
135
 
@@ -1,3 +1,10 @@
1
+ ---
2
+ layout: page
3
+ title: Extending Polyphony
4
+ nav_order: 5
5
+ parent: Technical Overview
6
+ permalink: /technical-overview/extending/
7
+ ---
1
8
  # Extending Polyphony
2
9
 
3
10
  Polyphony was designed to ease the transition from blocking APIs and
@@ -1,21 +1,108 @@
1
+ ---
2
+ layout: page
3
+ title: How Fibers are Scheduled
4
+ nav_order: 3
5
+ parent: Technical Overview
6
+ permalink: /technical-overview/fiber-scheduling/
7
+ ---
1
8
  # How Fibers are Scheduled
2
9
 
3
- Ruby provides two mechanisms for transferring control between fibers: `Fiber#resume` / `Fiber.yield` and `Fiber#transfer`. The first is inherently asymmetric and is famously used for implementing generators and [resumable enumerators](https://blog.appsignal.com/2018/11/27/ruby-magic-fibers-and-enumerators-in-ruby.html). Another limiting factor of using resume / yield is that the root fiber can't yield away, limiting its usability as a resumable fiber.
10
+ Ruby provides two mechanisms for transferring control between fibers:
11
+ `Fiber#resume` /`Fiber.yield` and `Fiber#transfer`. The first is inherently
12
+ asymmetric and is famously used for implementing generators and [resumable
13
+ enumerators](https://blog.appsignal.com/2018/11/27/ruby-magic-fibers-and-enumerators-in-ruby.html).
14
+ Another limiting factor of using resume / yield is that the main fiber can't
15
+ yield away, limiting its usability as a resumable fiber.
4
16
 
5
- The second mechanism, using `Fiber#transfer`, is completely symmetric and allows use of the root fiber as a general purpose resumable execution context. Polyphony uses `Fiber#transfer` exclusively for scheduling fibers.
17
+ The second mechanism, using `Fiber#transfer`, is completely symmetric and allows
18
+ use of the main fiber as a general purpose resumable execution context.
19
+ Polyphony uses `Fiber#transfer` exclusively for scheduling fibers.
6
20
 
7
21
  ## Scheduler-less scheduling
8
22
 
9
- Polyphony relies on [libev](http://software.schmorp.de/pkg/libev.html) for handling events such as I/O readiness, timers and signals. In most event reactor-based libraries and frameworks, such as `nio4r`, `EventMachine` or `node.js`, the reactor loop is run, and event callbacks are used to schedule user-supplied code *from inside the loop*. In Polyphony, however, we have chosen a programming model that does not use a loop to schedule fibers. In fact, in Polyphony there's no such thing as a reactor loop, and there's no *scheduler* running on a separate execution context.
10
-
11
- Instead, Polyphony maintains a list of scheduled fibers, fibers that will be resumed once the current fiber yields control, which will occur on every blocking operation. If no fibers are scheduled, the libev event reactor will be ran until one or more events have occurred. Events are handled by adding the corresponding fibers onto the *scheduled fibers* list. Finally, control is transferred to the first scheduled fiber, which will run until it blocks or terminates, at which point control is transferred to the next scheduled fiber.
23
+ Polyphony relies on [libev](http://software.schmorp.de/pkg/libev.html) for
24
+ handling events such as I/O readiness, timers and signals. In most event
25
+ reactor-based libraries and frameworks, such as `nio4r`, `EventMachine` or
26
+ `node.js`, the reactor loop is run, and event callbacks are used to schedule
27
+ user-supplied code *from inside the loop*. In Polyphony, however, we have chosen
28
+ a programming model that does not use a loop to schedule fibers. In fact, in
29
+ Polyphony there's no such thing as a reactor loop, and there's no *scheduler*
30
+ running on a separate execution context.
31
+
32
+ Instead, Polyphony maintains for each thread a list of scheduled fibers, fibers
33
+ that will be resumed once the current fiber yields control, which will occur on
34
+ every blocking operation. If no fibers are scheduled, the libev event reactor
35
+ will be ran until one or more events have occurred. Events are handled by adding
36
+ the corresponding fibers onto the *scheduled fibers* list. Finally, control is
37
+ transferred to the first scheduled fiber, which will run until it blocks or
38
+ terminates, at which point control is transferred to the next scheduled fiber.
12
39
 
13
40
  This approach has numerous benefits:
14
41
 
15
- - No separate reactor fiber that needs to be resumed on each blocking operation, leading to less context switches, and less bookkeeping.
16
- - Clear separation between the reactor code (the `libev` code) and the fiber scheduling code.
17
- - Much less time is spent in reactor loop callbacks, letting the reactor loop run more efficiently.
18
- - Fibers are resumed outside of the event reactor code, making it easier to avoid race conditions and unexpected behaviours.
42
+ - No separate reactor fiber that needs to be resumed on each blocking operation,
43
+ leading to less context switches, and less bookkeeping.
44
+ - Clear separation between the reactor code (the `libev` code) and the fiber
45
+ scheduling code.
46
+ - Much less time is spent in reactor loop callbacks, letting the reactor loop
47
+ run more efficiently.
48
+ - Fibers are resumed outside of the event reactor code, making it easier to
49
+ avoid race conditions and unexpected behaviours.
50
+
51
+ ## A concrete example - fiber scheduling in an echo server
52
+
53
+ Here's a barebones echo server written in Polyphony:
54
+
55
+ ```ruby
56
+ require 'polyphony'
57
+
58
+ server = TCPServer.open('127.0.0.1', 8081)
59
+ while (client = server.accept)
60
+ spin do
61
+ while (data = client.gets)
62
+ client << ">>>you sent: #{data}"
63
+ break if data =~ /quit/i
64
+ end
65
+ end
66
+ end
67
+ ```
68
+
69
+ Let's examine the the flow of control in our echo server program:
70
+
71
+ <p class="img-figure"><img src="../../assets/img/echo-fibers.svg"></p>
72
+
73
+ > In the above figure, the fat blue dots represents moments at which fibers can
74
+ > be switched. The light blue horizontal arrows represent switching from one
75
+ > fiber to another. The blue vertical lines represent a train of execution on a
76
+ > single fiber.
77
+
78
+ - The main fiber (fiber 1) runs a loop waiting for incoming connections.
79
+ - The call to `server.accept` blocks, and an I/O event watcher is set up. The
80
+ main fiber is suspended.
81
+ - Since there's no other runnable fiber, the associated event loop is run.
82
+ - An event is generated for the server's socket, and fiber 1 is added to the run
83
+ queue.
84
+ - Fiber 1 is pulled from the run queue and resumed. The `server.accept` call
85
+ returns a new client socket (an instance of `TCPSocket`).
86
+ - Fiber 1 continues by `spin`ning up a new fiber for handling the client. The
87
+ new fiber (fiber 2) is added to the run queue.
88
+ - Fiber 1 goes back to waiting for an incoming connection by calling
89
+ `server.accept` again. The call blocks, the main fiber suspends, and switches
90
+ to the next fiber in the run queue, fiber 2.
91
+ - Fiber 2 starts a loop and calls `client.gets`. The call blocks, an I/O event
92
+ watcher is set up, and the fiber suspends.
93
+ - Since no other fiber is runnable, the event loop is run, waiting for at least
94
+ one event to fire.
95
+ - An event fires for the acceptor socket, and fiber 1 is put on the run queue.
96
+ - An event fires for the client socket, and fiber 2 is put on the run queue.
97
+ - Fiber 1 is resumed and spins up a new client handling fiber (fiber 3), which
98
+ is put on the run queue.
99
+ - Fiber 1 calls `server.accept` again, and suspends, switching to the next
100
+ runnable fiber, fiber 2.
101
+ - Fiber 2 resumes and completes reading a line from the socket.
102
+ - Fiber 2 calls `client.<<`, blocks, sets up an I/O watcher, and suspends,
103
+ switching to the next runnable fiber, fiber 3.
104
+ - Fiber 3 resumes and calls `client.gets`. The call blocks, an I/O event watcher
105
+ is set up, and the fiber suspends.
19
106
 
20
107
  ## Fiber states
21
108
 
@@ -23,24 +110,45 @@ In Polyphony, each fiber has one of the following states at any given moment:
23
110
 
24
111
  - `:running`: this is the state of the currently running fiber.
25
112
  - `:dead`: the fiber has terminated.
26
- - `:paused`: the fiber is paused;
27
- - `:scheduled`: the fiber is scheduled to run soon.
113
+ - `:waiting`: the fiber is suspended, waiting for something to wake it up.
114
+ - `:runnable`: the fiber is on the run queue and will be run shortly.
28
115
 
29
116
  ## Fiber scheduling and fiber switching
30
117
 
31
- The Polyphony scheduling model makes a clear separation between the scheduling of fibers and the switching of fibers. The scheduling of fibers is the act of marking the fiber to be run at the earliest opportunity, but not immediately. The switching of fibers is the act of transferring control to another fiber, in this case the first fiber in the list of *currently* scheduled fibers.
118
+ The Polyphony scheduling model makes a clear separation between the scheduling
119
+ of fibers and the switching of fibers. The scheduling of fibers is the act of
120
+ marking the fiber to be run at the earliest opportunity, but not immediately.
121
+ The switching of fibers is the act of transferring control to another fiber, in
122
+ this case the first fiber in the list of *currently* scheduled fibers.
32
123
 
33
- The scheduling of fibers can occur at any time, either as a result of an event occuring, an exception being raised, or using `Fiber#schedule`. The switching of fibers will occur only when a blocking operation is started, or upon calling `Fiber#suspend` or `Fiber#snooze`. In order to switch to a scheduled fiber, Polyphony uses `Fiber#transfer`.
124
+ The scheduling of fibers can occur at any time, either as a result of an event
125
+ occuring, an exception being raised, or using `Fiber#schedule`. The switching of
126
+ fibers will occur only when a blocking operation is started, or upon calling
127
+ `Fiber#suspend` or `Fiber#snooze`. In order to switch to a scheduled fiber,
128
+ Polyphony uses `Fiber#transfer`.
34
129
 
35
- When a fiber terminates, any other scheduled fibers will be run. If no fibers are waiting and the main fiber is done running, the Ruby process will terminate.
130
+ When a fiber terminates, any other scheduled fibers will be run. If no fibers
131
+ are waiting and the main fiber is done running, the Ruby process will terminate.
36
132
 
37
133
  ## Interrupting blocking operations
38
134
 
39
- Sometimes it is desirable to be able to interrupt a blocking operation, such as waiting for a socket to be readable, or sleeping for an extended period of time. This is especially useful when higher-level constructs are needed for controlling multiple concurrent operations.
135
+ Sometimes it is desirable to be able to interrupt a blocking operation, such as
136
+ waiting for a socket to be readable, or sleeping for an extended period of time.
137
+ This is especially useful when higher-level constructs are needed for
138
+ controlling multiple concurrent operations.
40
139
 
41
- Polyphony provides the ability to interrupt a blocking operation by harnessing the ability to transfer values back and forth between fibers using `Fiber#transfer`. Whenever a waiting fiber yields control to the next scheduled fiber, the value received upon being resumed is checked. If the value is an exception, it will be raised in the context of the waiting fiber, effectively signalling that the blocking operation has been unsuccessful and allowing exception handling using the builtin mechanisms offered by Ruby, namely `rescue` and `ensure` (see also [exception handling](exception-handling.md)).
140
+ Polyphony provides the ability to interrupt a blocking operation by harnessing
141
+ the ability to transfer values back and forth between fibers using
142
+ `Fiber#transfer`. Whenever a waiting fiber yields control to the next scheduled
143
+ fiber, the value received upon being resumed is checked. If the value is an
144
+ exception, it will be raised in the context of the waiting fiber, effectively
145
+ signalling that the blocking operation has been unsuccessful and allowing
146
+ exception handling using the builtin mechanisms offered by Ruby, namely `rescue`
147
+ and `ensure` (see also [exception handling](exception-handling.md)).
42
148
 
43
- Here's a naive implementation of a yielding I/O read operation in Polyphony (the actual code for I/O reading in Polyphony is written in C and is a bit more involved):
149
+ Here's a naive implementation of a yielding I/O read operation in Polyphony (the
150
+ actual code for I/O reading in Polyphony is written in C and is a bit more
151
+ involved):
44
152
 
45
153
  ```ruby
46
154
  def read_from(io)
@@ -70,7 +178,9 @@ ensure
70
178
  end
71
179
  ```
72
180
 
73
- In the above example, the `wait_readable` method will normally wait indefinitely until the IO object has become readable. But we could interrupt it at any time by scheduling the corresponding fiber with an exception:
181
+ In the above example, the `wait_readable` method will normally wait indefinitely
182
+ until the IO object has become readable. But we could interrupt it at any time
183
+ by scheduling the corresponding fiber with an exception:
74
184
 
75
185
  ```ruby
76
186
  def timeout(duration)
@@ -0,0 +1,10 @@
1
+ ---
2
+ layout: page
3
+ title: Technical Overview
4
+ description: Lorem ipsum
5
+ has_children: true
6
+ section: true
7
+ has_toc: false
8
+ nav_order: 3
9
+ section_link: /technical-overview/design-principles
10
+ ---
@@ -1,3 +1,10 @@
1
+ ---
2
+ layout: page
3
+ title: Web Server
4
+ nav_order: 5
5
+ parent: User Guide
6
+ permalink: /user-guide/web-server/
7
+ ---
1
8
  # Web Server
2
9
 
3
10
  Polyphony's web server functionality offers a powerful and flexible way to
@@ -0,0 +1,10 @@
1
+ ---
2
+ layout: page
3
+ title: User Guide
4
+ description: Lorem ipsum
5
+ has_children: true
6
+ section: true
7
+ has_toc: false
8
+ nav_order: 4
9
+ section_link: /user-guide/web-server
10
+ ---
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ spin { sleep }
7
+
8
+ sleep
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'bundler/setup'
4
+ # require 'polyphony'
5
+
6
+ require 'fiber'
7
+
8
+ class StateMachine < ::Fiber
9
+ def initialize(state, rules)
10
+ @state = state
11
+ @rules = rules
12
+ super() { |input| state_loop(input) }
13
+ end
14
+
15
+ attr_reader :state
16
+ def state_loop(input)
17
+ loop do
18
+ @state = apply(input)
19
+ input = Fiber.yield(@state)
20
+ end
21
+ end
22
+
23
+ def apply(input)
24
+ f = @rules[@state][input]
25
+ return f.(@state) if f.is_a?(Proc)
26
+
27
+ raise 'Invalid input'
28
+ rescue => e
29
+ @state
30
+ end
31
+
32
+ def transition(input)
33
+ state = self.resume(input)
34
+ # state.is_a?(Exception) ? (raise state) : state
35
+ end
36
+ end
37
+
38
+ o = StateMachine.new(
39
+ :off,
40
+ {
41
+ off: { turnon: ->(s) { :on } },
42
+ on: { turnoff: ->(s) { :off } }
43
+ }
44
+ )
45
+
46
+ loop do
47
+ STDOUT << "#{o.state}: "
48
+ input = gets.strip.to_sym
49
+ # puts " command: #{input.inspect}"
50
+ o.transition(input)
51
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ Gyro.trace(true)
9
+
10
+ sleep 0
11
+ $records = []
12
+
13
+ trace = Polyphony::Trace.new { |r| $records << r }
14
+
15
+ trace.enable
16
+
17
+ f2 = spin(:f2) { 3.times { sleep 0.1 } }
18
+
19
+ 10.times {
20
+ spin { 3.times { sleep rand(0.05..0.15) } }
21
+ }
22
+
23
+ suspend
24
+ trace.disable
25
+ puts("record count: %d" % $records.size)
26
+
27
+ analysis = Polyphony::Trace.analyze $records
28
+
29
+ puts("fiber count: %d" % analysis[:by_fiber].size)
30
+ puts
31
+
32
+ worker_fibers = analysis[:by_fiber].keys - [Fiber.current]
33
+
34
+ analysis[:by_fiber][f2].each { |r|
35
+ case r[:event]
36
+ when /^fiber_/
37
+ STDOUT.orig_puts "#{r[:stamp]} #{r[:event]} (#{r[:value].inspect})"
38
+ else
39
+ STDOUT.orig_puts "#{r[:stamp]} #{r[:fiber]&.tag} #{r[:event]} (#{r[:value].inspect})"
40
+ end
41
+ }
42
+
43
+ state = 0
44
+ run_wait_stamp = nil
45
+ schedule_stamp = nil
46
+ run_time = 0
47
+ wait_time = 0
48
+ schedule_count = 0
49
+ schedule_acc = 0
50
+ worker_fibers.each do |f|
51
+ analysis[:by_fiber][f].each { |r|
52
+ case r[:event]
53
+ when :fiber_create
54
+ state = 0
55
+ run_wait_stamp = r[:stamp]
56
+ when :fiber_schedule
57
+ schedule_count += 1
58
+ schedule_stamp = r[:stamp]
59
+ when :fiber_run
60
+ schedule_acc += r[:stamp] - schedule_stamp
61
+ wait_time += r[:stamp] - run_wait_stamp
62
+ state = 1
63
+ schedule_stamp = run_wait_stamp = r[:stamp]
64
+ when :fiber_switchpoint, :fiber_terminate
65
+ run_time += r[:stamp] - run_wait_stamp
66
+ state = 0
67
+ run_wait_stamp = r[:stamp]
68
+ end
69
+ }
70
+ end
71
+
72
+ puts(
73
+ format(
74
+ "f2 run: %f wait: %f schedule_count: %d avg schedule latency: %f",
75
+ run_time,
76
+ wait_time,
77
+ schedule_count,
78
+ schedule_acc / schedule_count
79
+ )
80
+ )
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/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
@@ -3,11 +3,36 @@
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
5
  require 'httparty'
6
+ require 'json'
6
7
 
7
- timer = spin { throttled_loop(100) {
8
- STDOUT << '.'
9
- } }
8
+ def get_time(tzone)
9
+ res = HTTParty.get("http://worldtimeapi.org/api/timezone/#{tzone}")
10
+ return '- failed -' unless res.ok?
10
11
 
11
- res = HTTParty.get('http://worldtimeapi.org/api/timezone/Europe/Paris')
12
- puts res
13
- timer.stop
12
+ json = JSON.parse(res.body)
13
+ Time.parse(json['datetime'])
14
+ end
15
+
16
+ zones = %w{
17
+ Europe/London Europe/Paris Europe/Bucharest America/New_York Asia/Bangkok
18
+ }
19
+ # zones.each do |tzone|
20
+ # spin do
21
+ # time = get_time(tzone)
22
+ # puts "Time in #{tzone}: #{time}"
23
+ # end
24
+ # end
25
+
26
+ # suspend
27
+
28
+ def get_times(zones)
29
+ Polyphony::Supervisor.new do |s|
30
+ zones.each do |tzone|
31
+ s.spin { [tzone, get_time(tzone)] }
32
+ end
33
+ end
34
+ end
35
+
36
+ get_times(zones).await.each do |tzone, time|
37
+ puts "Time in #{tzone}: #{time}"
38
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
5
4
  require 'irb'
5
+ require 'polyphony/irb'
6
6
 
7
7
  $counter = 0
8
8
  timer = spin do
@@ -11,16 +11,6 @@ timer = spin do
11
11
  end
12
12
  end
13
13
 
14
- # readline blocks the current thread, so we offload it to the blocking-ops
15
- # thread pool. That way, the reactor loop can keep running while waiting for
16
- # readline to return
17
- module ::Readline
18
- alias_method :orig_readline, :readline
19
- def readline(*args)
20
- Polyphony::ThreadPool.process { orig_readline(*args) }
21
- end
22
- end
23
-
24
14
  at_exit { timer.stop }
25
15
 
26
16
  puts 'try typing $counter to see the counter incremented in the background'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fiber'
4
+
5
+ X = 10_000_000
6
+
7
+ main = Fiber.current
8
+ f = Fiber.new do
9
+ loop { main.transfer }
10
+ end
11
+
12
+ t0 = Time.now
13
+ X.times { f.transfer }
14
+ dt = Time.now - t0
15
+ puts "#{X / dt.to_f}/s"