polyphony 0.43.4 → 0.43.5
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/.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
|