polyphony 0.32 → 0.33
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/polyphony.svg)](http://rubygems.org/gems/polyphony)
|
4
|
+
[![Modulation Test](https://github.com/digital-fabric/polyphony/workflows/Tests/badge.svg)](https://github.com/digital-fabric/polyphony/actions?query=workflow%3ATests)
|
5
|
+
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](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);
|