polyphony 0.41 → 0.43.3
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 +30 -0
- data/Gemfile.lock +6 -6
- data/README.md +0 -1
- data/Rakefile +1 -1
- data/TODO.md +18 -9
- data/docs/_config.yml +56 -7
- data/docs/_sass/custom/custom.scss +6 -26
- 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/favicon.ico +0 -0
- data/docs/getting-started/index.md +10 -0
- data/docs/getting-started/installing.md +2 -6
- data/docs/getting-started/overview.md +486 -0
- data/docs/getting-started/tutorial.md +27 -19
- data/docs/index.md +6 -2
- 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/docs/polyphony-logo.png +0 -0
- data/examples/adapters/concurrent-ruby.rb +9 -0
- data/examples/adapters/redis_blpop.rb +12 -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/core/xx-daemon.rb +14 -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 +303 -81
- data/ext/polyphony/libev_queue.c +8 -5
- data/ext/polyphony/polyphony.c +0 -16
- data/ext/polyphony/polyphony.h +6 -6
- data/ext/polyphony/polyphony_ext.c +0 -2
- data/ext/polyphony/thread.c +8 -42
- data/lib/polyphony.rb +29 -2
- data/lib/polyphony/adapters/redis.rb +3 -2
- data/lib/polyphony/core/channel.rb +2 -2
- data/lib/polyphony/core/global_api.rb +6 -4
- data/lib/polyphony/core/resource_pool.rb +19 -9
- data/lib/polyphony/extensions/core.rb +8 -3
- data/lib/polyphony/extensions/fiber.rb +0 -12
- data/lib/polyphony/extensions/io.rb +4 -0
- data/lib/polyphony/extensions/openssl.rb +34 -10
- data/lib/polyphony/extensions/socket.rb +2 -2
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +1 -1
- data/test/test_agent.rb +59 -6
- data/test/test_fiber.rb +3 -3
- data/test/test_global_api.rb +48 -15
- data/test/test_resource_pool.rb +12 -0
- data/test/test_socket.rb +5 -4
- data/test/test_throttler.rb +6 -5
- metadata +21 -21
- 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
- data/ext/polyphony/socket.c +0 -213
@@ -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
@@ -6,7 +6,11 @@ permalink: /
|
|
6
6
|
next_title: Installing Polyphony
|
7
7
|
---
|
8
8
|
|
9
|
-
# Polyphony
|
9
|
+
# Polyphony
|
10
|
+
{:.text-center .logo-title}
|
11
|
+
|
12
|
+
## Fine-grained concurrency for Ruby
|
13
|
+
{:.text-center .logo-title}
|
10
14
|
|
11
15
|
Polyphony is a library for building concurrent applications in Ruby. Polyphony
|
12
16
|
implements a comprehensive
|
@@ -53,7 +57,7 @@ adapters are being developed.
|
|
53
57
|
* Natural, sequential programming style that makes it easy to reason about
|
54
58
|
concurrent code.
|
55
59
|
* Abstractions and constructs for controlling the execution of concurrent code:
|
56
|
-
supervisors,
|
60
|
+
supervisors, throttling, resource pools etc.
|
57
61
|
* Code can use native networking classes and libraries, growing support for
|
58
62
|
third-party gems such as `pg` and `redis`.
|
59
63
|
* 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.
|
Binary file
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony/adapters/redis'
|
5
|
+
# require 'redis'
|
6
|
+
|
7
|
+
redis = Redis.new(host: ENV['REDISHOST'] || 'localhost')
|
8
|
+
|
9
|
+
redis.lpush("queue_key", "omgvalue")
|
10
|
+
puts "len: #{redis.llen("queue_key")}"
|
11
|
+
result = redis.blpop("queue_key")
|
12
|
+
puts result.inspect
|
@@ -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
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
Exception.__disable_sanitized_backtrace__ = true
|
7
|
+
|
8
|
+
puts "pid: #{Process.pid}"
|
9
|
+
|
10
|
+
Process.daemon(true, true)
|
11
|
+
|
12
|
+
Polyphony::ThreadPool.process do
|
13
|
+
puts "Hello world from pid #{Process.pid}"
|
14
|
+
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
|
+
socket.read_loop 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
@@ -1,6 +1,8 @@
|
|
1
|
+
#include <netdb.h>
|
2
|
+
#include <sys/socket.h>
|
3
|
+
|
1
4
|
#include "polyphony.h"
|
2
5
|
#include "../libev/ev.h"
|
3
|
-
#include <sys/socket.h>
|
4
6
|
|
5
7
|
VALUE cLibevAgent = Qnil;
|
6
8
|
VALUE cTCPSocket;
|
@@ -9,6 +11,7 @@ struct LibevAgent_t {
|
|
9
11
|
struct ev_loop *ev_loop;
|
10
12
|
struct ev_async break_async;
|
11
13
|
int running;
|
14
|
+
int ref_count;
|
12
15
|
int run_no_wait_count;
|
13
16
|
};
|
14
17
|
|
@@ -49,6 +52,7 @@ static VALUE LibevAgent_initialize(VALUE self) {
|
|
49
52
|
ev_unref(agent->ev_loop); // don't count the break_async watcher
|
50
53
|
|
51
54
|
agent->running = 0;
|
55
|
+
agent->ref_count = 0;
|
52
56
|
agent->run_no_wait_count = 0;
|
53
57
|
|
54
58
|
return Qnil;
|
@@ -82,6 +86,36 @@ VALUE LibevAgent_post_fork(VALUE self) {
|
|
82
86
|
return self;
|
83
87
|
}
|
84
88
|
|
89
|
+
VALUE LibevAgent_ref(VALUE self) {
|
90
|
+
struct LibevAgent_t *agent;
|
91
|
+
GetLibevAgent(self, agent);
|
92
|
+
|
93
|
+
agent->ref_count++;
|
94
|
+
return self;
|
95
|
+
}
|
96
|
+
|
97
|
+
VALUE LibevAgent_unref(VALUE self) {
|
98
|
+
struct LibevAgent_t *agent;
|
99
|
+
GetLibevAgent(self, agent);
|
100
|
+
|
101
|
+
agent->ref_count--;
|
102
|
+
return self;
|
103
|
+
}
|
104
|
+
|
105
|
+
int LibevAgent_ref_count(VALUE self) {
|
106
|
+
struct LibevAgent_t *agent;
|
107
|
+
GetLibevAgent(self, agent);
|
108
|
+
|
109
|
+
return agent->ref_count;
|
110
|
+
}
|
111
|
+
|
112
|
+
void LibevAgent_reset_ref_count(VALUE self) {
|
113
|
+
struct LibevAgent_t *agent;
|
114
|
+
GetLibevAgent(self, agent);
|
115
|
+
|
116
|
+
agent->ref_count = 0;
|
117
|
+
}
|
118
|
+
|
85
119
|
VALUE LibevAgent_pending_count(VALUE self) {
|
86
120
|
int count;
|
87
121
|
struct LibevAgent_t *agent;
|
@@ -96,7 +130,7 @@ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue
|
|
96
130
|
GetLibevAgent(self, agent);
|
97
131
|
|
98
132
|
if (is_nowait) {
|
99
|
-
|
133
|
+
long runnable_count = RARRAY_LEN(queue);
|
100
134
|
agent->run_no_wait_count++;
|
101
135
|
if (agent->run_no_wait_count < runnable_count || agent->run_no_wait_count < 10)
|
102
136
|
return self;
|
@@ -143,6 +177,8 @@ struct io_internal_read_struct {
|
|
143
177
|
size_t capa;
|
144
178
|
};
|
145
179
|
|
180
|
+
#define StringValue(v) rb_string_value(&(v))
|
181
|
+
|
146
182
|
int io_setstrbuf(VALUE *str, long len) {
|
147
183
|
#ifdef _WIN32
|
148
184
|
len = (len + 1) & ~1L; /* round up for wide char */
|
@@ -200,17 +236,52 @@ struct libev_io {
|
|
200
236
|
VALUE fiber;
|
201
237
|
};
|
202
238
|
|
203
|
-
|
239
|
+
void LibevAgent_io_callback(EV_P_ ev_io *w, int revents)
|
204
240
|
{
|
205
241
|
struct libev_io *watcher = (struct libev_io *)w;
|
206
242
|
Fiber_make_runnable(watcher->fiber, Qnil);
|
207
243
|
}
|
208
244
|
|
245
|
+
inline VALUE libev_await(struct LibevAgent_t *agent) {
|
246
|
+
VALUE ret;
|
247
|
+
agent->ref_count++;
|
248
|
+
ret = Thread_switch_fiber(rb_thread_current());
|
249
|
+
agent->ref_count--;
|
250
|
+
RB_GC_GUARD(ret);
|
251
|
+
return ret;
|
252
|
+
}
|
253
|
+
|
254
|
+
VALUE libev_agent_await(VALUE self) {
|
255
|
+
struct LibevAgent_t *agent;
|
256
|
+
GetLibevAgent(self, agent);
|
257
|
+
return libev_await(agent);
|
258
|
+
}
|
259
|
+
|
260
|
+
VALUE libev_io_wait(struct LibevAgent_t *agent, struct libev_io *watcher, rb_io_t *fptr, int flags) {
|
261
|
+
VALUE switchpoint_result;
|
262
|
+
|
263
|
+
if (watcher->fiber == Qnil) {
|
264
|
+
watcher->fiber = rb_fiber_current();
|
265
|
+
ev_io_init(&watcher->io, LibevAgent_io_callback, fptr->fd, flags);
|
266
|
+
}
|
267
|
+
ev_io_start(agent->ev_loop, &watcher->io);
|
268
|
+
switchpoint_result = libev_await(agent);
|
269
|
+
ev_io_stop(agent->ev_loop, &watcher->io);
|
270
|
+
|
271
|
+
RB_GC_GUARD(switchpoint_result);
|
272
|
+
return switchpoint_result;
|
273
|
+
}
|
274
|
+
|
275
|
+
VALUE libev_snooze() {
|
276
|
+
Fiber_make_runnable(rb_fiber_current(), Qnil);
|
277
|
+
return Thread_switch_fiber(rb_thread_current());
|
278
|
+
}
|
279
|
+
|
209
280
|
VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
|
210
281
|
struct LibevAgent_t *agent;
|
211
282
|
struct libev_io watcher;
|
212
283
|
rb_io_t *fptr;
|
213
|
-
|
284
|
+
long len = NUM2INT(length);
|
214
285
|
int shrinkable = io_setstrbuf(&str, len);
|
215
286
|
char *buf = RSTRING_PTR(str);
|
216
287
|
long total = 0;
|
@@ -228,32 +299,25 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
|
|
228
299
|
OBJ_TAINT(str);
|
229
300
|
|
230
301
|
while (len > 0) {
|
231
|
-
|
232
|
-
if (n
|
233
|
-
|
234
|
-
|
302
|
+
ssize_t n = read(fptr->fd, buf, len);
|
303
|
+
if (n < 0) {
|
304
|
+
int e = errno;
|
305
|
+
if (e != EWOULDBLOCK && e != EAGAIN) rb_syserr_fail(e, strerror(e));
|
306
|
+
|
307
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
|
308
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
309
|
+
}
|
310
|
+
else {
|
311
|
+
switchpoint_result = libev_snooze();
|
312
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
313
|
+
|
314
|
+
if (n == 0) break; // EOF
|
315
|
+
|
235
316
|
total = total + n;
|
236
317
|
buf += n;
|
237
318
|
len -= n;
|
238
319
|
if (!read_to_eof || (len == 0)) break;
|
239
320
|
}
|
240
|
-
else {
|
241
|
-
int e = errno;
|
242
|
-
if ((e == EWOULDBLOCK || e == EAGAIN)) {
|
243
|
-
if (watcher.fiber == Qnil) {
|
244
|
-
watcher.fiber = rb_fiber_current();
|
245
|
-
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, EV_READ);
|
246
|
-
}
|
247
|
-
ev_io_start(agent->ev_loop, &watcher.io);
|
248
|
-
switchpoint_result = Polyphony_switchpoint();
|
249
|
-
ev_io_stop(agent->ev_loop, &watcher.io);
|
250
|
-
if (TEST_EXCEPTION(switchpoint_result))
|
251
|
-
goto error;
|
252
|
-
}
|
253
|
-
else
|
254
|
-
rb_syserr_fail(e, strerror(e));
|
255
|
-
// rb_syserr_fail_path(e, fptr->pathv);
|
256
|
-
}
|
257
321
|
}
|
258
322
|
|
259
323
|
if (total == 0) return Qnil;
|
@@ -269,6 +333,78 @@ error:
|
|
269
333
|
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
270
334
|
}
|
271
335
|
|
336
|
+
VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
|
337
|
+
|
338
|
+
#define PREPARE_STR() { \
|
339
|
+
str = Qnil; \
|
340
|
+
shrinkable = io_setstrbuf(&str, len); \
|
341
|
+
buf = RSTRING_PTR(str); \
|
342
|
+
total = 0; \
|
343
|
+
}
|
344
|
+
|
345
|
+
#define YIELD_STR() { \
|
346
|
+
io_set_read_length(str, total, shrinkable); \
|
347
|
+
io_enc_str(str, fptr); \
|
348
|
+
rb_yield(str); \
|
349
|
+
PREPARE_STR(); \
|
350
|
+
}
|
351
|
+
|
352
|
+
struct LibevAgent_t *agent;
|
353
|
+
struct libev_io watcher;
|
354
|
+
rb_io_t *fptr;
|
355
|
+
VALUE str;
|
356
|
+
long total;
|
357
|
+
long len = 8192;
|
358
|
+
int shrinkable;
|
359
|
+
char *buf;
|
360
|
+
VALUE switchpoint_result = Qnil;
|
361
|
+
VALUE underlying_io = rb_iv_get(io, "@io");
|
362
|
+
|
363
|
+
PREPARE_STR();
|
364
|
+
|
365
|
+
GetLibevAgent(self, agent);
|
366
|
+
if (underlying_io != Qnil) io = underlying_io;
|
367
|
+
GetOpenFile(io, fptr);
|
368
|
+
rb_io_check_byte_readable(fptr);
|
369
|
+
rb_io_set_nonblock(fptr);
|
370
|
+
watcher.fiber = Qnil;
|
371
|
+
|
372
|
+
OBJ_TAINT(str);
|
373
|
+
|
374
|
+
while (1) {
|
375
|
+
ssize_t n = read(fptr->fd, buf, len);
|
376
|
+
if (n < 0) {
|
377
|
+
int e = errno;
|
378
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
379
|
+
|
380
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
|
381
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
382
|
+
}
|
383
|
+
else {
|
384
|
+
switchpoint_result = libev_snooze();
|
385
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
386
|
+
|
387
|
+
if (n == 0) break; // EOF
|
388
|
+
|
389
|
+
total = n;
|
390
|
+
YIELD_STR();
|
391
|
+
Fiber_make_runnable(rb_fiber_current(), Qnil);
|
392
|
+
switchpoint_result = Thread_switch_fiber(rb_thread_current());
|
393
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
394
|
+
goto error;
|
395
|
+
}
|
396
|
+
}
|
397
|
+
}
|
398
|
+
|
399
|
+
RB_GC_GUARD(str);
|
400
|
+
RB_GC_GUARD(watcher.fiber);
|
401
|
+
RB_GC_GUARD(switchpoint_result);
|
402
|
+
|
403
|
+
return io;
|
404
|
+
error:
|
405
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
406
|
+
}
|
407
|
+
|
272
408
|
VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
273
409
|
struct LibevAgent_t *agent;
|
274
410
|
struct libev_io watcher;
|
@@ -276,8 +412,8 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
|
276
412
|
VALUE switchpoint_result = Qnil;
|
277
413
|
|
278
414
|
char *buf = StringValuePtr(str);
|
279
|
-
|
280
|
-
|
415
|
+
long len = RSTRING_LEN(str);
|
416
|
+
long left = len;
|
281
417
|
|
282
418
|
VALUE underlying_io = rb_iv_get(io, "@io");
|
283
419
|
if (underlying_io != Qnil) io = underlying_io;
|
@@ -287,27 +423,20 @@ VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
|
287
423
|
watcher.fiber = Qnil;
|
288
424
|
|
289
425
|
while (left > 0) {
|
290
|
-
|
291
|
-
if (
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
ev_io_start(agent->ev_loop, &watcher.io);
|
298
|
-
switchpoint_result = Polyphony_switchpoint();
|
299
|
-
ev_io_stop(agent->ev_loop, &watcher.io);
|
300
|
-
if (TEST_EXCEPTION(switchpoint_result))
|
301
|
-
goto error;
|
302
|
-
}
|
303
|
-
else {
|
304
|
-
// report error
|
305
|
-
|
306
|
-
}
|
426
|
+
ssize_t n = write(fptr->fd, buf, left);
|
427
|
+
if (n < 0) {
|
428
|
+
int e = errno;
|
429
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
430
|
+
|
431
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
|
432
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
307
433
|
}
|
308
434
|
else {
|
309
|
-
|
310
|
-
|
435
|
+
switchpoint_result = libev_snooze();
|
436
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
437
|
+
|
438
|
+
buf += n;
|
439
|
+
left -= n;
|
311
440
|
}
|
312
441
|
}
|
313
442
|
|
@@ -321,16 +450,6 @@ error:
|
|
321
450
|
|
322
451
|
///////////////////////////////////////////////////////////////////////////
|
323
452
|
|
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
453
|
VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
335
454
|
struct LibevAgent_t *agent;
|
336
455
|
struct libev_io watcher;
|
@@ -339,6 +458,8 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
339
458
|
struct sockaddr addr;
|
340
459
|
socklen_t len = (socklen_t)sizeof addr;
|
341
460
|
VALUE switchpoint_result = Qnil;
|
461
|
+
VALUE underlying_sock = rb_iv_get(sock, "@io");
|
462
|
+
if (underlying_sock != Qnil) sock = underlying_sock;
|
342
463
|
|
343
464
|
GetLibevAgent(self, agent);
|
344
465
|
GetOpenFile(sock, fptr);
|
@@ -348,43 +469,138 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
348
469
|
fd = accept(fptr->fd, &addr, &len);
|
349
470
|
if (fd < 0) {
|
350
471
|
int e = errno;
|
351
|
-
if (e
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
}
|
356
|
-
ev_io_start(agent->ev_loop, &watcher.io);
|
357
|
-
switchpoint_result = Polyphony_switchpoint();
|
358
|
-
ev_io_stop(agent->ev_loop, &watcher.io);
|
359
|
-
|
360
|
-
TEST_RESUME_EXCEPTION(switchpoint_result);
|
361
|
-
RB_GC_GUARD(watcher.fiber);
|
362
|
-
RB_GC_GUARD(switchpoint_result);
|
363
|
-
}
|
364
|
-
else
|
365
|
-
rb_syserr_fail(e, strerror(e));
|
366
|
-
// rb_syserr_fail_path(e, fptr->pathv);
|
472
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
473
|
+
|
474
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
|
475
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
367
476
|
}
|
368
477
|
else {
|
369
|
-
VALUE
|
478
|
+
VALUE socket;
|
370
479
|
rb_io_t *fp;
|
371
|
-
|
480
|
+
switchpoint_result = libev_snooze();
|
481
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
482
|
+
close(fd); // close fd since we're raising an exception
|
483
|
+
goto error;
|
484
|
+
}
|
485
|
+
|
486
|
+
socket = rb_obj_alloc(cTCPSocket);
|
487
|
+
MakeOpenFile(socket, fp);
|
372
488
|
rb_update_max_fd(fd);
|
373
489
|
fp->fd = fd;
|
374
490
|
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
375
|
-
rb_io_ascii8bit_binmode(
|
491
|
+
rb_io_ascii8bit_binmode(socket);
|
376
492
|
rb_io_set_nonblock(fp);
|
377
493
|
rb_io_synchronized(fp);
|
494
|
+
|
378
495
|
// if (rsock_do_not_reverse_lookup) {
|
379
496
|
// fp->mode |= FMODE_NOREVLOOKUP;
|
380
497
|
// }
|
498
|
+
return socket;
|
499
|
+
}
|
500
|
+
}
|
501
|
+
RB_GC_GUARD(switchpoint_result);
|
502
|
+
return Qnil;
|
503
|
+
error:
|
504
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
505
|
+
}
|
381
506
|
|
382
|
-
|
507
|
+
VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
|
508
|
+
struct LibevAgent_t *agent;
|
509
|
+
struct libev_io watcher;
|
510
|
+
rb_io_t *fptr;
|
511
|
+
int fd;
|
512
|
+
struct sockaddr addr;
|
513
|
+
socklen_t len = (socklen_t)sizeof addr;
|
514
|
+
VALUE switchpoint_result = Qnil;
|
515
|
+
VALUE socket = Qnil;
|
516
|
+
VALUE underlying_sock = rb_iv_get(sock, "@io");
|
517
|
+
if (underlying_sock != Qnil) sock = underlying_sock;
|
518
|
+
|
519
|
+
GetLibevAgent(self, agent);
|
520
|
+
GetOpenFile(sock, fptr);
|
521
|
+
rb_io_set_nonblock(fptr);
|
522
|
+
watcher.fiber = Qnil;
|
523
|
+
|
524
|
+
while (1) {
|
525
|
+
fd = accept(fptr->fd, &addr, &len);
|
526
|
+
if (fd < 0) {
|
527
|
+
int e = errno;
|
528
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
529
|
+
|
530
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
|
531
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
532
|
+
}
|
533
|
+
else {
|
534
|
+
rb_io_t *fp;
|
535
|
+
switchpoint_result = libev_snooze();
|
536
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
537
|
+
close(fd); // close fd since we're raising an exception
|
538
|
+
goto error;
|
539
|
+
}
|
540
|
+
|
541
|
+
socket = rb_obj_alloc(cTCPSocket);
|
542
|
+
MakeOpenFile(socket, fp);
|
543
|
+
rb_update_max_fd(fd);
|
544
|
+
fp->fd = fd;
|
545
|
+
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
546
|
+
rb_io_ascii8bit_binmode(socket);
|
547
|
+
rb_io_set_nonblock(fp);
|
548
|
+
rb_io_synchronized(fp);
|
549
|
+
|
550
|
+
rb_yield(socket);
|
551
|
+
socket = Qnil;
|
383
552
|
}
|
384
553
|
}
|
554
|
+
|
555
|
+
RB_GC_GUARD(socket);
|
556
|
+
RB_GC_GUARD(watcher.fiber);
|
557
|
+
RB_GC_GUARD(switchpoint_result);
|
385
558
|
return Qnil;
|
559
|
+
error:
|
560
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
386
561
|
}
|
387
562
|
|
563
|
+
// VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
|
564
|
+
// struct LibevAgent_t *agent;
|
565
|
+
// struct libev_io watcher;
|
566
|
+
// rb_io_t *fptr;
|
567
|
+
// struct sockaddr_in addr;
|
568
|
+
// char *host_buf = StringValueCStr(host);
|
569
|
+
// VALUE switchpoint_result = Qnil;
|
570
|
+
// VALUE underlying_sock = rb_iv_get(sock, "@io");
|
571
|
+
// if (underlying_sock != Qnil) sock = underlying_sock;
|
572
|
+
|
573
|
+
// GetLibevAgent(self, agent);
|
574
|
+
// GetOpenFile(sock, fptr);
|
575
|
+
// rb_io_set_nonblock(fptr);
|
576
|
+
// watcher.fiber = Qnil;
|
577
|
+
|
578
|
+
// addr.sin_family = AF_INET;
|
579
|
+
// addr.sin_addr.s_addr = inet_addr(host_buf);
|
580
|
+
// addr.sin_port = htons(NUM2INT(port));
|
581
|
+
|
582
|
+
// while (1) {
|
583
|
+
// int result = connect(fptr->fd, &addr, sizeof(addr));
|
584
|
+
// if (result < 0) {
|
585
|
+
// int e = errno;
|
586
|
+
// if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
587
|
+
|
588
|
+
// switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
|
589
|
+
// if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
590
|
+
// }
|
591
|
+
// else {
|
592
|
+
// switchpoint_result = libev_snooze();
|
593
|
+
// if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
594
|
+
|
595
|
+
// return sock;
|
596
|
+
// }
|
597
|
+
// }
|
598
|
+
// RB_GC_GUARD(switchpoint_result);
|
599
|
+
// return Qnil;
|
600
|
+
// error:
|
601
|
+
// return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
602
|
+
// }
|
603
|
+
|
388
604
|
VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
|
389
605
|
struct LibevAgent_t *agent;
|
390
606
|
struct libev_io watcher;
|
@@ -400,7 +616,7 @@ VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
|
|
400
616
|
watcher.fiber = rb_fiber_current();
|
401
617
|
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, events);
|
402
618
|
ev_io_start(agent->ev_loop, &watcher.io);
|
403
|
-
switchpoint_result =
|
619
|
+
switchpoint_result = libev_await(agent);
|
404
620
|
ev_io_stop(agent->ev_loop, &watcher.io);
|
405
621
|
|
406
622
|
TEST_RESUME_EXCEPTION(switchpoint_result);
|
@@ -414,7 +630,7 @@ struct libev_timer {
|
|
414
630
|
VALUE fiber;
|
415
631
|
};
|
416
632
|
|
417
|
-
|
633
|
+
void LibevAgent_timer_callback(EV_P_ ev_timer *w, int revents)
|
418
634
|
{
|
419
635
|
struct libev_timer *watcher = (struct libev_timer *)w;
|
420
636
|
Fiber_make_runnable(watcher->fiber, Qnil);
|
@@ -430,7 +646,7 @@ VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
|
|
430
646
|
ev_timer_init(&watcher.timer, LibevAgent_timer_callback, NUM2DBL(duration), 0.);
|
431
647
|
ev_timer_start(agent->ev_loop, &watcher.timer);
|
432
648
|
|
433
|
-
switchpoint_result =
|
649
|
+
switchpoint_result = libev_await(agent);
|
434
650
|
ev_timer_stop(agent->ev_loop, &watcher.timer);
|
435
651
|
|
436
652
|
TEST_RESUME_EXCEPTION(switchpoint_result);
|
@@ -444,7 +660,7 @@ struct libev_child {
|
|
444
660
|
VALUE fiber;
|
445
661
|
};
|
446
662
|
|
447
|
-
|
663
|
+
void LibevAgent_child_callback(EV_P_ ev_child *w, int revents)
|
448
664
|
{
|
449
665
|
struct libev_child *watcher = (struct libev_child *)w;
|
450
666
|
int exit_status = w->rstatus >> 8; // weird, why should we do this?
|
@@ -464,7 +680,7 @@ VALUE LibevAgent_waitpid(VALUE self, VALUE pid) {
|
|
464
680
|
ev_child_init(&watcher.child, LibevAgent_child_callback, NUM2INT(pid), 0);
|
465
681
|
ev_child_start(agent->ev_loop, &watcher.child);
|
466
682
|
|
467
|
-
switchpoint_result =
|
683
|
+
switchpoint_result = libev_await(agent);
|
468
684
|
ev_child_stop(agent->ev_loop, &watcher.child);
|
469
685
|
|
470
686
|
TEST_RESUME_EXCEPTION(switchpoint_result);
|
@@ -491,12 +707,18 @@ void Init_LibevAgent() {
|
|
491
707
|
rb_define_method(cLibevAgent, "post_fork", LibevAgent_post_fork, 0);
|
492
708
|
rb_define_method(cLibevAgent, "pending_count", LibevAgent_pending_count, 0);
|
493
709
|
|
710
|
+
rb_define_method(cLibevAgent, "ref", LibevAgent_ref, 0);
|
711
|
+
rb_define_method(cLibevAgent, "unref", LibevAgent_unref, 0);
|
712
|
+
|
494
713
|
rb_define_method(cLibevAgent, "poll", LibevAgent_poll, 3);
|
495
714
|
rb_define_method(cLibevAgent, "break", LibevAgent_break, 0);
|
496
715
|
|
497
716
|
rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
|
717
|
+
rb_define_method(cLibevAgent, "read_loop", LibevAgent_read_loop, 1);
|
498
718
|
rb_define_method(cLibevAgent, "write", LibevAgent_write, 2);
|
499
719
|
rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
|
720
|
+
rb_define_method(cLibevAgent, "accept_loop", LibevAgent_accept_loop, 1);
|
721
|
+
// rb_define_method(cLibevAgent, "connect", LibevAgent_accept, 3);
|
500
722
|
rb_define_method(cLibevAgent, "wait_io", LibevAgent_wait_io, 2);
|
501
723
|
rb_define_method(cLibevAgent, "sleep", LibevAgent_sleep, 1);
|
502
724
|
rb_define_method(cLibevAgent, "waitpid", LibevAgent_waitpid, 1);
|