polyphony 0.41 → 0.42
Sign up to get free protection for your applications and to get access to all the features.
- 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);
|