polyphony 0.43.4 → 0.43.5
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 +1 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +1 -1
- data/docs/index.md +1 -2
- data/docs/main-concepts/design-principles.md +23 -34
- data/docs/main-concepts/fiber-scheduling.md +1 -1
- data/examples/performance/messaging.rb +29 -0
- data/examples/performance/multi_snooze.rb +11 -9
- data/ext/polyphony/libev_agent.c +37 -22
- data/ext/polyphony/libev_queue.c +125 -54
- data/ext/polyphony/polyphony.h +12 -5
- data/ext/polyphony/ring_buffer.c +120 -0
- data/ext/polyphony/ring_buffer.h +28 -0
- data/ext/polyphony/thread.c +13 -7
- data/lib/polyphony/core/thread_pool.rb +1 -1
- data/lib/polyphony/extensions/core.rb +9 -0
- data/lib/polyphony/extensions/fiber.rb +8 -1
- data/lib/polyphony/extensions/io.rb +16 -15
- data/lib/polyphony/extensions/socket.rb +10 -0
- data/lib/polyphony/version.rb +1 -1
- data/test/q.rb +24 -0
- data/test/test_global_api.rb +2 -2
- data/test/test_io.rb +10 -2
- data/test/test_queue.rb +26 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55e44a81e7358528fabd37e1d48b6387957bc6001e4d947bf6c881d2425953d9
|
4
|
+
data.tar.gz: 45af7e485891927a1e9f88e8a6342692c25f306aa60e40fa8a74c0ca8494d3d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 444675baf3131ffc843b61649b92ce5ca7d77773277db750d8c8ac80a88ccd133e6f60d61282fa38ffb91de59eaf2304ea9b0fca196a7eb61c7a53becaa1fe46
|
7
|
+
data.tar.gz: f21e26a41c8dfeea0d803b5b9bc82b1e648bf0ca588225f721560c432f363020b37561c59daeefbe2d434b012f1f5f767460557939a22d04e78e40471813a8ca
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 0.43.5 2020-07-13
|
2
|
+
|
3
|
+
* Fix `#read_nonblock`, `#write_nonblock` for `IO` and `Socket` (#27)
|
4
|
+
* Patch `Kernel#p`, `IO#puts` to issue single write call
|
5
|
+
* Add support for multiple arguments in `IO#write` and `LibevAgent#write`
|
6
|
+
* Use LibevQueue for fiber run queue
|
7
|
+
* Reimplement LibevQueue as ring buffer
|
8
|
+
|
1
9
|
## 0.43.4 2020-07-09
|
2
10
|
|
3
11
|
* Reimplement Kernel#trap
|
data/Gemfile.lock
CHANGED
data/docs/index.md
CHANGED
@@ -18,9 +18,8 @@ implements a comprehensive
|
|
18
18
|
using [libev](https://github.com/enki/libev) as a high-performance event reactor
|
19
19
|
for I/O, timers, and other asynchronous events.
|
20
20
|
|
21
|
+
[Overview](getting-started/overview){: .btn .btn-green .text-gamma }
|
21
22
|
[Take the tutorial](getting-started/tutorial){: .btn .btn-blue .text-gamma }
|
22
|
-
[Main Concepts](main-concepts/concurrency/){: .btn .btn-green .text-gamma }
|
23
|
-
[FAQ](faq){: .btn .btn-green .text-gamma }
|
24
23
|
[Source code](https://github.com/digital-fabric/polyphony){: .btn .btn-purple .text-gamma target="_blank" }
|
25
24
|
{: .mt-6 .h-align-center }
|
26
25
|
|
@@ -6,7 +6,7 @@ parent: Main Concepts
|
|
6
6
|
permalink: /main-concepts/design-principles/
|
7
7
|
prev_title: Extending Polyphony
|
8
8
|
---
|
9
|
-
# The Design of Polyphony
|
9
|
+
# The Design of Polyphony
|
10
10
|
|
11
11
|
Polyphony is a new gem that aims to enable developing high-performance
|
12
12
|
concurrent applications in Ruby using a fluent, compact syntax and API.
|
@@ -47,7 +47,7 @@ Nevertheless, while work is being done to harness fibers for providing a better
|
|
47
47
|
way to do concurrency in Ruby, fibers remain a mistery for most Ruby
|
48
48
|
programmers, a perplexing unfamiliar corner right at the heart of Ruby.
|
49
49
|
|
50
|
-
##
|
50
|
+
## The History of Polyphony
|
51
51
|
|
52
52
|
Polyphony started as an experiment, but over about two years of slow, jerky
|
53
53
|
evolution turned into something I'm really excited to share with the Ruby
|
@@ -58,31 +58,24 @@ Polyphony today as nothing like the way it began. A careful examination of the
|
|
58
58
|
[CHANGELOG](https://github.com/digital-fabric/polyphony/blob/master/CHANGELOG.md)
|
59
59
|
would show how Polyphony explored not only different event reactor designs, but
|
60
60
|
also different API designs incorporating various concurrent paradigms such as
|
61
|
-
promises, async/await, fibers, and finally structured concurrency.
|
62
|
-
|
63
|
-
While Polyphony, like nio4r or EventMachine, uses an event reactor to turn
|
64
|
-
blocking operations into non-blocking ones, it completely embraces fibers and in
|
65
|
-
fact does not provide any callback-based APIs. Furthermore, Polyphony provides
|
66
|
-
fullblown fiber-aware implementations of blocking operations, such as
|
67
|
-
`read/write`, `sleep` or `waitpid`, instead of just event watching primitives.
|
61
|
+
promises, async/await, fibers, and finally structured concurrency.
|
68
62
|
|
69
63
|
Throughout the development process, it was my intention to create a programming
|
70
|
-
interface that would make highly-concurrent
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
64
|
+
interface that would make it easy to author highly-concurrent Ruby programs.
|
75
65
|
|
66
|
+
## Design Principles
|
76
67
|
|
68
|
+
While Polyphony, like nio4r or EventMachine, uses an event reactor to turn
|
69
|
+
blocking operations into non-blocking ones, it completely embraces fibers and in
|
70
|
+
fact does not provide any callback-based APIs.
|
77
71
|
|
78
|
-
|
79
|
-
|
72
|
+
Furthermore, Polyphony provides fullblown fiber-aware implementations of
|
73
|
+
blocking operations, such as `read/write`, `sleep` or `waitpid`, instead of just
|
74
|
+
event watching primitives.
|
80
75
|
|
81
|
-
|
82
|
-
[libev](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod) event reactor
|
83
|
-
library. Polyphony's design is based on the following principles:
|
76
|
+
Polyphony's design is based on the following principles:
|
84
77
|
|
85
|
-
-
|
78
|
+
- The concurrency model should feel "baked-in". The API should allow
|
86
79
|
concurrency with minimal effort. Polyphony should facilitate writing both
|
87
80
|
large apps and small scripts with as little boilerplate code as possible.
|
88
81
|
There should be no calls to initialize the event reactor, or other ceremonial
|
@@ -91,8 +84,8 @@ library. Polyphony's design is based on the following principles:
|
|
91
84
|
```ruby
|
92
85
|
require 'polyphony'
|
93
86
|
|
94
|
-
# start 10 fibers, each sleeping for
|
95
|
-
10.times { spin { sleep
|
87
|
+
# start 10 fibers, each sleeping for 3 seconds
|
88
|
+
10.times { spin { sleep 3 } }
|
96
89
|
|
97
90
|
puts 'going to sleep now'
|
98
91
|
# wait for other fibers to terminate
|
@@ -106,14 +99,14 @@ library. Polyphony's design is based on the following principles:
|
|
106
99
|
```ruby
|
107
100
|
# in Polyphony, I/O ops might block the current fiber, but implicitly yield to
|
108
101
|
# other concurrent fibers:
|
109
|
-
clients.each
|
102
|
+
clients.each do |client|
|
110
103
|
spin { client.puts 'Elvis has left the chatroom' }
|
111
|
-
|
104
|
+
end
|
112
105
|
```
|
113
106
|
|
114
107
|
- Concurrency primitives should be accessible using idiomatic Ruby techniques
|
115
108
|
(blocks, method chaining...) and should feel as much as possible "part of the
|
116
|
-
language". The resulting API is based
|
109
|
+
language". The resulting API is fundamentally based on methods rather than classes,
|
117
110
|
for example `spin` or `move_on_after`, leading to a coding style that is both
|
118
111
|
more compact and more legible:
|
119
112
|
|
@@ -125,8 +118,6 @@ library. Polyphony's design is based on the following principles:
|
|
125
118
|
}
|
126
119
|
```
|
127
120
|
|
128
|
-
- Breaking up operations into
|
129
|
-
|
130
121
|
- Polyphony should embrace Ruby's standard `raise/rescue/ensure` exception
|
131
122
|
handling mechanism. Exception handling in a highly concurrent environment
|
132
123
|
should be robust and foolproof:
|
@@ -147,7 +138,8 @@ library. Polyphony's design is based on the following principles:
|
|
147
138
|
constructs through composition.
|
148
139
|
|
149
140
|
- The entire design should embrace fibers. There should be no callback-based
|
150
|
-
asynchronous APIs.
|
141
|
+
asynchronous APIs. The library and its ecosystem will foster the development
|
142
|
+
of techniques and tools for converting callback-based APIs to fiber-based ones.
|
151
143
|
|
152
144
|
- Use of extensive monkey patching of Ruby core modules and classes such as
|
153
145
|
`Kernel`, `Fiber`, `IO` and `Timeout`. This allows porting over non-Polyphony
|
@@ -160,13 +152,10 @@ library. Polyphony's design is based on the following principles:
|
|
160
152
|
# use TCPServer from Ruby's stdlib
|
161
153
|
server = TCPServer.open('127.0.0.1', 1234)
|
162
154
|
while (client = server.accept)
|
163
|
-
spin
|
155
|
+
spin {
|
164
156
|
while (data = client.gets)
|
165
|
-
client.write(
|
157
|
+
client.write("you said: #{ data.chomp }\n")
|
166
158
|
end
|
167
|
-
|
159
|
+
}
|
168
160
|
end
|
169
161
|
```
|
170
|
-
|
171
|
-
- Development of techniques and tools for converting callback-based APIs to
|
172
|
-
fiber-based ones.
|
@@ -44,7 +44,7 @@ pong = Fiber.new { loop { puts "pong"; ping.transfer } }
|
|
44
44
|
ping.transfer
|
45
45
|
```
|
46
46
|
|
47
|
-
`Fiber#
|
47
|
+
`Fiber#transfer` also allows using the main fiber as a general purpose
|
48
48
|
resumable execution context. For that reason, Polyphony uses `Fiber#transfer`
|
49
49
|
exclusively for scheduling fibers. Normally, however, applications based on
|
50
50
|
Polyphony will not use this API directly.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
X = 1_000_000
|
7
|
+
|
8
|
+
GC.disable
|
9
|
+
|
10
|
+
count = 0
|
11
|
+
|
12
|
+
pong = spin_loop do
|
13
|
+
msg, ping = receive
|
14
|
+
count += 1
|
15
|
+
ping << 'pong'
|
16
|
+
end
|
17
|
+
|
18
|
+
ping = spin do
|
19
|
+
X.times do
|
20
|
+
pong << ['ping', Fiber.current]
|
21
|
+
msg = receive
|
22
|
+
count += 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
t0 = Time.now
|
27
|
+
ping.await
|
28
|
+
dt = Time.now - t0
|
29
|
+
puts format('message rate: %d/s', (X / dt))
|
@@ -5,19 +5,20 @@ require 'polyphony'
|
|
5
5
|
|
6
6
|
def bm(fibers, iterations)
|
7
7
|
count = 0
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
count += 1
|
15
|
-
end
|
8
|
+
t_pre = Time.now
|
9
|
+
fibers.times do
|
10
|
+
spin do
|
11
|
+
iterations.times do
|
12
|
+
snooze
|
13
|
+
count += 1
|
16
14
|
end
|
17
15
|
end
|
18
16
|
end
|
17
|
+
t0 = Time.now
|
18
|
+
Fiber.current.await_all_children
|
19
19
|
dt = Time.now - t0
|
20
|
-
puts "#{[fibers, iterations].inspect} count: #{count} #{count / dt.to_f}/s"
|
20
|
+
puts "#{[fibers, iterations].inspect} setup: #{t0 - t_pre}s count: #{count} #{count / dt.to_f}/s"
|
21
|
+
Thread.current.run_queue_trace
|
21
22
|
end
|
22
23
|
|
23
24
|
GC.disable
|
@@ -27,5 +28,6 @@ bm(10, 100_000)
|
|
27
28
|
bm(100, 10_000)
|
28
29
|
bm(1_000, 1_000)
|
29
30
|
bm(10_000, 100)
|
31
|
+
|
30
32
|
# bm(100_000, 10)
|
31
33
|
# bm(1_000_000, 1)
|
data/ext/polyphony/libev_agent.c
CHANGED
@@ -130,7 +130,7 @@ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue
|
|
130
130
|
GetLibevAgent(self, agent);
|
131
131
|
|
132
132
|
if (is_nowait) {
|
133
|
-
long runnable_count =
|
133
|
+
long runnable_count = LibevQueue_len(queue);
|
134
134
|
agent->run_no_wait_count++;
|
135
135
|
if (agent->run_no_wait_count < runnable_count || agent->run_no_wait_count < 10)
|
136
136
|
return self;
|
@@ -413,45 +413,60 @@ error:
|
|
413
413
|
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
414
414
|
}
|
415
415
|
|
416
|
-
VALUE LibevAgent_write(
|
416
|
+
VALUE LibevAgent_write(int argc, VALUE *argv, VALUE self) {
|
417
417
|
struct LibevAgent_t *agent;
|
418
418
|
struct libev_io watcher;
|
419
419
|
rb_io_t *fptr;
|
420
420
|
VALUE switchpoint_result = Qnil;
|
421
|
+
VALUE io;
|
422
|
+
VALUE underlying_io;
|
423
|
+
long total_written = 0;
|
424
|
+
int arg_idx = 1;
|
421
425
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
426
|
+
if (argc < 2)
|
427
|
+
rb_raise(rb_eRuntimeError, "(wrong number of arguments (expected 2 or more))");
|
428
|
+
|
429
|
+
io = argv[0];
|
430
|
+
underlying_io = rb_iv_get(io, "@io");
|
427
431
|
if (underlying_io != Qnil) io = underlying_io;
|
428
432
|
GetLibevAgent(self, agent);
|
429
433
|
io = rb_io_get_write_io(io);
|
430
434
|
GetOpenFile(io, fptr);
|
431
435
|
watcher.fiber = Qnil;
|
432
436
|
|
433
|
-
while (
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
437
|
+
while (arg_idx < argc) {
|
438
|
+
VALUE str = argv[arg_idx];
|
439
|
+
char *buf = StringValuePtr(str);
|
440
|
+
long len = RSTRING_LEN(str);
|
441
|
+
long left = len;
|
438
442
|
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
443
|
+
while (left > 0) {
|
444
|
+
ssize_t n = write(fptr->fd, buf, left);
|
445
|
+
if (n < 0) {
|
446
|
+
int e = errno;
|
447
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
445
448
|
|
446
|
-
|
447
|
-
|
449
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
|
450
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
451
|
+
}
|
452
|
+
else {
|
453
|
+
buf += n;
|
454
|
+
left -= n;
|
455
|
+
}
|
448
456
|
}
|
457
|
+
total_written += len;
|
458
|
+
arg_idx++;
|
459
|
+
}
|
460
|
+
|
461
|
+
if (watcher.fiber == Qnil) {
|
462
|
+
switchpoint_result = libev_snooze();
|
463
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
449
464
|
}
|
450
465
|
|
451
466
|
RB_GC_GUARD(watcher.fiber);
|
452
467
|
RB_GC_GUARD(switchpoint_result);
|
453
468
|
|
454
|
-
return INT2NUM(
|
469
|
+
return INT2NUM(total_written);
|
455
470
|
error:
|
456
471
|
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
457
472
|
}
|
@@ -723,7 +738,7 @@ void Init_LibevAgent() {
|
|
723
738
|
|
724
739
|
rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
|
725
740
|
rb_define_method(cLibevAgent, "read_loop", LibevAgent_read_loop, 1);
|
726
|
-
rb_define_method(cLibevAgent, "write", LibevAgent_write,
|
741
|
+
rb_define_method(cLibevAgent, "write", LibevAgent_write, -1);
|
727
742
|
rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
|
728
743
|
rb_define_method(cLibevAgent, "accept_loop", LibevAgent_accept_loop, 1);
|
729
744
|
// rb_define_method(cLibevAgent, "connect", LibevAgent_accept, 3);
|
data/ext/polyphony/libev_queue.c
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#include "polyphony.h"
|
2
|
+
#include "ring_buffer.h"
|
2
3
|
|
3
4
|
struct async_watcher {
|
4
5
|
ev_async async;
|
@@ -6,48 +7,65 @@ struct async_watcher {
|
|
6
7
|
VALUE fiber;
|
7
8
|
};
|
8
9
|
|
9
|
-
struct
|
10
|
+
struct async_watcher_queue {
|
10
11
|
struct async_watcher **queue;
|
11
|
-
unsigned int
|
12
|
+
unsigned int length;
|
12
13
|
unsigned int count;
|
13
14
|
unsigned int push_idx;
|
14
|
-
unsigned int
|
15
|
+
unsigned int shift_idx;
|
15
16
|
};
|
16
17
|
|
17
|
-
void
|
18
|
-
queue->
|
18
|
+
void async_watcher_queue_init(struct async_watcher_queue *queue) {
|
19
|
+
queue->length = 1;
|
19
20
|
queue->count = 0;
|
20
|
-
queue->queue = malloc(sizeof(struct async_watcher *) * queue->
|
21
|
+
queue->queue = malloc(sizeof(struct async_watcher *) * queue->length);
|
21
22
|
queue->push_idx = 0;
|
22
|
-
queue->
|
23
|
+
queue->shift_idx = 0;
|
23
24
|
}
|
24
25
|
|
25
|
-
void
|
26
|
+
void async_watcher_queue_free(struct async_watcher_queue *queue) {
|
26
27
|
free(queue->queue);
|
27
28
|
}
|
28
29
|
|
29
|
-
void
|
30
|
-
|
31
|
-
queue->
|
32
|
-
queue->queue
|
33
|
-
|
30
|
+
void async_watcher_queue_realign(struct async_watcher_queue *queue) {
|
31
|
+
memmove(
|
32
|
+
queue->queue,
|
33
|
+
queue->queue + queue->shift_idx,
|
34
|
+
queue->count * sizeof(struct async_watcher *)
|
35
|
+
);
|
36
|
+
queue->push_idx = queue->push_idx - queue->shift_idx;
|
37
|
+
queue->shift_idx = 0;
|
38
|
+
}
|
39
|
+
|
40
|
+
#define QUEUE_REALIGN_THRESHOLD 32
|
41
|
+
|
42
|
+
void async_watcher_queue_push(struct async_watcher_queue *queue, struct async_watcher *watcher) {
|
34
43
|
if (queue->count == 0) {
|
35
44
|
queue->push_idx = 0;
|
36
|
-
queue->
|
45
|
+
queue->shift_idx = 0;
|
46
|
+
}
|
47
|
+
if (queue->push_idx == queue->length) {
|
48
|
+
// prevent shift idx moving too much away from zero
|
49
|
+
if (queue->length >= QUEUE_REALIGN_THRESHOLD && queue->shift_idx >= (queue->length / 2))
|
50
|
+
async_watcher_queue_realign(queue);
|
51
|
+
else {
|
52
|
+
queue->length = (queue->length == 1) ? 4 : queue->length * 2;
|
53
|
+
queue->queue = realloc(queue->queue, sizeof(struct async_watcher *) * queue->length);
|
54
|
+
}
|
37
55
|
}
|
38
56
|
queue->count++;
|
39
57
|
queue->queue[queue->push_idx++] = watcher;
|
40
58
|
}
|
41
59
|
|
42
|
-
struct async_watcher *
|
60
|
+
struct async_watcher *async_watcher_queue_shift(struct async_watcher_queue *queue) {
|
43
61
|
if (queue->count == 0) return 0;
|
44
62
|
|
45
63
|
queue->count--;
|
46
64
|
|
47
|
-
return queue->queue[queue->
|
65
|
+
return queue->queue[queue->shift_idx++];
|
48
66
|
}
|
49
67
|
|
50
|
-
void
|
68
|
+
void async_watcher_queue_remove_at_idx(struct async_watcher_queue *queue, unsigned int remove_idx) {
|
51
69
|
queue->count--;
|
52
70
|
queue->push_idx--;
|
53
71
|
if (remove_idx < queue->push_idx)
|
@@ -58,33 +76,33 @@ void async_queue_remove_at_idx(struct async_queue *queue, unsigned int remove_id
|
|
58
76
|
);
|
59
77
|
}
|
60
78
|
|
61
|
-
void
|
79
|
+
void async_watcher_queue_remove_by_fiber(struct async_watcher_queue *queue, VALUE fiber) {
|
62
80
|
if (queue->count == 0) return;
|
63
81
|
|
64
|
-
for (unsigned idx = queue->
|
82
|
+
for (unsigned idx = queue->shift_idx; idx < queue->push_idx; idx++) {
|
65
83
|
if (queue->queue[idx]->fiber == fiber) {
|
66
|
-
|
84
|
+
async_watcher_queue_remove_at_idx(queue, idx);
|
67
85
|
return;
|
68
86
|
}
|
69
87
|
}
|
70
88
|
}
|
71
89
|
|
72
90
|
typedef struct queue {
|
73
|
-
|
74
|
-
struct
|
91
|
+
ring_buffer values;
|
92
|
+
struct async_watcher_queue shift_queue;
|
75
93
|
} LibevQueue_t;
|
76
94
|
|
77
|
-
|
78
95
|
VALUE cLibevQueue = Qnil;
|
79
96
|
|
80
97
|
static void LibevQueue_mark(void *ptr) {
|
81
98
|
LibevQueue_t *queue = ptr;
|
82
|
-
|
99
|
+
ring_buffer_mark(&queue->values);
|
83
100
|
}
|
84
101
|
|
85
102
|
static void LibevQueue_free(void *ptr) {
|
86
103
|
LibevQueue_t *queue = ptr;
|
87
|
-
|
104
|
+
ring_buffer_free(&queue->values);
|
105
|
+
async_watcher_queue_free(&queue->shift_queue);
|
88
106
|
xfree(ptr);
|
89
107
|
}
|
90
108
|
|
@@ -112,8 +130,8 @@ static VALUE LibevQueue_initialize(VALUE self) {
|
|
112
130
|
LibevQueue_t *queue;
|
113
131
|
GetQueue(self, queue);
|
114
132
|
|
115
|
-
queue->
|
116
|
-
|
133
|
+
ring_buffer_init(&queue->values);
|
134
|
+
async_watcher_queue_init(&queue->shift_queue);
|
117
135
|
|
118
136
|
return self;
|
119
137
|
}
|
@@ -122,18 +140,31 @@ VALUE LibevQueue_push(VALUE self, VALUE value) {
|
|
122
140
|
LibevQueue_t *queue;
|
123
141
|
GetQueue(self, queue);
|
124
142
|
if (queue->shift_queue.count > 0) {
|
125
|
-
struct async_watcher *watcher =
|
143
|
+
struct async_watcher *watcher = async_watcher_queue_shift(&queue->shift_queue);
|
144
|
+
if (watcher) {
|
145
|
+
ev_async_send(watcher->ev_loop, &watcher->async);
|
146
|
+
}
|
147
|
+
}
|
148
|
+
ring_buffer_push(&queue->values, value);
|
149
|
+
return self;
|
150
|
+
}
|
151
|
+
|
152
|
+
VALUE LibevQueue_unshift(VALUE self, VALUE value) {
|
153
|
+
LibevQueue_t *queue;
|
154
|
+
GetQueue(self, queue);
|
155
|
+
if (queue->shift_queue.count > 0) {
|
156
|
+
struct async_watcher *watcher = async_watcher_queue_shift(&queue->shift_queue);
|
126
157
|
if (watcher) {
|
127
158
|
ev_async_send(watcher->ev_loop, &watcher->async);
|
128
159
|
}
|
129
160
|
}
|
130
|
-
|
161
|
+
ring_buffer_unshift(&queue->values, value);
|
131
162
|
return self;
|
132
163
|
}
|
133
164
|
|
134
165
|
struct ev_loop *LibevAgent_ev_loop(VALUE self);
|
135
166
|
|
136
|
-
void
|
167
|
+
void async_watcher_queue_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
|
137
168
|
struct async_watcher *watcher = (struct async_watcher *)ev_async;
|
138
169
|
Fiber_make_runnable(watcher->fiber, Qnil);
|
139
170
|
}
|
@@ -144,22 +175,22 @@ VALUE LibevQueue_shift(VALUE self) {
|
|
144
175
|
LibevQueue_t *queue;
|
145
176
|
GetQueue(self, queue);
|
146
177
|
|
147
|
-
if (
|
178
|
+
if (queue->values.count == 0) {
|
148
179
|
struct async_watcher watcher;
|
149
180
|
VALUE agent = rb_ivar_get(rb_thread_current(), ID_ivar_agent);
|
150
181
|
VALUE switchpoint_result = Qnil;
|
151
182
|
|
152
183
|
watcher.ev_loop = LibevAgent_ev_loop(agent);
|
153
184
|
watcher.fiber = rb_fiber_current();
|
154
|
-
|
155
|
-
ev_async_init(&watcher.async,
|
185
|
+
async_watcher_queue_push(&queue->shift_queue, &watcher);
|
186
|
+
ev_async_init(&watcher.async, async_watcher_queue_callback);
|
156
187
|
ev_async_start(watcher.ev_loop, &watcher.async);
|
157
188
|
|
158
189
|
switchpoint_result = libev_agent_await(agent);
|
159
190
|
ev_async_stop(watcher.ev_loop, &watcher.async);
|
160
191
|
|
161
192
|
if (RTEST(rb_obj_is_kind_of(switchpoint_result, rb_eException))) {
|
162
|
-
|
193
|
+
async_watcher_queue_remove_by_fiber(&queue->shift_queue, watcher.fiber);
|
163
194
|
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
164
195
|
}
|
165
196
|
RB_GC_GUARD(watcher.fiber);
|
@@ -167,36 +198,72 @@ VALUE LibevQueue_shift(VALUE self) {
|
|
167
198
|
RB_GC_GUARD(switchpoint_result);
|
168
199
|
}
|
169
200
|
|
170
|
-
return
|
201
|
+
return ring_buffer_shift(&queue->values);
|
202
|
+
}
|
203
|
+
|
204
|
+
VALUE LibevQueue_shift_no_wait(VALUE self) {
|
205
|
+
LibevQueue_t *queue;
|
206
|
+
GetQueue(self, queue);
|
207
|
+
|
208
|
+
return ring_buffer_shift(&queue->values);
|
209
|
+
}
|
210
|
+
|
211
|
+
VALUE LibevQueue_delete(VALUE self, VALUE value) {
|
212
|
+
LibevQueue_t *queue;
|
213
|
+
GetQueue(self, queue);
|
214
|
+
|
215
|
+
ring_buffer_delete(&queue->values, value);
|
216
|
+
return self;
|
217
|
+
}
|
218
|
+
|
219
|
+
VALUE LibevQueue_clear(VALUE self) {
|
220
|
+
LibevQueue_t *queue;
|
221
|
+
GetQueue(self, queue);
|
222
|
+
|
223
|
+
ring_buffer_clear(&queue->values);
|
224
|
+
return self;
|
225
|
+
}
|
226
|
+
|
227
|
+
long LibevQueue_len(VALUE self) {
|
228
|
+
LibevQueue_t *queue;
|
229
|
+
GetQueue(self, queue);
|
230
|
+
|
231
|
+
return queue->values.count;
|
171
232
|
}
|
172
233
|
|
173
234
|
VALUE LibevQueue_shift_each(VALUE self) {
|
174
235
|
LibevQueue_t *queue;
|
175
|
-
VALUE old_queue;
|
176
236
|
GetQueue(self, queue);
|
177
|
-
|
178
|
-
queue->
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
return self;
|
188
|
-
}
|
189
|
-
else {
|
190
|
-
RB_GC_GUARD(old_queue);
|
191
|
-
return old_queue;
|
192
|
-
}
|
237
|
+
|
238
|
+
ring_buffer_shift_each(&queue->values);
|
239
|
+
return self;
|
240
|
+
}
|
241
|
+
|
242
|
+
VALUE LibevQueue_shift_all(VALUE self) {
|
243
|
+
LibevQueue_t *queue;
|
244
|
+
GetQueue(self, queue);
|
245
|
+
|
246
|
+
return ring_buffer_shift_all(&queue->values);
|
193
247
|
}
|
194
248
|
|
195
249
|
VALUE LibevQueue_empty_p(VALUE self) {
|
196
250
|
LibevQueue_t *queue;
|
197
251
|
GetQueue(self, queue);
|
198
252
|
|
199
|
-
return (
|
253
|
+
return (queue->values.count == 0) ? Qtrue : Qfalse;
|
254
|
+
}
|
255
|
+
|
256
|
+
void LibevQueue_trace(VALUE self) {
|
257
|
+
LibevQueue_t *queue;
|
258
|
+
GetQueue(self, queue);
|
259
|
+
|
260
|
+
printf(
|
261
|
+
"queue size: %d count: %d head: %d tail: %d\n",
|
262
|
+
queue->values.size,
|
263
|
+
queue->values.count,
|
264
|
+
queue->values.head,
|
265
|
+
queue->values.tail
|
266
|
+
);
|
200
267
|
}
|
201
268
|
|
202
269
|
void Init_LibevQueue() {
|
@@ -206,11 +273,15 @@ void Init_LibevQueue() {
|
|
206
273
|
rb_define_method(cLibevQueue, "initialize", LibevQueue_initialize, 0);
|
207
274
|
rb_define_method(cLibevQueue, "push", LibevQueue_push, 1);
|
208
275
|
rb_define_method(cLibevQueue, "<<", LibevQueue_push, 1);
|
276
|
+
rb_define_method(cLibevQueue, "unshift", LibevQueue_unshift, 1);
|
209
277
|
|
210
|
-
rb_define_method(cLibevQueue, "pop", LibevQueue_shift, 0);
|
211
278
|
rb_define_method(cLibevQueue, "shift", LibevQueue_shift, 0);
|
279
|
+
rb_define_method(cLibevQueue, "pop", LibevQueue_shift, 0);
|
280
|
+
rb_define_method(cLibevQueue, "shift_no_wait", LibevQueue_shift_no_wait, 0);
|
281
|
+
rb_define_method(cLibevQueue, "delete", LibevQueue_delete, 1);
|
212
282
|
|
213
283
|
rb_define_method(cLibevQueue, "shift_each", LibevQueue_shift_each, 0);
|
284
|
+
rb_define_method(cLibevQueue, "shift_all", LibevQueue_shift_all, 0);
|
214
285
|
rb_define_method(cLibevQueue, "empty?", LibevQueue_empty_p, 0);
|
215
286
|
}
|
216
287
|
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
#ifndef
|
2
|
-
#define
|
1
|
+
#ifndef POLYPHONY_H
|
2
|
+
#define POLYPHONY_H
|
3
3
|
|
4
4
|
#include "ruby.h"
|
5
5
|
#include "ruby/io.h"
|
@@ -76,9 +76,16 @@ VALUE LibevAgent_unref(VALUE self);
|
|
76
76
|
int LibevAgent_ref_count(VALUE self);
|
77
77
|
void LibevAgent_reset_ref_count(VALUE self);
|
78
78
|
|
79
|
-
VALUE
|
79
|
+
VALUE LibevQueue_push(VALUE self, VALUE value);
|
80
|
+
VALUE LibevQueue_unshift(VALUE self, VALUE value);
|
81
|
+
VALUE LibevQueue_shift(VALUE self);
|
82
|
+
VALUE LibevQueue_shift_no_wait(VALUE self);
|
83
|
+
VALUE LibevQueue_clear(VALUE self);
|
84
|
+
VALUE LibevQueue_delete(VALUE self, VALUE value);
|
85
|
+
long LibevQueue_len(VALUE self);
|
86
|
+
void LibevQueue_trace(VALUE self);
|
80
87
|
|
81
|
-
VALUE
|
88
|
+
VALUE Polyphony_snooze(VALUE self);
|
82
89
|
|
83
90
|
VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
|
84
91
|
VALUE Thread_switch_fiber(VALUE thread);
|
@@ -87,4 +94,4 @@ int io_setstrbuf(VALUE *str, long len);
|
|
87
94
|
void io_set_read_length(VALUE str, long n, int shrinkable);
|
88
95
|
VALUE io_enc_str(VALUE str, rb_io_t *fptr);
|
89
96
|
|
90
|
-
#endif /*
|
97
|
+
#endif /* POLYPHONY_H */
|
@@ -0,0 +1,120 @@
|
|
1
|
+
#include "polyphony.h"
|
2
|
+
#include "ring_buffer.h"
|
3
|
+
|
4
|
+
void ring_buffer_init(ring_buffer *buffer) {
|
5
|
+
buffer->size = 1;
|
6
|
+
buffer->count = 0;
|
7
|
+
buffer->entries = malloc(buffer->size * sizeof(VALUE));
|
8
|
+
buffer->head = 0;
|
9
|
+
buffer->tail = 0;
|
10
|
+
}
|
11
|
+
|
12
|
+
void ring_buffer_free(ring_buffer *buffer) {
|
13
|
+
free(buffer->entries);
|
14
|
+
}
|
15
|
+
|
16
|
+
int ring_buffer_empty_p(ring_buffer *buffer) {
|
17
|
+
return buffer->count == 0;
|
18
|
+
}
|
19
|
+
|
20
|
+
#define TRACE_RING_BUFFER(func, buffer) printf( \
|
21
|
+
"%s size: %d count: %d head: %d tail: %d\n", \
|
22
|
+
func, \
|
23
|
+
buffer->size, \
|
24
|
+
buffer->count, \
|
25
|
+
buffer->head, \
|
26
|
+
buffer->tail \
|
27
|
+
)
|
28
|
+
|
29
|
+
VALUE ring_buffer_shift(ring_buffer *buffer) {
|
30
|
+
// TRACE_RING_BUFFER("ring_buffer_shift", buffer);
|
31
|
+
|
32
|
+
VALUE value;
|
33
|
+
if (buffer->count == 0) return Qnil;
|
34
|
+
|
35
|
+
value = buffer->entries[buffer->head];
|
36
|
+
buffer->head = (buffer->head + 1) % buffer->size;
|
37
|
+
buffer->count--;
|
38
|
+
// INSPECT(value);
|
39
|
+
return value;
|
40
|
+
}
|
41
|
+
|
42
|
+
void ring_buffer_resize(ring_buffer *buffer) {
|
43
|
+
// TRACE_RING_BUFFER("ring_buffer_resize", buffer);
|
44
|
+
|
45
|
+
unsigned int old_size = buffer->size;
|
46
|
+
buffer->size = old_size == 1 ? 4 : old_size * 2;
|
47
|
+
// printf("new size: %d\n", buffer->size);
|
48
|
+
buffer->entries = realloc(buffer->entries, buffer->size * sizeof(VALUE));
|
49
|
+
for (unsigned int idx = 0; idx < buffer->head && idx < buffer->tail; idx++)
|
50
|
+
buffer->entries[old_size + idx] = buffer->entries[idx];
|
51
|
+
buffer->tail = buffer->head + buffer->count;
|
52
|
+
}
|
53
|
+
|
54
|
+
void ring_buffer_unshift(ring_buffer *buffer, VALUE value) {
|
55
|
+
// TRACE_RING_BUFFER("ring_buffer_unshift", buffer);
|
56
|
+
// INSPECT(value);
|
57
|
+
|
58
|
+
if (buffer->count == buffer->size) ring_buffer_resize(buffer);
|
59
|
+
|
60
|
+
buffer->head = (buffer->head - 1) % buffer->size;
|
61
|
+
buffer->entries[buffer->head] = value;
|
62
|
+
buffer->count++;
|
63
|
+
}
|
64
|
+
|
65
|
+
void ring_buffer_push(ring_buffer *buffer, VALUE value) {
|
66
|
+
// TRACE_RING_BUFFER("ring_buffer_push", buffer);
|
67
|
+
// INSPECT(value);
|
68
|
+
if (buffer->count == buffer->size) ring_buffer_resize(buffer);
|
69
|
+
|
70
|
+
buffer->entries[buffer->tail] = value;
|
71
|
+
buffer->tail = (buffer->tail + 1) % buffer->size;
|
72
|
+
buffer->count++;
|
73
|
+
}
|
74
|
+
|
75
|
+
void ring_buffer_mark(ring_buffer *buffer) {
|
76
|
+
for (unsigned int i = 0; i < buffer->count; i++)
|
77
|
+
rb_gc_mark(buffer->entries[(buffer->head + i) % buffer->size]);
|
78
|
+
}
|
79
|
+
|
80
|
+
void ring_buffer_shift_each(ring_buffer *buffer) {
|
81
|
+
// TRACE_RING_BUFFER("ring_buffer_shift_each", buffer);
|
82
|
+
|
83
|
+
for (unsigned int i = 0; i < buffer->count; i++)
|
84
|
+
rb_yield(buffer->entries[(buffer->head + i) % buffer->size]);
|
85
|
+
|
86
|
+
buffer->count = buffer->head = buffer->tail = 0;
|
87
|
+
}
|
88
|
+
|
89
|
+
VALUE ring_buffer_shift_all(ring_buffer *buffer) {
|
90
|
+
// TRACE_RING_BUFFER("ring_buffer_all", buffer);
|
91
|
+
VALUE array = rb_ary_new_capa(buffer->count);
|
92
|
+
for (unsigned int i = 0; i < buffer->count; i++)
|
93
|
+
rb_ary_push(array, buffer->entries[(buffer->head + i) % buffer->size]);
|
94
|
+
buffer->count = buffer->head = buffer->tail = 0;
|
95
|
+
return array;
|
96
|
+
}
|
97
|
+
|
98
|
+
void ring_buffer_delete_at(ring_buffer *buffer, unsigned int idx) {
|
99
|
+
for (unsigned int idx2 = idx; idx2 != buffer->tail; idx2 = (idx2 + 1) % buffer->size) {
|
100
|
+
buffer->entries[idx2] = buffer->entries[(idx2 + 1) % buffer->size];
|
101
|
+
}
|
102
|
+
buffer->count--;
|
103
|
+
buffer->tail = (buffer->tail - 1) % buffer->size;
|
104
|
+
}
|
105
|
+
|
106
|
+
void ring_buffer_delete(ring_buffer *buffer, VALUE value) {
|
107
|
+
// TRACE_RING_BUFFER("ring_buffer_delete", buffer);
|
108
|
+
for (unsigned int i = 0; i < buffer->count; i++) {
|
109
|
+
unsigned int idx = (buffer->head + i) % buffer->size;
|
110
|
+
if (buffer->entries[idx] == value) {
|
111
|
+
ring_buffer_delete_at(buffer, idx);
|
112
|
+
return;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
void ring_buffer_clear(ring_buffer *buffer) {
|
118
|
+
// TRACE_RING_BUFFER("ring_buffer_clear", buffer);
|
119
|
+
buffer->count = buffer->head = buffer->tail = 0;
|
120
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#ifndef RING_BUFFER_H
|
2
|
+
#define RING_BUFFER_H
|
3
|
+
|
4
|
+
#include "ruby.h"
|
5
|
+
|
6
|
+
typedef struct ring_buffer {
|
7
|
+
VALUE *entries;
|
8
|
+
unsigned int size;
|
9
|
+
unsigned int count;
|
10
|
+
unsigned int head;
|
11
|
+
unsigned int tail;
|
12
|
+
} ring_buffer;
|
13
|
+
|
14
|
+
void ring_buffer_init(ring_buffer *buffer);
|
15
|
+
void ring_buffer_free(ring_buffer *buffer);
|
16
|
+
void ring_buffer_mark(ring_buffer *buffer);
|
17
|
+
int ring_buffer_empty_p(ring_buffer *buffer);
|
18
|
+
void ring_buffer_clear(ring_buffer *buffer);
|
19
|
+
|
20
|
+
VALUE ring_buffer_shift(ring_buffer *buffer);
|
21
|
+
void ring_buffer_unshift(ring_buffer *buffer, VALUE value);
|
22
|
+
void ring_buffer_push(ring_buffer *buffer, VALUE value);
|
23
|
+
|
24
|
+
void ring_buffer_shift_each(ring_buffer *buffer);
|
25
|
+
VALUE ring_buffer_shift_all(ring_buffer *buffer);
|
26
|
+
void ring_buffer_delete(ring_buffer *buffer, VALUE value);
|
27
|
+
|
28
|
+
#endif /* RING_BUFFER_H */
|
data/ext/polyphony/thread.c
CHANGED
@@ -11,10 +11,9 @@ ID ID_runnable_next;
|
|
11
11
|
ID ID_stop;
|
12
12
|
|
13
13
|
static VALUE Thread_setup_fiber_scheduling(VALUE self) {
|
14
|
-
VALUE queue;
|
14
|
+
VALUE queue = rb_funcall(cLibevQueue, ID_new, 0);
|
15
15
|
|
16
16
|
rb_ivar_set(self, ID_ivar_main_fiber, rb_fiber_current());
|
17
|
-
queue = rb_ary_new();
|
18
17
|
rb_ivar_set(self, ID_run_queue, queue);
|
19
18
|
|
20
19
|
return self;
|
@@ -61,7 +60,7 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
|
61
60
|
}
|
62
61
|
|
63
62
|
queue = rb_ivar_get(self, ID_run_queue);
|
64
|
-
|
63
|
+
LibevQueue_push(queue, fiber);
|
65
64
|
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
66
65
|
|
67
66
|
if (rb_thread_current() != self) {
|
@@ -88,13 +87,13 @@ VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value)
|
|
88
87
|
|
89
88
|
// if fiber is already scheduled, remove it from the run queue
|
90
89
|
if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
|
91
|
-
|
90
|
+
LibevQueue_delete(queue, fiber);
|
92
91
|
} else {
|
93
92
|
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
94
93
|
}
|
95
94
|
|
96
95
|
// the fiber is given priority by putting it at the front of the run queue
|
97
|
-
|
96
|
+
LibevQueue_unshift(queue, fiber);
|
98
97
|
|
99
98
|
if (rb_thread_current() != self) {
|
100
99
|
// if the fiber scheduling is done across threads, we need to make sure the
|
@@ -124,7 +123,7 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
124
123
|
|
125
124
|
ref_count = LibevAgent_ref_count(agent);
|
126
125
|
while (1) {
|
127
|
-
next_fiber =
|
126
|
+
next_fiber = LibevQueue_shift_no_wait(queue);
|
128
127
|
if (next_fiber != Qnil) {
|
129
128
|
if (ref_count > 0) {
|
130
129
|
// this mechanism prevents event starvation in case the run queue never
|
@@ -151,9 +150,15 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
151
150
|
value : rb_funcall(next_fiber, ID_transfer, 1, value);
|
152
151
|
}
|
153
152
|
|
153
|
+
VALUE Thread_run_queue_trace(VALUE self) {
|
154
|
+
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
155
|
+
LibevQueue_trace(queue);
|
156
|
+
return self;
|
157
|
+
}
|
158
|
+
|
154
159
|
VALUE Thread_reset_fiber_scheduling(VALUE self) {
|
155
160
|
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
156
|
-
|
161
|
+
LibevQueue_clear(queue);
|
157
162
|
Thread_fiber_reset_ref_count(self);
|
158
163
|
return self;
|
159
164
|
}
|
@@ -182,6 +187,7 @@ void Init_Thread() {
|
|
182
187
|
rb_define_method(rb_cThread, "schedule_fiber_with_priority",
|
183
188
|
Thread_schedule_fiber_with_priority, 2);
|
184
189
|
rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
|
190
|
+
rb_define_method(rb_cThread, "run_queue_trace", Thread_run_queue_trace, 0);
|
185
191
|
|
186
192
|
ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
|
187
193
|
ID_ivar_agent = rb_intern("@agent");
|
@@ -107,6 +107,15 @@ module ::Kernel
|
|
107
107
|
$stdin.gets
|
108
108
|
end
|
109
109
|
|
110
|
+
alias_method :orig_p, :p
|
111
|
+
def p(*args)
|
112
|
+
strs = args.inject([]) do |m, a|
|
113
|
+
m << a.inspect << "\n"
|
114
|
+
end
|
115
|
+
STDOUT.write *strs
|
116
|
+
args.size == 1 ? args.first : args
|
117
|
+
end
|
118
|
+
|
110
119
|
alias_method :orig_system, :system
|
111
120
|
def system(*args)
|
112
121
|
Open3.popen2(*args) do |i, o, _t|
|
@@ -189,7 +189,7 @@ module Polyphony
|
|
189
189
|
end
|
190
190
|
|
191
191
|
def receive_pending
|
192
|
-
@mailbox.
|
192
|
+
@mailbox.shift_all
|
193
193
|
end
|
194
194
|
end
|
195
195
|
|
@@ -221,6 +221,13 @@ module Polyphony
|
|
221
221
|
def await_all_children
|
222
222
|
return unless @children && !@children.empty?
|
223
223
|
|
224
|
+
# @results = @children.dup
|
225
|
+
# @on_child_done = proc do |c, r|
|
226
|
+
# @results[c] = r
|
227
|
+
# self.schedule if @children.empty?
|
228
|
+
# end
|
229
|
+
# suspend
|
230
|
+
# @results.values
|
224
231
|
Fiber.await(*@children.keys)
|
225
232
|
end
|
226
233
|
|
@@ -108,19 +108,23 @@ class ::IO
|
|
108
108
|
end
|
109
109
|
|
110
110
|
alias_method :orig_readpartial, :read
|
111
|
-
def readpartial(len)
|
111
|
+
def readpartial(len, str = nil)
|
112
112
|
@read_buffer ||= +''
|
113
113
|
result = Thread.current.agent.read(self, @read_buffer, len, false)
|
114
114
|
raise EOFError unless result
|
115
115
|
|
116
|
-
|
116
|
+
if str
|
117
|
+
str << @read_buffer
|
118
|
+
else
|
119
|
+
str = @read_buffer
|
120
|
+
end
|
117
121
|
@read_buffer = +''
|
118
|
-
|
122
|
+
str
|
119
123
|
end
|
120
124
|
|
121
125
|
alias_method :orig_write, :write
|
122
|
-
def write(str)
|
123
|
-
Thread.current.agent.write(self, str)
|
126
|
+
def write(str, *args)
|
127
|
+
Thread.current.agent.write(self, str, *args)
|
124
128
|
end
|
125
129
|
|
126
130
|
alias_method :orig_write_chevron, :<<
|
@@ -166,16 +170,13 @@ class ::IO
|
|
166
170
|
return
|
167
171
|
end
|
168
172
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
str << a
|
175
|
-
str << "\n" unless a =~ /\n$/
|
176
|
-
end
|
173
|
+
strs = args.inject([]) do |m, a|
|
174
|
+
a = a.to_s
|
175
|
+
m << a
|
176
|
+
m << "\n" unless a =~ /\n$/
|
177
|
+
m
|
177
178
|
end
|
178
|
-
write
|
179
|
+
write *strs
|
179
180
|
nil
|
180
181
|
end
|
181
182
|
|
@@ -193,7 +194,7 @@ class ::IO
|
|
193
194
|
|
194
195
|
alias_method :orig_write_nonblock, :write_nonblock
|
195
196
|
def write_nonblock(string, _options = {})
|
196
|
-
write(string
|
197
|
+
write(string)
|
197
198
|
end
|
198
199
|
|
199
200
|
alias_method :orig_read_nonblock, :read_nonblock
|
@@ -5,6 +5,16 @@ require 'socket'
|
|
5
5
|
require_relative './io'
|
6
6
|
require_relative '../core/thread_pool'
|
7
7
|
|
8
|
+
class ::BasicSocket
|
9
|
+
def write_nonblock(string, _options = {})
|
10
|
+
write(string)
|
11
|
+
end
|
12
|
+
|
13
|
+
def read_nonblock(maxlen, str = nil, _options = {})
|
14
|
+
readpartial(maxlen, str)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
8
18
|
# Socket overrides (eventually rewritten in C)
|
9
19
|
class ::Socket
|
10
20
|
def accept
|
data/lib/polyphony/version.rb
CHANGED
data/test/q.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'fiber'
|
5
|
+
require_relative '../lib/polyphony_ext'
|
6
|
+
|
7
|
+
queue = Polyphony::LibevQueue.new
|
8
|
+
|
9
|
+
queue.push :a
|
10
|
+
queue.push :b
|
11
|
+
queue.push :c
|
12
|
+
p [queue.shift_no_wait]
|
13
|
+
queue.push :d
|
14
|
+
p [queue.shift_no_wait]
|
15
|
+
p [queue.shift_no_wait]
|
16
|
+
p [queue.shift_no_wait]
|
17
|
+
p [queue.shift_no_wait]
|
18
|
+
|
19
|
+
queue.unshift :e
|
20
|
+
p [queue.shift_no_wait]
|
21
|
+
|
22
|
+
queue.push :f
|
23
|
+
queue.push :g
|
24
|
+
p [queue.shift_no_wait]
|
data/test/test_global_api.rb
CHANGED
@@ -211,13 +211,13 @@ class SpinLoopTest < MiniTest::Test
|
|
211
211
|
|
212
212
|
def test_spin_loop_location
|
213
213
|
location = /^#{__FILE__}:#{__LINE__ + 1}/
|
214
|
-
f = spin_loop {}
|
214
|
+
f = spin_loop { snooze }
|
215
215
|
|
216
216
|
assert_match location, f.location
|
217
217
|
end
|
218
218
|
|
219
219
|
def test_spin_loop_tag
|
220
|
-
f = spin_loop(:my_loop) {}
|
220
|
+
f = spin_loop(:my_loop) { snooze }
|
221
221
|
|
222
222
|
assert_equal :my_loop, f.tag
|
223
223
|
end
|
data/test/test_io.rb
CHANGED
@@ -30,6 +30,14 @@ class IOTest < MiniTest::Test
|
|
30
30
|
assert_equal 'hello', msg
|
31
31
|
end
|
32
32
|
|
33
|
+
def test_write_multiple_arguments
|
34
|
+
i, o = IO.pipe
|
35
|
+
count = o.write('a', 'b', "\n", 'c')
|
36
|
+
assert_equal 4, count
|
37
|
+
o.close
|
38
|
+
assert_equal "ab\nc", i.read
|
39
|
+
end
|
40
|
+
|
33
41
|
def test_that_double_chevron_method_returns_io
|
34
42
|
assert_equal @o, @o << 'foo'
|
35
43
|
|
@@ -121,7 +129,7 @@ class IOClassMethodsTest < MiniTest::Test
|
|
121
129
|
assert_equal "end\n", lines[-1]
|
122
130
|
end
|
123
131
|
|
124
|
-
def
|
132
|
+
def test_read_class_method
|
125
133
|
s = IO.read(__FILE__)
|
126
134
|
assert_kind_of String, s
|
127
135
|
assert(!s.empty?)
|
@@ -144,7 +152,7 @@ class IOClassMethodsTest < MiniTest::Test
|
|
144
152
|
|
145
153
|
WRITE_DATA = "foo\nbar קוקו"
|
146
154
|
|
147
|
-
def
|
155
|
+
def test_write_class_method
|
148
156
|
fn = '/tmp/test_write'
|
149
157
|
FileUtils.rm(fn) rescue nil
|
150
158
|
|
data/test/test_queue.rb
CHANGED
@@ -8,7 +8,7 @@ class QueueTest < MiniTest::Test
|
|
8
8
|
@queue = Polyphony::Queue.new
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
11
|
+
def test_push_shift
|
12
12
|
spin {
|
13
13
|
@queue << 42
|
14
14
|
}
|
@@ -21,6 +21,18 @@ class QueueTest < MiniTest::Test
|
|
21
21
|
assert_equal [1, 2, 3, 4], buf
|
22
22
|
end
|
23
23
|
|
24
|
+
def test_unshift
|
25
|
+
@queue.push 1
|
26
|
+
@queue.push 2
|
27
|
+
@queue.push 3
|
28
|
+
@queue.unshift 4
|
29
|
+
|
30
|
+
buf = []
|
31
|
+
buf << @queue.shift while !@queue.empty?
|
32
|
+
|
33
|
+
assert_equal [4, 1, 2, 3], buf
|
34
|
+
end
|
35
|
+
|
24
36
|
def test_multiple_waiters
|
25
37
|
a = spin { @queue.shift }
|
26
38
|
b = spin { @queue.shift }
|
@@ -41,6 +53,19 @@ class QueueTest < MiniTest::Test
|
|
41
53
|
buf = []
|
42
54
|
@queue.shift_each { |i| buf << i }
|
43
55
|
assert_equal [1, 2, 3, 4], buf
|
56
|
+
|
57
|
+
buf = []
|
58
|
+
@queue.shift_each { |i| buf << i }
|
59
|
+
assert_equal [], buf
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_shift_all
|
63
|
+
(1..4).each { |i| @queue << i }
|
64
|
+
buf = @queue.shift_all
|
65
|
+
assert_equal [1, 2, 3, 4], buf
|
66
|
+
|
67
|
+
buf = @queue.shift_all
|
68
|
+
assert_equal [], buf
|
44
69
|
end
|
45
70
|
|
46
71
|
def test_empty?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: polyphony
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.43.
|
4
|
+
version: 0.43.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-07-
|
11
|
+
date: 2020-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -353,6 +353,7 @@ files:
|
|
353
353
|
- examples/io/xx-tcpsocket.rb
|
354
354
|
- examples/performance/fs_read.rb
|
355
355
|
- examples/performance/mem-usage.rb
|
356
|
+
- examples/performance/messaging.rb
|
356
357
|
- examples/performance/multi_snooze.rb
|
357
358
|
- examples/performance/snooze.rb
|
358
359
|
- examples/performance/snooze_raw.rb
|
@@ -391,6 +392,8 @@ files:
|
|
391
392
|
- ext/polyphony/polyphony.c
|
392
393
|
- ext/polyphony/polyphony.h
|
393
394
|
- ext/polyphony/polyphony_ext.c
|
395
|
+
- ext/polyphony/ring_buffer.c
|
396
|
+
- ext/polyphony/ring_buffer.h
|
394
397
|
- ext/polyphony/thread.c
|
395
398
|
- ext/polyphony/tracing.c
|
396
399
|
- lib/polyphony.rb
|
@@ -420,6 +423,7 @@ files:
|
|
420
423
|
- test/coverage.rb
|
421
424
|
- test/eg.rb
|
422
425
|
- test/helper.rb
|
426
|
+
- test/q.rb
|
423
427
|
- test/run.rb
|
424
428
|
- test/stress.rb
|
425
429
|
- test/test_agent.rb
|