polyphony 0.27 → 0.28
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.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/CHANGELOG.md +13 -0
- data/Gemfile +12 -1
- data/Gemfile.lock +83 -5
- data/Rakefile +4 -0
- data/TODO.md +11 -20
- data/docs/_config.yml +15 -0
- data/docs/_includes/nav.html +47 -0
- data/docs/_sass/custom/custom.scss +5 -0
- data/docs/_sass/overrides.scss +45 -0
- data/docs/assets/img/echo-fibers.svg +1 -0
- data/docs/assets/img/sleeping-fiber.svg +1 -0
- data/docs/faq.md +182 -0
- data/docs/getting-started/installing.md +10 -2
- data/docs/getting-started/tutorial.md +333 -26
- data/docs/getting-started.md +10 -0
- data/docs/index.md +91 -0
- data/docs/technical-overview/concurrency.md +78 -16
- data/docs/technical-overview/design-principles.md +7 -0
- data/docs/technical-overview/exception-handling.md +57 -9
- data/docs/technical-overview/extending.md +7 -0
- data/docs/technical-overview/fiber-scheduling.md +128 -18
- data/docs/technical-overview.md +10 -0
- data/docs/user-guide/web-server.md +7 -0
- data/docs/user-guide.md +10 -0
- data/examples/core/xx-deadlock.rb +8 -0
- data/examples/core/xx-state-machine.rb +51 -0
- data/examples/core/xx-trace.rb +80 -0
- data/examples/interfaces/pg_notify.rb +35 -0
- data/examples/io/xx-httparty.rb +31 -6
- data/examples/io/xx-irb.rb +1 -11
- data/examples/io/xx-switch.rb +15 -0
- data/ext/gyro/gyro.c +77 -38
- data/ext/gyro/gyro.h +15 -5
- data/ext/gyro/gyro_ext.c +3 -0
- data/ext/gyro/thread.c +47 -32
- data/ext/gyro/tracing.c +11 -0
- data/lib/polyphony/core/global_api.rb +11 -4
- data/lib/polyphony/core/supervisor.rb +1 -0
- data/lib/polyphony/core/thread_pool.rb +44 -35
- data/lib/polyphony/extensions/fiber.rb +19 -9
- data/lib/polyphony/extensions/io.rb +14 -14
- data/lib/polyphony/extensions/socket.rb +3 -3
- data/lib/polyphony/irb.rb +13 -0
- data/lib/polyphony/postgres.rb +15 -0
- data/lib/polyphony/trace.rb +98 -0
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +1 -0
- data/polyphony.gemspec +21 -12
- data/test/helper.rb +3 -2
- data/test/test_fiber.rb +53 -3
- data/test/test_global_api.rb +12 -0
- data/test/test_gyro.rb +2 -2
- data/test/test_supervisor.rb +12 -0
- data/test/test_thread.rb +12 -0
- data/test/test_thread_pool.rb +75 -0
- data/test/test_throttler.rb +6 -0
- data/test/test_trace.rb +66 -0
- metadata +99 -9
- data/docs/README.md +0 -36
- data/docs/summary.md +0 -60
- 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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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,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:
|
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
|
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
|
10
|
-
|
11
|
-
|
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,
|
16
|
-
|
17
|
-
-
|
18
|
-
|
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
|
-
- `:
|
27
|
-
- `:
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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)
|
data/docs/user-guide.md
ADDED
@@ -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
|
data/examples/io/xx-httparty.rb
CHANGED
@@ -3,11 +3,36 @@
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'polyphony'
|
5
5
|
require 'httparty'
|
6
|
+
require 'json'
|
6
7
|
|
7
|
-
|
8
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
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
|
data/examples/io/xx-irb.rb
CHANGED
@@ -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'
|