polyphony 0.41 → 0.42
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/CHANGELOG.md +9 -0
- data/Gemfile.lock +5 -5
- data/Rakefile +1 -1
- data/TODO.md +19 -9
- data/docs/_config.yml +56 -7
- data/docs/_sass/custom/custom.scss +0 -30
- data/docs/_sass/overrides.scss +0 -46
- data/docs/{user-guide → _user-guide}/all-about-timers.md +0 -0
- data/docs/_user-guide/index.md +9 -0
- data/docs/{user-guide → _user-guide}/web-server.md +0 -0
- data/docs/api-reference/index.md +9 -0
- data/docs/api-reference/polyphony-process.md +1 -1
- data/docs/api-reference/thread.md +1 -1
- data/docs/faq.md +21 -11
- data/docs/getting-started/index.md +10 -0
- data/docs/getting-started/installing.md +2 -6
- data/docs/getting-started/overview.md +507 -0
- data/docs/getting-started/tutorial.md +27 -19
- data/docs/index.md +1 -1
- data/docs/main-concepts/concurrency.md +0 -5
- data/docs/main-concepts/design-principles.md +2 -12
- data/docs/main-concepts/index.md +9 -0
- data/examples/core/01-spinning-up-fibers.rb +1 -0
- data/examples/core/03-interrupting.rb +4 -1
- data/examples/core/04-handling-signals.rb +19 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +6 -18
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
- data/examples/performance/xx-array.rb +11 -0
- data/examples/performance/xx-fiber-switch.rb +9 -0
- data/examples/performance/xx-snooze.rb +15 -0
- data/ext/polyphony/fiber.c +0 -3
- data/ext/polyphony/libev_agent.c +234 -19
- data/ext/polyphony/libev_queue.c +3 -1
- data/ext/polyphony/polyphony.c +0 -10
- data/ext/polyphony/polyphony.h +6 -6
- data/ext/polyphony/thread.c +8 -36
- data/lib/polyphony.rb +5 -2
- data/lib/polyphony/core/channel.rb +2 -2
- data/lib/polyphony/core/global_api.rb +2 -2
- data/lib/polyphony/core/resource_pool.rb +2 -2
- data/lib/polyphony/extensions/core.rb +2 -3
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +1 -1
- data/test/test_agent.rb +49 -2
- metadata +16 -20
- data/docs/_includes/head.html +0 -40
- data/docs/_includes/nav.html +0 -51
- data/docs/_includes/prevnext.html +0 -17
- data/docs/_layouts/default.html +0 -106
- data/docs/api-reference.md +0 -11
- data/docs/api-reference/gyro-async.md +0 -57
- data/docs/api-reference/gyro-child.md +0 -29
- data/docs/api-reference/gyro-queue.md +0 -44
- data/docs/api-reference/gyro-timer.md +0 -51
- data/docs/api-reference/gyro.md +0 -25
- data/docs/getting-started.md +0 -10
- data/docs/main-concepts.md +0 -10
- data/docs/user-guide.md +0 -10
- data/examples/core/forever_sleep.rb +0 -19
@@ -1,13 +1,20 @@
|
|
1
1
|
---
|
2
2
|
layout: page
|
3
3
|
title: Tutorial
|
4
|
-
nav_order: 2
|
5
4
|
parent: Getting Started
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
nav_order: 3
|
6
|
+
---
|
7
|
+
|
8
|
+
# Tutorial
|
9
|
+
{: .no_toc }
|
10
|
+
|
11
|
+
## Table of contents
|
12
|
+
{: .no_toc .text-delta }
|
13
|
+
|
14
|
+
- TOC
|
15
|
+
{:toc}
|
16
|
+
|
9
17
|
---
|
10
|
-
# Polyphony: a Tutorial
|
11
18
|
|
12
19
|
Polyphony is a new Ruby library aimed at making writing concurrent Ruby apps
|
13
20
|
easy and fun. In this article, we'll introduce Polyphony's fiber-based
|
@@ -116,7 +123,7 @@ suspend # The main fiber suspends, waiting for all other work to finish
|
|
116
123
|
sleep 1 # The sleeper fiber goes to sleep
|
117
124
|
Gyro::Timer.new(1, 0).await # A timer event watcher is setup and yields
|
118
125
|
Thread.current.switch_fiber # Polyphony looks for other runnable fibers
|
119
|
-
Thread.current.
|
126
|
+
Thread.current.agent.poll # With no work left, the event loop is ran
|
120
127
|
fiber.schedule # The timer event fires, scheduling the sleeper fiber
|
121
128
|
# <= The sleep method returns
|
122
129
|
puts "Woke up"
|
@@ -218,7 +225,7 @@ this by setting up a timeout fiber that cancels the fiber dealing with the conne
|
|
218
225
|
def handle_client(client)
|
219
226
|
timeout = cancel_after(10)
|
220
227
|
while (data = client.gets)
|
221
|
-
timeout.
|
228
|
+
timeout.restart
|
222
229
|
client << data
|
223
230
|
end
|
224
231
|
rescue Polyphony::Cancel
|
@@ -230,18 +237,19 @@ ensure
|
|
230
237
|
end
|
231
238
|
```
|
232
239
|
|
233
|
-
The
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
240
|
+
The call to `#cancel_after` spins up a new fiber that will sleep for 10 seconds,
|
241
|
+
then cancel its parent. The call to `client.gets` blocks until new data is
|
242
|
+
available. If no new data is available, the `timeout` fiber will finish
|
243
|
+
sleeping, and then cancel the client handling fiber by raising a
|
244
|
+
`Polyphony::Cancel` exception. However, if new data is received, the `timeout`
|
245
|
+
fiber is restarted, causing to begin sleeping again for 10 seconds. If the
|
246
|
+
client has closed the connection, or some other exception occurs, the `timeout`
|
247
|
+
fiber is automatically stopped as it is a child of the client handling fiber.
|
248
|
+
|
249
|
+
The habit of always cleaning up using `ensure` in the face of potential
|
250
|
+
interruptions is a fundamental element of using Polyphony correctly. This makes
|
251
|
+
your code robust, even in a highly chaotic concurrent execution environment
|
252
|
+
where tasks can be started, restarted and interrupted at any time.
|
245
253
|
|
246
254
|
## Implementing graceful shutdown
|
247
255
|
|
data/docs/index.md
CHANGED
@@ -53,7 +53,7 @@ adapters are being developed.
|
|
53
53
|
* Natural, sequential programming style that makes it easy to reason about
|
54
54
|
concurrent code.
|
55
55
|
* Abstractions and constructs for controlling the execution of concurrent code:
|
56
|
-
supervisors,
|
56
|
+
supervisors, throttling, resource pools etc.
|
57
57
|
* Code can use native networking classes and libraries, growing support for
|
58
58
|
third-party gems such as `pg` and `redis`.
|
59
59
|
* Use stdlib classes such as `TCPServer` and `TCPSocket` and `Net::HTTP`.
|
@@ -130,16 +130,11 @@ Polyphony also provides several methods and constructs for controlling multiple
|
|
130
130
|
fibers. Methods like `cancel_after` and `move_on_after` allow interrupting a
|
131
131
|
fiber that's blocking on any arbitrary operation.
|
132
132
|
|
133
|
-
Cancel scopes \(borrowed from the brilliant Python library
|
134
|
-
[Trio](https://trio.readthedocs.io/en/stable/)\) allows cancelling ongoing
|
135
|
-
operations for any reason with more control over cancelling behaviour.
|
136
|
-
|
137
133
|
Some other constructs offered by Polyphony:
|
138
134
|
|
139
135
|
* `Mutex` - a mutex used to synchronize access to a single shared resource.
|
140
136
|
* `ResourcePool` - used for synchronizing access to a limited amount of shared
|
141
137
|
resources, for example a pool of database connections.
|
142
|
-
|
143
138
|
* `Throttler` - used for throttling repeating operations, for example throttling
|
144
139
|
access to a shared resource, or throttling incoming requests.
|
145
140
|
|
@@ -143,18 +143,8 @@ library. Polyphony's design is based on the following principles:
|
|
143
143
|
end
|
144
144
|
```
|
145
145
|
|
146
|
-
- Concurrency primitives should allow creating higher-order concurrent
|
147
|
-
constructs through composition.
|
148
|
-
cancel scopes:
|
149
|
-
|
150
|
-
```ruby
|
151
|
-
# wait for multiple fibers
|
152
|
-
supervise { |s|
|
153
|
-
clients.each { |client|
|
154
|
-
s.spin { client.puts 'Elvis has left the chatroom' }
|
155
|
-
}
|
156
|
-
}
|
157
|
-
```
|
146
|
+
- Concurrency primitives should allow creating higher-order concurrent
|
147
|
+
constructs through composition.
|
158
148
|
|
159
149
|
- The entire design should embrace fibers. There should be no callback-based
|
160
150
|
asynchronous APIs.
|
@@ -18,7 +18,8 @@ ensure
|
|
18
18
|
end
|
19
19
|
|
20
20
|
# The Kernel#cancel_after interrupts a blocking operation by raising a
|
21
|
-
# Polyphony::Cancel exception after the given timeout
|
21
|
+
# Polyphony::Cancel exception after the given timeout. If not rescued, the
|
22
|
+
# exception is propagated up the fiber hierarchy
|
22
23
|
spin do
|
23
24
|
# cancel after 1 second
|
24
25
|
cancel_after(1) { nap(:cancel, 2) }
|
@@ -26,6 +27,8 @@ rescue Polyphony::Cancel => e
|
|
26
27
|
puts "got exception: #{e}"
|
27
28
|
end
|
28
29
|
|
30
|
+
# The Kernel#move_on_after interrupts a blocking operation by raising a
|
31
|
+
# Polyphony::MoveOn exception, which is silently swallowed by the fiber
|
29
32
|
spin do
|
30
33
|
# move on after 1 second
|
31
34
|
move_on_after(1) do
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
# trap('TERM') do
|
7
|
+
# Polyphony.emit_signal_exception(::SystemExit)
|
8
|
+
# end
|
9
|
+
|
10
|
+
# trap('INT') do
|
11
|
+
# Polyphony.emit_signal_exception(::Interrupt)
|
12
|
+
# end
|
13
|
+
|
14
|
+
puts "go to sleep"
|
15
|
+
begin
|
16
|
+
sleep
|
17
|
+
ensure
|
18
|
+
puts "done sleeping"
|
19
|
+
end
|
@@ -35,23 +35,11 @@ def handle_request(client, parser)
|
|
35
35
|
client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
|
36
36
|
end
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
server = TCPServer.open('0.0.0.0', 1234)
|
39
|
+
puts "pid #{Process.pid}"
|
40
|
+
puts "listening on port 1234"
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
ensure
|
47
|
-
server&.close
|
42
|
+
loop do
|
43
|
+
client = server.accept
|
44
|
+
spin { handle_client(client) }
|
48
45
|
end
|
49
|
-
|
50
|
-
# every(1) {
|
51
|
-
# stats = Thread.current.fiber_scheduling_stats
|
52
|
-
# stats[:connection_count] = $connection_count
|
53
|
-
# puts "#{Time.now} #{stats}"
|
54
|
-
# }
|
55
|
-
|
56
|
-
puts "pid #{Process.pid}"
|
57
|
-
suspend
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
require 'http/parser'
|
6
|
+
|
7
|
+
$connection_count = 0
|
8
|
+
|
9
|
+
def handle_client(socket)
|
10
|
+
$connection_count += 1
|
11
|
+
parser = Http::Parser.new
|
12
|
+
reqs = []
|
13
|
+
parser.on_message_complete = proc do |env|
|
14
|
+
reqs << Object.new # parser
|
15
|
+
end
|
16
|
+
Thread.current.agent.read_loop(socket) do |data|
|
17
|
+
parser << data
|
18
|
+
while (req = reqs.shift)
|
19
|
+
handle_request(socket, req)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
rescue IOError, SystemCallError => e
|
23
|
+
# do nothing
|
24
|
+
ensure
|
25
|
+
$connection_count -= 1
|
26
|
+
socket&.close
|
27
|
+
end
|
28
|
+
|
29
|
+
def handle_request(client, parser)
|
30
|
+
status_code = "200 OK"
|
31
|
+
data = "Hello world!\n"
|
32
|
+
headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
|
33
|
+
client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
|
34
|
+
end
|
35
|
+
|
36
|
+
spin do
|
37
|
+
server = TCPServer.open('0.0.0.0', 1234)
|
38
|
+
puts "listening on port 1234"
|
39
|
+
|
40
|
+
Thread.current.agent.accept_loop(server) do |client|
|
41
|
+
spin { handle_client(client) }
|
42
|
+
end
|
43
|
+
# loop do
|
44
|
+
# client = server.accept
|
45
|
+
# spin { handle_client(client) }
|
46
|
+
# end
|
47
|
+
ensure
|
48
|
+
server&.close
|
49
|
+
end
|
50
|
+
|
51
|
+
# every(1) {
|
52
|
+
# stats = Thread.current.fiber_scheduling_stats
|
53
|
+
# stats[:connection_count] = $connection_count
|
54
|
+
# puts "#{Time.now} #{stats}"
|
55
|
+
# }
|
56
|
+
|
57
|
+
puts "pid #{Process.pid}"
|
58
|
+
suspend
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'polyphony'
|
3
|
+
|
4
|
+
Y = ARGV[0] ? ARGV[0].to_i : 1
|
5
|
+
|
6
|
+
count = 0
|
7
|
+
Y.times do
|
8
|
+
spin { loop { count += 1; snooze } }
|
9
|
+
end
|
10
|
+
|
11
|
+
t0 = Time.now
|
12
|
+
sleep 10
|
13
|
+
elapsed = Time.now - t0
|
14
|
+
rate = count / elapsed
|
15
|
+
puts "concurrency: #{Y} rate: #{rate} switchpoints per second"
|
data/ext/polyphony/fiber.c
CHANGED
@@ -68,10 +68,7 @@ void Fiber_make_runnable(VALUE fiber, VALUE value) {
|
|
68
68
|
Thread_schedule_fiber(thread, fiber, value);
|
69
69
|
}
|
70
70
|
else {
|
71
|
-
VALUE caller;
|
72
71
|
rb_warn("No thread set for fiber (fiber, value, caller):");
|
73
|
-
caller = rb_funcall(rb_cObject, rb_intern("caller"), 0);
|
74
|
-
INSPECT(3, fiber, value, caller);
|
75
72
|
}
|
76
73
|
}
|
77
74
|
|
data/ext/polyphony/libev_agent.c
CHANGED
@@ -9,6 +9,7 @@ struct LibevAgent_t {
|
|
9
9
|
struct ev_loop *ev_loop;
|
10
10
|
struct ev_async break_async;
|
11
11
|
int running;
|
12
|
+
int ref_count;
|
12
13
|
int run_no_wait_count;
|
13
14
|
};
|
14
15
|
|
@@ -49,6 +50,7 @@ static VALUE LibevAgent_initialize(VALUE self) {
|
|
49
50
|
ev_unref(agent->ev_loop); // don't count the break_async watcher
|
50
51
|
|
51
52
|
agent->running = 0;
|
53
|
+
agent->ref_count = 0;
|
52
54
|
agent->run_no_wait_count = 0;
|
53
55
|
|
54
56
|
return Qnil;
|
@@ -82,6 +84,36 @@ VALUE LibevAgent_post_fork(VALUE self) {
|
|
82
84
|
return self;
|
83
85
|
}
|
84
86
|
|
87
|
+
VALUE LibevAgent_ref(VALUE self) {
|
88
|
+
struct LibevAgent_t *agent;
|
89
|
+
GetLibevAgent(self, agent);
|
90
|
+
|
91
|
+
agent->ref_count++;
|
92
|
+
return self;
|
93
|
+
}
|
94
|
+
|
95
|
+
VALUE LibevAgent_unref(VALUE self) {
|
96
|
+
struct LibevAgent_t *agent;
|
97
|
+
GetLibevAgent(self, agent);
|
98
|
+
|
99
|
+
agent->ref_count--;
|
100
|
+
return self;
|
101
|
+
}
|
102
|
+
|
103
|
+
int LibevAgent_ref_count(VALUE self) {
|
104
|
+
struct LibevAgent_t *agent;
|
105
|
+
GetLibevAgent(self, agent);
|
106
|
+
|
107
|
+
return agent->ref_count;
|
108
|
+
}
|
109
|
+
|
110
|
+
void LibevAgent_reset_ref_count(VALUE self) {
|
111
|
+
struct LibevAgent_t *agent;
|
112
|
+
GetLibevAgent(self, agent);
|
113
|
+
|
114
|
+
agent->ref_count = 0;
|
115
|
+
}
|
116
|
+
|
85
117
|
VALUE LibevAgent_pending_count(VALUE self) {
|
86
118
|
int count;
|
87
119
|
struct LibevAgent_t *agent;
|
@@ -143,6 +175,8 @@ struct io_internal_read_struct {
|
|
143
175
|
size_t capa;
|
144
176
|
};
|
145
177
|
|
178
|
+
#define StringValue(v) rb_string_value(&(v))
|
179
|
+
|
146
180
|
int io_setstrbuf(VALUE *str, long len) {
|
147
181
|
#ifdef _WIN32
|
148
182
|
len = (len + 1) & ~1L; /* round up for wide char */
|
@@ -206,6 +240,21 @@ static void LibevAgent_io_callback(EV_P_ ev_io *w, int revents)
|
|
206
240
|
Fiber_make_runnable(watcher->fiber, Qnil);
|
207
241
|
}
|
208
242
|
|
243
|
+
inline VALUE libev_await(struct LibevAgent_t *agent) {
|
244
|
+
VALUE ret;
|
245
|
+
agent->ref_count++;
|
246
|
+
ret = Thread_switch_fiber(rb_thread_current());
|
247
|
+
agent->ref_count--;
|
248
|
+
RB_GC_GUARD(ret);
|
249
|
+
return ret;
|
250
|
+
}
|
251
|
+
|
252
|
+
VALUE libev_agent_await(VALUE self) {
|
253
|
+
struct LibevAgent_t *agent;
|
254
|
+
GetLibevAgent(self, agent);
|
255
|
+
return libev_await(agent);
|
256
|
+
}
|
257
|
+
|
209
258
|
VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
|
210
259
|
struct LibevAgent_t *agent;
|
211
260
|
struct libev_io watcher;
|
@@ -245,10 +294,11 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
|
|
245
294
|
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
|
246
295
|
}
|
247
296
|
ev_io_start(agent->ev_loop, &watcher.io);
|
248
|
-
switchpoint_result =
|
297
|
+
switchpoint_result = libev_await(agent);
|
249
298
|
ev_io_stop(agent->ev_loop, &watcher.io);
|
250
|
-
if (TEST_EXCEPTION(switchpoint_result))
|
299
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
251
300
|
goto error;
|
301
|
+
}
|
252
302
|
}
|
253
303
|
else
|
254
304
|
rb_syserr_fail(e, strerror(e));
|
@@ -256,6 +306,14 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
|
|
256
306
|
}
|
257
307
|
}
|
258
308
|
|
309
|
+
if (watcher.fiber == Qnil) {
|
310
|
+
Fiber_make_runnable(rb_fiber_current(), Qnil);
|
311
|
+
switchpoint_result = Thread_switch_fiber(rb_thread_current());
|
312
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
313
|
+
goto error;
|
314
|
+
}
|
315
|
+
}
|
316
|
+
|
259
317
|
if (total == 0) return Qnil;
|
260
318
|
|
261
319
|
io_set_read_length(str, total, shrinkable);
|
@@ -269,6 +327,86 @@ error:
|
|
269
327
|
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
270
328
|
}
|
271
329
|
|
330
|
+
VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
|
331
|
+
|
332
|
+
#define PREPARE_STR() { \
|
333
|
+
str = Qnil; \
|
334
|
+
shrinkable = io_setstrbuf(&str, len); \
|
335
|
+
buf = RSTRING_PTR(str); \
|
336
|
+
total = 0; \
|
337
|
+
}
|
338
|
+
|
339
|
+
#define YIELD_STR() { \
|
340
|
+
io_set_read_length(str, total, shrinkable); \
|
341
|
+
io_enc_str(str, fptr); \
|
342
|
+
rb_yield(str); \
|
343
|
+
PREPARE_STR(); \
|
344
|
+
}
|
345
|
+
|
346
|
+
struct LibevAgent_t *agent;
|
347
|
+
struct libev_io watcher;
|
348
|
+
rb_io_t *fptr;
|
349
|
+
VALUE str;
|
350
|
+
int total;
|
351
|
+
int len = 8192;
|
352
|
+
int shrinkable;
|
353
|
+
char *buf;
|
354
|
+
VALUE switchpoint_result = Qnil;
|
355
|
+
VALUE underlying_io = rb_iv_get(io, "@io");
|
356
|
+
|
357
|
+
PREPARE_STR();
|
358
|
+
|
359
|
+
GetLibevAgent(self, agent);
|
360
|
+
if (underlying_io != Qnil) io = underlying_io;
|
361
|
+
GetOpenFile(io, fptr);
|
362
|
+
rb_io_check_byte_readable(fptr);
|
363
|
+
rb_io_set_nonblock(fptr);
|
364
|
+
watcher.fiber = Qnil;
|
365
|
+
|
366
|
+
OBJ_TAINT(str);
|
367
|
+
|
368
|
+
while (1) {
|
369
|
+
int n = read(fptr->fd, buf, len);
|
370
|
+
if (n == 0)
|
371
|
+
break;
|
372
|
+
if (n > 0) {
|
373
|
+
total = n;
|
374
|
+
YIELD_STR();
|
375
|
+
Fiber_make_runnable(rb_fiber_current(), Qnil);
|
376
|
+
switchpoint_result = Thread_switch_fiber(rb_thread_current());
|
377
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
378
|
+
goto error;
|
379
|
+
}
|
380
|
+
}
|
381
|
+
else {
|
382
|
+
int e = errno;
|
383
|
+
if ((e == EWOULDBLOCK || e == EAGAIN)) {
|
384
|
+
if (watcher.fiber == Qnil) {
|
385
|
+
watcher.fiber = rb_fiber_current();
|
386
|
+
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
|
387
|
+
}
|
388
|
+
ev_io_start(agent->ev_loop, &watcher.io);
|
389
|
+
switchpoint_result = libev_await(agent);
|
390
|
+
ev_io_stop(agent->ev_loop, &watcher.io);
|
391
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
392
|
+
goto error;
|
393
|
+
}
|
394
|
+
}
|
395
|
+
else
|
396
|
+
rb_syserr_fail(e, strerror(e));
|
397
|
+
// rb_syserr_fail_path(e, fptr->pathv);
|
398
|
+
}
|
399
|
+
}
|
400
|
+
|
401
|
+
RB_GC_GUARD(str);
|
402
|
+
RB_GC_GUARD(watcher.fiber);
|
403
|
+
RB_GC_GUARD(switchpoint_result);
|
404
|
+
|
405
|
+
return io;
|
406
|
+
error:
|
407
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
408
|
+
}
|
409
|
+
|
272
410
|
VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
273
411
|
struct LibevAgent_t *agent;
|
274
412
|
struct libev_io watcher;
|
@@ -289,19 +427,21 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
|
289
427
|
while (left > 0) {
|
290
428
|
int result = write(fptr->fd, buf, left);
|
291
429
|
if (result < 0) {
|
292
|
-
|
430
|
+
int e = errno;
|
431
|
+
if (e == EAGAIN) {
|
293
432
|
if (watcher.fiber == Qnil) {
|
294
433
|
watcher.fiber = rb_fiber_current();
|
295
434
|
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_WRITE);
|
296
435
|
}
|
297
436
|
ev_io_start(agent->ev_loop, &watcher.io);
|
298
|
-
switchpoint_result =
|
437
|
+
switchpoint_result = libev_await(agent);
|
299
438
|
ev_io_stop(agent->ev_loop, &watcher.io);
|
300
439
|
if (TEST_EXCEPTION(switchpoint_result))
|
301
440
|
goto error;
|
302
441
|
}
|
303
442
|
else {
|
304
|
-
|
443
|
+
rb_syserr_fail(e, strerror(e));
|
444
|
+
// rb_syserr_fail_path(e, fptr->pathv);
|
305
445
|
|
306
446
|
}
|
307
447
|
}
|
@@ -311,6 +451,14 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
|
311
451
|
}
|
312
452
|
}
|
313
453
|
|
454
|
+
if (watcher.fiber == Qnil) {
|
455
|
+
Fiber_make_runnable(rb_fiber_current(), Qnil);
|
456
|
+
switchpoint_result = Thread_switch_fiber(rb_thread_current());
|
457
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
458
|
+
goto error;
|
459
|
+
}
|
460
|
+
}
|
461
|
+
|
314
462
|
RB_GC_GUARD(watcher.fiber);
|
315
463
|
RB_GC_GUARD(switchpoint_result);
|
316
464
|
|
@@ -321,16 +469,6 @@ error:
|
|
321
469
|
|
322
470
|
///////////////////////////////////////////////////////////////////////////
|
323
471
|
|
324
|
-
struct rsock_send_arg {
|
325
|
-
int fd, flags;
|
326
|
-
VALUE mesg;
|
327
|
-
struct sockaddr *to;
|
328
|
-
socklen_t tolen;
|
329
|
-
};
|
330
|
-
|
331
|
-
#define StringValue(v) rb_string_value(&(v))
|
332
|
-
#define IS_ADDRINFO(obj) rb_typeddata_is_kind_of((obj), &addrinfo_type)
|
333
|
-
|
334
472
|
VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
335
473
|
struct LibevAgent_t *agent;
|
336
474
|
struct libev_io watcher;
|
@@ -339,6 +477,8 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
339
477
|
struct sockaddr addr;
|
340
478
|
socklen_t len = (socklen_t)sizeof addr;
|
341
479
|
VALUE switchpoint_result = Qnil;
|
480
|
+
VALUE underlying_sock = rb_iv_get(sock, "@io");
|
481
|
+
if (underlying_sock != Qnil) sock = underlying_sock;
|
342
482
|
|
343
483
|
GetLibevAgent(self, agent);
|
344
484
|
GetOpenFile(sock, fptr);
|
@@ -354,7 +494,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
354
494
|
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
|
355
495
|
}
|
356
496
|
ev_io_start(agent->ev_loop, &watcher.io);
|
357
|
-
switchpoint_result =
|
497
|
+
switchpoint_result = libev_await(agent);
|
358
498
|
ev_io_stop(agent->ev_loop, &watcher.io);
|
359
499
|
|
360
500
|
TEST_RESUME_EXCEPTION(switchpoint_result);
|
@@ -375,16 +515,86 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
375
515
|
rb_io_ascii8bit_binmode(connection);
|
376
516
|
rb_io_set_nonblock(fp);
|
377
517
|
rb_io_synchronized(fp);
|
518
|
+
|
378
519
|
// if (rsock_do_not_reverse_lookup) {
|
379
520
|
// fp->mode |= FMODE_NOREVLOOKUP;
|
380
521
|
// }
|
381
522
|
|
523
|
+
if (watcher.fiber == Qnil) {
|
524
|
+
Fiber_make_runnable(rb_fiber_current(), Qnil);
|
525
|
+
switchpoint_result = Thread_switch_fiber(rb_thread_current());
|
526
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
527
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
528
|
+
}
|
529
|
+
}
|
530
|
+
|
382
531
|
return connection;
|
383
532
|
}
|
384
533
|
}
|
385
534
|
return Qnil;
|
386
535
|
}
|
387
536
|
|
537
|
+
VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
|
538
|
+
struct LibevAgent_t *agent;
|
539
|
+
struct libev_io watcher;
|
540
|
+
rb_io_t *fptr;
|
541
|
+
int fd;
|
542
|
+
struct sockaddr addr;
|
543
|
+
socklen_t len = (socklen_t)sizeof addr;
|
544
|
+
VALUE switchpoint_result = Qnil;
|
545
|
+
VALUE connection = Qnil;
|
546
|
+
VALUE underlying_sock = rb_iv_get(sock, "@io");
|
547
|
+
if (underlying_sock != Qnil) sock = underlying_sock;
|
548
|
+
|
549
|
+
GetLibevAgent(self, agent);
|
550
|
+
GetOpenFile(sock, fptr);
|
551
|
+
rb_io_set_nonblock(fptr);
|
552
|
+
watcher.fiber = Qnil;
|
553
|
+
|
554
|
+
while (1) {
|
555
|
+
fd = accept(fptr->fd, &addr, &len);
|
556
|
+
if (fd < 0) {
|
557
|
+
int e = errno;
|
558
|
+
if (e == EWOULDBLOCK || e == EAGAIN) {
|
559
|
+
if (watcher.fiber == Qnil) {
|
560
|
+
watcher.fiber = rb_fiber_current();
|
561
|
+
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
|
562
|
+
}
|
563
|
+
ev_io_start(agent->ev_loop, &watcher.io);
|
564
|
+
switchpoint_result = libev_await(agent);
|
565
|
+
ev_io_stop(agent->ev_loop, &watcher.io);
|
566
|
+
|
567
|
+
TEST_RESUME_EXCEPTION(switchpoint_result);
|
568
|
+
}
|
569
|
+
else
|
570
|
+
rb_syserr_fail(e, strerror(e));
|
571
|
+
// rb_syserr_fail_path(e, fptr->pathv);
|
572
|
+
}
|
573
|
+
else {
|
574
|
+
rb_io_t *fp;
|
575
|
+
connection = rb_obj_alloc(cTCPSocket);
|
576
|
+
MakeOpenFile(connection, fp);
|
577
|
+
rb_update_max_fd(fd);
|
578
|
+
fp->fd = fd;
|
579
|
+
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
580
|
+
rb_io_ascii8bit_binmode(connection);
|
581
|
+
rb_io_set_nonblock(fp);
|
582
|
+
rb_io_synchronized(fp);
|
583
|
+
|
584
|
+
rb_yield(connection);
|
585
|
+
connection = Qnil;
|
586
|
+
|
587
|
+
Fiber_make_runnable(rb_fiber_current(), Qnil);
|
588
|
+
switchpoint_result = Thread_switch_fiber(rb_thread_current());
|
589
|
+
TEST_RESUME_EXCEPTION(switchpoint_result);
|
590
|
+
}
|
591
|
+
}
|
592
|
+
|
593
|
+
RB_GC_GUARD(connection);
|
594
|
+
RB_GC_GUARD(watcher.fiber);
|
595
|
+
RB_GC_GUARD(switchpoint_result);
|
596
|
+
}
|
597
|
+
|
388
598
|
VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
|
389
599
|
struct LibevAgent_t *agent;
|
390
600
|
struct libev_io watcher;
|
@@ -400,7 +610,7 @@ VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
|
|
400
610
|
watcher.fiber = rb_fiber_current();
|
401
611
|
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, events);
|
402
612
|
ev_io_start(agent->ev_loop, &watcher.io);
|
403
|
-
switchpoint_result =
|
613
|
+
switchpoint_result = libev_await(agent);
|
404
614
|
ev_io_stop(agent->ev_loop, &watcher.io);
|
405
615
|
|
406
616
|
TEST_RESUME_EXCEPTION(switchpoint_result);
|
@@ -430,7 +640,7 @@ VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
|
|
430
640
|
ev_timer_init(&watcher.timer, LibevAgent_timer_callback, NUM2DBL(duration), 0.);
|
431
641
|
ev_timer_start(agent->ev_loop, &watcher.timer);
|
432
642
|
|
433
|
-
switchpoint_result =
|
643
|
+
switchpoint_result = libev_await(agent);
|
434
644
|
ev_timer_stop(agent->ev_loop, &watcher.timer);
|
435
645
|
|
436
646
|
TEST_RESUME_EXCEPTION(switchpoint_result);
|
@@ -464,7 +674,7 @@ VALUE LibevAgent_waitpid(VALUE self, VALUE pid) {
|
|
464
674
|
ev_child_init(&watcher.child, LibevAgent_child_callback, NUM2INT(pid), 0);
|
465
675
|
ev_child_start(agent->ev_loop, &watcher.child);
|
466
676
|
|
467
|
-
switchpoint_result =
|
677
|
+
switchpoint_result = libev_await(agent);
|
468
678
|
ev_child_stop(agent->ev_loop, &watcher.child);
|
469
679
|
|
470
680
|
TEST_RESUME_EXCEPTION(switchpoint_result);
|
@@ -491,12 +701,17 @@ void Init_LibevAgent() {
|
|
491
701
|
rb_define_method(cLibevAgent, "post_fork", LibevAgent_post_fork, 0);
|
492
702
|
rb_define_method(cLibevAgent, "pending_count", LibevAgent_pending_count, 0);
|
493
703
|
|
704
|
+
rb_define_method(cLibevAgent, "ref", LibevAgent_ref, 0);
|
705
|
+
rb_define_method(cLibevAgent, "unref", LibevAgent_unref, 0);
|
706
|
+
|
494
707
|
rb_define_method(cLibevAgent, "poll", LibevAgent_poll, 3);
|
495
708
|
rb_define_method(cLibevAgent, "break", LibevAgent_break, 0);
|
496
709
|
|
497
710
|
rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
|
711
|
+
rb_define_method(cLibevAgent, "read_loop", LibevAgent_read_loop, 1);
|
498
712
|
rb_define_method(cLibevAgent, "write", LibevAgent_write, 2);
|
499
713
|
rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
|
714
|
+
rb_define_method(cLibevAgent, "accept_loop", LibevAgent_accept_loop, 1);
|
500
715
|
rb_define_method(cLibevAgent, "wait_io", LibevAgent_wait_io, 2);
|
501
716
|
rb_define_method(cLibevAgent, "sleep", LibevAgent_sleep, 1);
|
502
717
|
rb_define_method(cLibevAgent, "waitpid", LibevAgent_waitpid, 1);
|