polyphony 0.32 → 0.33
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/.github/workflows/test.yml +20 -0
- data/.rubocop.yml +14 -1
- data/CHANGELOG.md +10 -2
- data/Gemfile.lock +1 -1
- data/README.md +4 -0
- data/TODO.md +116 -1
- data/docs/_sass/custom/custom.scss +4 -0
- data/docs/_sass/overrides.scss +4 -6
- data/docs/getting-started/installing.md +2 -2
- data/docs/getting-started/tutorial.md +17 -15
- data/docs/index.md +18 -23
- data/docs/main-concepts/concurrency.md +1 -1
- data/ext/gyro/async.c +27 -0
- data/ext/gyro/gyro.h +1 -0
- data/ext/gyro/queue.c +10 -9
- data/ext/gyro/selector.c +3 -5
- data/ext/gyro/thread.c +6 -17
- data/lib/polyphony.rb +1 -0
- data/lib/polyphony/core/exceptions.rb +4 -1
- data/lib/polyphony/core/global_api.rb +4 -0
- data/lib/polyphony/extensions/core.rb +4 -3
- data/lib/polyphony/extensions/fiber.rb +72 -11
- data/lib/polyphony/extensions/thread.rb +20 -7
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +2 -2
- data/test/stress.rb +20 -0
- data/test/test_fiber.rb +158 -4
- data/test/test_global_api.rb +21 -14
- data/test/test_kernel.rb +23 -0
- data/test/test_signal.rb +1 -1
- data/test/test_thread.rb +4 -3
- data/test/test_thread_pool.rb +2 -2
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fde67b1d9cd285b35d265f12648e48e43073fd3b8e27713f981883ab4b080c12
|
4
|
+
data.tar.gz: a567e1e171b4f61f5fb344b3345b4a9e6f32ec7a810258c99007a17614e5cd5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e99f3e9f9acce3b2deadf516dc254ae6a305a64b9bd05a04e6207834f1189c61ae44481e485381fbb08eae3ee2f1da0843441ee518f64448f76c3529e1281a7
|
7
|
+
data.tar.gz: b6ebe93d0eb812010aafdcc2f9298e1237c14a6b0da6b3da30abe917217fb04fac24671e02792c0c3f790912660ed0b64355849e81e07bb35029dc647f413589
|
@@ -0,0 +1,20 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on: [push]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v1
|
10
|
+
- uses: actions/setup-ruby@v1
|
11
|
+
with:
|
12
|
+
ruby-version: 2.6.5
|
13
|
+
- name: Install dependencies
|
14
|
+
run: |
|
15
|
+
gem install bundler
|
16
|
+
bundle install
|
17
|
+
- name: Compile C-extension
|
18
|
+
run: bundle exec rake compile
|
19
|
+
- name: Run tests
|
20
|
+
run: bundle exec rake test
|
data/.rubocop.yml
CHANGED
@@ -129,4 +129,17 @@ Naming/MethodParameterName:
|
|
129
129
|
|
130
130
|
Security/MarshalLoad:
|
131
131
|
Exclude:
|
132
|
-
- examples/**/*.rb
|
132
|
+
- examples/**/*.rb
|
133
|
+
|
134
|
+
Lint/ShadowedArgument:
|
135
|
+
Exclude:
|
136
|
+
- lib/polyphony/extensions/fiber.rb
|
137
|
+
|
138
|
+
Style/HashEachMethods:
|
139
|
+
Enabled: true
|
140
|
+
|
141
|
+
Style/HashTransformKeys:
|
142
|
+
Enabled: true
|
143
|
+
|
144
|
+
Style/HashTransformValues:
|
145
|
+
Enabled: true
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 0.33 2020-03-08
|
2
|
+
|
3
|
+
* Implement `Fiber#supervise` (WIP)
|
4
|
+
* Add `Fiber#restart` API
|
5
|
+
* Fix race condition in `Thread#join`, `Thread#raise` (#14)
|
6
|
+
* Add `Exception#source_fiber` - references the fiber in which an uncaught
|
7
|
+
exception occurred
|
8
|
+
|
1
9
|
## 0.32 2020-02-29
|
2
10
|
|
3
11
|
* Accept optional throttling rate in `#spin_loop`
|
@@ -6,8 +14,8 @@
|
|
6
14
|
* Add `#receive_pending` global API.
|
7
15
|
* Prevent race condition in `Gyro::Queue`.
|
8
16
|
* Improve signal handling - `INT`, `TERM` signals are now always handled in the
|
9
|
-
main fiber
|
10
|
-
* Fix adapter requires (redis and postgres)
|
17
|
+
main fiber
|
18
|
+
* Fix adapter requires (redis and postgres)
|
11
19
|
|
12
20
|
## 0.31 2020-02-20
|
13
21
|
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Polyphony - Fine-Grained Concurrency for Ruby
|
2
2
|
|
3
|
+
[](http://rubygems.org/gems/polyphony)
|
4
|
+
[](https://github.com/digital-fabric/polyphony/actions?query=workflow%3ATests)
|
5
|
+
[](https://github.com/digital-fabric/polyphony/blob/master/LICENSE)
|
6
|
+
|
3
7
|
[DOCS](https://digital-fabric.github.io/polyphony/) |
|
4
8
|
[EXAMPLES](examples)
|
5
9
|
|
data/TODO.md
CHANGED
@@ -1,4 +1,118 @@
|
|
1
|
-
|
1
|
+
- Debugging
|
2
|
+
- Eat your own dogfood: need a good tool to check what's going on when some
|
3
|
+
test fails
|
4
|
+
- Needs to work with Pry (can write perhaps an extension for pry)
|
5
|
+
- First impl in Ruby using `TracePoint` API
|
6
|
+
- Mode of operation:
|
7
|
+
- Debugger runs on separate thread
|
8
|
+
- The `TracePoint` handler passes control to the debugger thread, and waits
|
9
|
+
for reply (probably using Fiber messages)
|
10
|
+
- Step over should return on the next line *for the same fiber*
|
11
|
+
- The event loop (all event loops?) should be suspended so timers are adjusted
|
12
|
+
accordingly, so on control passing to debugger we:
|
13
|
+
|
14
|
+
- call `ev_suspend()` for main thread ev_loop
|
15
|
+
- prompt and wait for input from user
|
16
|
+
- call `ev_resume()` for main thread ev_loop
|
17
|
+
- process user input
|
18
|
+
|
19
|
+
(We need to verify than `ev_suspend/resume` works for an ev_loop that's not
|
20
|
+
currently running.)
|
21
|
+
- Allow inspection of fiber tree, thread's run queue, fiber's scheduled values etc.
|
22
|
+
|
23
|
+
- UI
|
24
|
+
- `Kernel#breakpoint` is used to break into the debugger while running code
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
def test_sleep
|
28
|
+
f = spin { sleep 10 }
|
29
|
+
breakpoint
|
30
|
+
...
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
Hitting the breakpoint will show the current location in the source code
|
35
|
+
(with few lines before and after), and present a prompt for commands.
|
36
|
+
|
37
|
+
- commands:
|
38
|
+
- `step` / `up` / `skip` / `continue` etc. - step into, step out, step over, run
|
39
|
+
- `switch` - switch fiber
|
40
|
+
- how do we select a fiber?
|
41
|
+
- from a list?
|
42
|
+
- from an expression: `Fiber.current.children`
|
43
|
+
- maybe just `select f1` (where f1 is a local var)
|
44
|
+
|
45
|
+
- Fiber supervision
|
46
|
+
- can a fiber be restarted?
|
47
|
+
- Theoretically, we can take the same block and rerun it under a different
|
48
|
+
fiber
|
49
|
+
- (restart) strategy (taken from Erlang):
|
50
|
+
- `:one_for_one`: if a child terminates, it is restarted
|
51
|
+
- `:one_for_all`: if a child terminates, all other children are terminated,
|
52
|
+
then all are restarted
|
53
|
+
- `:simple_on_for_one`: same as `:one_for_one` except all children are
|
54
|
+
identical (run the same block)
|
55
|
+
- I'm not so sure this kind of supervision is what we need. We can envision
|
56
|
+
an API such as the following:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
spin {
|
60
|
+
spin { do_a }
|
61
|
+
spin { do_b }
|
62
|
+
supervise(restart: :never, shutdown: :terminate)
|
63
|
+
}
|
64
|
+
```
|
65
|
+
|
66
|
+
- The default for `#supervise` should be:
|
67
|
+
- wait for all fibers to terminate
|
68
|
+
- propagate exceptions
|
69
|
+
- no restart
|
70
|
+
|
71
|
+
- `#supervise` could also take a block:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
supervise do |fiber, error|
|
75
|
+
# this block is called when a fiber is terminated, letting the developer
|
76
|
+
# decide how to react.
|
77
|
+
|
78
|
+
# We can propagate the error
|
79
|
+
raise error if error
|
80
|
+
|
81
|
+
# We can restart the fiber (the respun fiber will have the same parent,
|
82
|
+
# the same caller location, the same tag)
|
83
|
+
fiber.respin # TODO: implement Fiber#respin
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
- Another option is to pass supervision parameters to `#spin`:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
spin(supervise: true) do
|
91
|
+
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
- Or maybe:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
spin_supervisor do
|
99
|
+
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
## 0.32 Some more API work, more docs
|
104
|
+
|
105
|
+
- Allow calling `cancel_after` and `move_on_after` without blocks, just return
|
106
|
+
the cancelling fiber:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
def foo
|
110
|
+
f = cancel_after(10)
|
111
|
+
do_something_slow
|
112
|
+
ensure
|
113
|
+
f.stop
|
114
|
+
end
|
115
|
+
```
|
2
116
|
|
3
117
|
- Docs
|
4
118
|
- landing page:
|
@@ -128,3 +242,4 @@ Prior art:
|
|
128
242
|
do_my_thing
|
129
243
|
end
|
130
244
|
```
|
245
|
+
|
data/docs/_sass/overrides.scss
CHANGED
@@ -26,12 +26,6 @@ span.section-title {
|
|
26
26
|
color: rgba($body-text-color, 0.3);
|
27
27
|
content: "";
|
28
28
|
}
|
29
|
-
|
30
|
-
&.active {
|
31
|
-
&::before {
|
32
|
-
color: $body-text-color;
|
33
|
-
}
|
34
|
-
}
|
35
29
|
}
|
36
30
|
}
|
37
31
|
|
@@ -40,6 +34,10 @@ a.navigation-list-link {
|
|
40
34
|
font-weight: 600;
|
41
35
|
}
|
42
36
|
|
37
|
+
a.navigation-list-link.active {
|
38
|
+
border-right: 4px #6ae solid;
|
39
|
+
}
|
40
|
+
|
43
41
|
a.navigation-list-link:hover {
|
44
42
|
color: $link-color;
|
45
43
|
}
|
@@ -1,13 +1,13 @@
|
|
1
1
|
---
|
2
2
|
layout: page
|
3
|
-
title:
|
3
|
+
title: Tutorial
|
4
4
|
nav_order: 2
|
5
5
|
parent: Getting Started
|
6
6
|
permalink: /getting-started/tutorial/
|
7
7
|
prev_title: Installing Polyphony
|
8
8
|
next_title: Concurrency the Easy Way
|
9
9
|
---
|
10
|
-
#
|
10
|
+
# Polyphony: a Tutorial
|
11
11
|
|
12
12
|
Polyphony is a new Ruby library aimed at making writing concurrent Ruby apps
|
13
13
|
easy and fun. In this article, we'll introduce Polyphony's fiber-based
|
@@ -170,7 +170,7 @@ rescue Errno::ECONNRESET
|
|
170
170
|
end
|
171
171
|
```
|
172
172
|
|
173
|
-
|
173
|
+
## Adding Concurrency
|
174
174
|
|
175
175
|
Up until now, we did nothing about concurrency. In fact, our code will not be
|
176
176
|
able to handle more than one client at a time, because the accept loop cannot
|
@@ -212,17 +212,17 @@ Let's consider the advantage of the Polyphony concurrency model:
|
|
212
212
|
|
213
213
|
Now that we have a working concurrent echo server, let's add some bells and
|
214
214
|
whistles. First of all, let's get rid of clients that are not active. We'll do
|
215
|
-
this by
|
216
|
-
an execution context that can cancel any operation ocurring within its scope:
|
215
|
+
this by setting up a timeout fiber that cancels the fiber dealing with the connection
|
217
216
|
|
218
217
|
```ruby
|
219
218
|
def handle_client(client)
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
end
|
219
|
+
timeout = cancel_after(10)
|
220
|
+
while (data = client.gets)
|
221
|
+
timeout.reset
|
222
|
+
client << data
|
225
223
|
end
|
224
|
+
rescue Polyphony::Cancel
|
225
|
+
client.puts 'Closing connection due to inactivity.'
|
226
226
|
rescue Errno::ECONNRESET
|
227
227
|
puts 'Connection reset by client'
|
228
228
|
ensure
|
@@ -252,15 +252,17 @@ server = TCPServer.open('127.0.0.1', 1234)
|
|
252
252
|
puts 'Echoing on port 1234...'
|
253
253
|
|
254
254
|
def handle_client(client)
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
end
|
255
|
+
timeout = cancel_after(10)
|
256
|
+
while (data = client.gets)
|
257
|
+
timeout.reset
|
258
|
+
client << data
|
260
259
|
end
|
260
|
+
rescue Polyphony::Cancel
|
261
|
+
client.puts 'Closing connection due to inactivity.'
|
261
262
|
rescue Errno::ECONNRESET
|
262
263
|
puts 'Connection reset by client'
|
263
264
|
ensure
|
265
|
+
timeout.stop
|
264
266
|
client.close
|
265
267
|
end
|
266
268
|
|
data/docs/index.md
CHANGED
@@ -8,45 +8,40 @@ next_title: Installing Polyphony
|
|
8
8
|
|
9
9
|
# Polyphony - fine-grained concurrency for Ruby
|
10
10
|
|
11
|
-
> Polyphony \| pəˈlɪf\(ə\)ni \|
|
12
|
-
> 1. _Music_ the style of simultaneously combining a number of parts, each
|
13
|
-
> forming an individual melody and harmonizing with each other.
|
14
|
-
> 2. _Programming_ a Ruby gem for concurrent programming focusing on performance
|
15
|
-
> and developer happiness.
|
16
|
-
|
17
11
|
Polyphony is a library for building concurrent applications in Ruby. Polyphony
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
12
|
+
implements a comprehensive
|
13
|
+
[fiber](https://ruby-doc.org/core-2.5.1/Fiber.html)-based concurrency model,
|
14
|
+
using [libev](https://github.com/enki/libev) as a high-performance event reactor
|
15
|
+
for I/O, timers, and other asynchronous events.
|
16
|
+
|
17
|
+
[Take the tutorial](getting-started/tutorial){: .btn .btn-blue .text-gamma }
|
18
|
+
{: .mt-6 .h-align-center }
|
23
19
|
|
24
20
|
## Focused on Developer Happiness
|
25
21
|
|
26
22
|
Polyphony is designed to make concurrent Ruby programming feel natural and
|
27
|
-
fluent. Polyphony
|
28
|
-
|
29
|
-
understand, and above all idiomatic.
|
23
|
+
fluent. The Polyphony API is easy to use, easy to understand, and above all
|
24
|
+
idiomatic.
|
30
25
|
|
31
26
|
## Optimized for High Performance
|
32
27
|
|
33
28
|
Polyphony offers high performance for I/O bound Ruby apps. Distributing
|
34
|
-
concurrent
|
35
|
-
consumption and reduces the cost of context-switching.
|
29
|
+
concurrent operations over fibers, instead of threads or processes, minimizes
|
30
|
+
memory consumption and reduces the cost of context-switching.
|
36
31
|
|
37
32
|
## Designed for Interoperability
|
38
33
|
|
39
|
-
Polyphony
|
40
|
-
`Socket` in a concurrent multi-fiber environment.
|
41
|
-
|
42
|
-
|
34
|
+
With Polyphony you can use any of the stock Ruby classes and modules like `IO`,
|
35
|
+
`Process`, `Socket` and `OpenSSL` in a concurrent multi-fiber environment. In
|
36
|
+
addition, Polyphony provides a structured model for exception handling that
|
37
|
+
builds on and enhances Ruby's exception handling system.
|
43
38
|
|
44
39
|
## A Growing Ecosystem
|
45
40
|
|
46
41
|
Polyphony includes a full-blown HTTP server implementation with integrated
|
47
|
-
support for HTTP
|
48
|
-
|
49
|
-
|
42
|
+
support for HTTP 2, WebSockets, TLS/SSL termination and more. Polyphony also
|
43
|
+
provides fiber-aware adapters for connecting to PostgreSQL and Redis. More
|
44
|
+
adapters are being developed.
|
50
45
|
|
51
46
|
## Features
|
52
47
|
|
data/ext/gyro/async.c
CHANGED
@@ -121,6 +121,33 @@ VALUE Gyro_Async_await(VALUE self) {
|
|
121
121
|
}
|
122
122
|
}
|
123
123
|
|
124
|
+
VALUE Gyro_Async_await_no_raise(VALUE self) {
|
125
|
+
struct Gyro_Async *async;
|
126
|
+
VALUE ret;
|
127
|
+
|
128
|
+
GetGyro_Async(self, async);
|
129
|
+
|
130
|
+
async->fiber = rb_fiber_current();
|
131
|
+
if (!async->active) {
|
132
|
+
async->active = 1;
|
133
|
+
async->ev_loop = Gyro_Selector_current_thread_ev_loop();
|
134
|
+
ev_async_start(async->ev_loop, &async->ev_async);
|
135
|
+
}
|
136
|
+
|
137
|
+
ret = Fiber_await();
|
138
|
+
RB_GC_GUARD(ret);
|
139
|
+
|
140
|
+
if (async->active) {
|
141
|
+
async->active = 0;
|
142
|
+
async->fiber = Qnil;
|
143
|
+
ev_async_stop(async->ev_loop, &async->ev_async);
|
144
|
+
async->value = Qnil;
|
145
|
+
}
|
146
|
+
|
147
|
+
return ret;
|
148
|
+
}
|
149
|
+
|
150
|
+
|
124
151
|
void Init_Gyro_Async() {
|
125
152
|
cGyro_Async = rb_define_class_under(mGyro, "Async", rb_cData);
|
126
153
|
rb_define_alloc_func(cGyro_Async, Gyro_Async_allocate);
|