polyphony 0.27 → 0.28
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|