polyphony 0.43.6 → 0.43.8
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 +14 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +2 -3
- data/docs/getting-started/overview.md +2 -2
- data/examples/performance/fiber_transfer.rb +47 -0
- data/ext/polyphony/libev_agent.c +76 -9
- data/ext/polyphony/polyphony.c +0 -2
- data/ext/polyphony/polyphony.h +11 -11
- data/ext/polyphony/polyphony_ext.c +2 -2
- data/ext/polyphony/queue.c +168 -0
- data/ext/polyphony/ring_buffer.c +0 -24
- data/ext/polyphony/thread.c +10 -8
- data/lib/polyphony.rb +0 -3
- data/lib/polyphony/core/resource_pool.rb +1 -1
- data/lib/polyphony/event.rb +5 -15
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +1 -1
- data/test/test_agent.rb +1 -1
- data/test/test_event.rb +11 -0
- data/test/test_io.rb +14 -0
- data/test/test_queue.rb +13 -0
- data/test/test_socket.rb +0 -43
- data/test/test_trace.rb +18 -17
- metadata +5 -4
- data/ext/polyphony/libev_queue.c +0 -288
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bea457e28d23f96570d448855d00cf76250a55fcb02e12c4305cb551cb55faf4
|
4
|
+
data.tar.gz: dc97409e61ce82c20eef25101a2c53046635461ef81302e54b121e5bb9c25aa1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53d345ee472bc77fc993880a1a725064bb934ec2789fa72bd97ec07b558942369860fb7077e0fcff6c95cfcff0c0d712090f70b1d9ff7f652160d15ddfc77de4
|
7
|
+
data.tar.gz: 513a79eeb8a7766078d159cf85d367a6f3f50a0870825275408081d9cfe4e446e7f0b0120bd712f6281ccd0787779ec464b20c9e17c3c60178ceb39c061033c7
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## 0.43.8 2020-07-21
|
2
|
+
|
3
|
+
* Rename `LibevQueue` to `Queue`
|
4
|
+
* Reimplement Event using `Agent#wait_event`
|
5
|
+
* Improve Queue shift queue performance
|
6
|
+
* Introduce `Agent#wait_event` API for waiting on asynchronous events
|
7
|
+
* Minimize `fcntl` syscalls in IO operations
|
8
|
+
|
9
|
+
## 0.43.7 2020-07-20
|
10
|
+
|
11
|
+
* Fix memory leak in ResourcePool (#31)
|
12
|
+
* Check and adjust file position before reading (#30)
|
13
|
+
* Minor documentation fixes
|
14
|
+
|
1
15
|
## 0.43.6 2020-07-18
|
2
16
|
|
3
17
|
* Allow brute-force interrupting with second Ctrl-C
|
data/Gemfile.lock
CHANGED
data/TODO.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
- Reimplement ResourcePool, Channel, Mutex using LibevQueue
|
1
|
+
- Implement `LibevAgent#connect` API
|
2
|
+
- Reimplement ResourcePool, Channel, Mutex using Queue
|
4
3
|
-- Add `Fiber#schedule_with_priority` method, aliased by `Fiber#wakeup`
|
5
4
|
- Implement agent interface is virtual function table
|
6
5
|
- Implement proxy agent for plugging in a user-provided agent class
|
@@ -113,8 +113,8 @@ active concurrent connections, each advancing at its own pace, consuming only a
|
|
113
113
|
single CPU core.
|
114
114
|
|
115
115
|
Nevertheless, Polyphony fully supports multithreading, with each thread having
|
116
|
-
its own fiber run queue and its own libev event loop.
|
117
|
-
|
116
|
+
its own fiber run queue and its own libev event loop. Polyphony even enables
|
117
|
+
cross-thread communication using [fiber messaging](#message-passing).
|
118
118
|
|
119
119
|
## Fibers vs Callbacks
|
120
120
|
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fiber'
|
4
|
+
|
5
|
+
class Fiber
|
6
|
+
attr_accessor :next
|
7
|
+
end
|
8
|
+
|
9
|
+
# This program shows how the performance
|
10
|
+
|
11
|
+
def run(num_fibers)
|
12
|
+
count = 0
|
13
|
+
|
14
|
+
GC.disable
|
15
|
+
|
16
|
+
first = nil
|
17
|
+
last = nil
|
18
|
+
supervisor = Fiber.current
|
19
|
+
num_fibers.times do
|
20
|
+
fiber = Fiber.new do
|
21
|
+
loop do
|
22
|
+
count += 1
|
23
|
+
if count == 1_000_000
|
24
|
+
supervisor.transfer
|
25
|
+
else
|
26
|
+
Fiber.current.next.transfer
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
first ||= fiber
|
31
|
+
last.next = fiber if last
|
32
|
+
last = fiber
|
33
|
+
end
|
34
|
+
|
35
|
+
last.next = first
|
36
|
+
|
37
|
+
t0 = Time.now
|
38
|
+
first.transfer
|
39
|
+
elapsed = Time.now - t0
|
40
|
+
|
41
|
+
puts "fibers: #{num_fibers} count: #{count} rate: #{count / elapsed}"
|
42
|
+
GC.start
|
43
|
+
end
|
44
|
+
|
45
|
+
run(100)
|
46
|
+
run(1000)
|
47
|
+
run(10000)
|
data/ext/polyphony/libev_agent.c
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
#include <netdb.h>
|
2
2
|
#include <sys/socket.h>
|
3
3
|
#include <sys/uio.h>
|
4
|
+
#include <unistd.h>
|
5
|
+
#include <fcntl.h>
|
4
6
|
|
5
7
|
#include "polyphony.h"
|
6
8
|
#include "../libev/ev.h"
|
@@ -131,7 +133,7 @@ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue
|
|
131
133
|
GetLibevAgent(self, agent);
|
132
134
|
|
133
135
|
if (is_nowait) {
|
134
|
-
long runnable_count =
|
136
|
+
long runnable_count = Queue_len(queue);
|
135
137
|
agent->run_no_wait_count++;
|
136
138
|
if (agent->run_no_wait_count < runnable_count || agent->run_no_wait_count < 10)
|
137
139
|
return self;
|
@@ -278,6 +280,31 @@ VALUE libev_snooze() {
|
|
278
280
|
return Thread_switch_fiber(rb_thread_current());
|
279
281
|
}
|
280
282
|
|
283
|
+
ID ID_ivar_is_nonblocking;
|
284
|
+
|
285
|
+
// Since we need to ensure that fd's are non-blocking before every I/O
|
286
|
+
// operation, here we improve upon Ruby's rb_io_set_nonblock by caching the
|
287
|
+
// "nonblock" state in an instance variable. Calling rb_ivar_get on every read
|
288
|
+
// is still much cheaper than doing a fcntl syscall on every read! Preliminary
|
289
|
+
// benchmarks (with a "hello world" HTTP server) show throughput is improved
|
290
|
+
// by 10-13%.
|
291
|
+
inline void io_set_nonblock(rb_io_t *fptr, VALUE io) {
|
292
|
+
#ifdef _WIN32
|
293
|
+
return rb_w32_set_nonblock(fptr->fd);
|
294
|
+
#elif defined(F_GETFL)
|
295
|
+
VALUE is_nonblocking = rb_ivar_get(io, ID_ivar_is_nonblocking);
|
296
|
+
if (is_nonblocking == Qnil) {
|
297
|
+
rb_ivar_set(io, ID_ivar_is_nonblocking, Qtrue);
|
298
|
+
int oflags = fcntl(fptr->fd, F_GETFL);
|
299
|
+
if (oflags == -1) return;
|
300
|
+
if (oflags & O_NONBLOCK) return;
|
301
|
+
oflags |= O_NONBLOCK;
|
302
|
+
fcntl(fptr->fd, F_SETFL, oflags);
|
303
|
+
}
|
304
|
+
#endif
|
305
|
+
return;
|
306
|
+
}
|
307
|
+
|
281
308
|
VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
|
282
309
|
struct LibevAgent_t *agent;
|
283
310
|
struct libev_io watcher;
|
@@ -295,11 +322,20 @@ VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eo
|
|
295
322
|
if (underlying_io != Qnil) io = underlying_io;
|
296
323
|
GetOpenFile(io, fptr);
|
297
324
|
rb_io_check_byte_readable(fptr);
|
298
|
-
|
325
|
+
io_set_nonblock(fptr, io);
|
299
326
|
watcher.fiber = Qnil;
|
300
327
|
|
301
328
|
OBJ_TAINT(str);
|
302
329
|
|
330
|
+
// Apparently after reopening a closed file, the file position is not reset,
|
331
|
+
// which causes the read to fail. Fortunately we can use fptr->rbuf.len to
|
332
|
+
// find out if that's the case.
|
333
|
+
// See: https://github.com/digital-fabric/polyphony/issues/30
|
334
|
+
if (fptr->rbuf.len > 0) {
|
335
|
+
lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
|
336
|
+
fptr->rbuf.len = 0;
|
337
|
+
}
|
338
|
+
|
303
339
|
while (1) {
|
304
340
|
ssize_t n = read(fptr->fd, buf, len - total);
|
305
341
|
if (n < 0) {
|
@@ -349,6 +385,7 @@ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
|
|
349
385
|
shrinkable = io_setstrbuf(&str, len); \
|
350
386
|
buf = RSTRING_PTR(str); \
|
351
387
|
total = 0; \
|
388
|
+
OBJ_TAINT(str); \
|
352
389
|
}
|
353
390
|
|
354
391
|
#define YIELD_STR() { \
|
@@ -375,10 +412,17 @@ VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
|
|
375
412
|
if (underlying_io != Qnil) io = underlying_io;
|
376
413
|
GetOpenFile(io, fptr);
|
377
414
|
rb_io_check_byte_readable(fptr);
|
378
|
-
|
415
|
+
io_set_nonblock(fptr, io);
|
379
416
|
watcher.fiber = Qnil;
|
380
417
|
|
381
|
-
|
418
|
+
// Apparently after reopening a closed file, the file position is not reset,
|
419
|
+
// which causes the read to fail. Fortunately we can use fptr->rbuf.len to
|
420
|
+
// find out if that's the case.
|
421
|
+
// See: https://github.com/digital-fabric/polyphony/issues/30
|
422
|
+
if (fptr->rbuf.len > 0) {
|
423
|
+
lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
|
424
|
+
fptr->rbuf.len = 0;
|
425
|
+
}
|
382
426
|
|
383
427
|
while (1) {
|
384
428
|
ssize_t n = read(fptr->fd, buf, len);
|
@@ -553,7 +597,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
553
597
|
|
554
598
|
GetLibevAgent(self, agent);
|
555
599
|
GetOpenFile(sock, fptr);
|
556
|
-
|
600
|
+
io_set_nonblock(fptr, sock);
|
557
601
|
watcher.fiber = Qnil;
|
558
602
|
while (1) {
|
559
603
|
fd = accept(fptr->fd, &addr, &len);
|
@@ -579,7 +623,7 @@ VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
|
579
623
|
fp->fd = fd;
|
580
624
|
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
581
625
|
rb_io_ascii8bit_binmode(socket);
|
582
|
-
|
626
|
+
io_set_nonblock(fp, socket);
|
583
627
|
rb_io_synchronized(fp);
|
584
628
|
|
585
629
|
// if (rsock_do_not_reverse_lookup) {
|
@@ -608,7 +652,7 @@ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
|
|
608
652
|
|
609
653
|
GetLibevAgent(self, agent);
|
610
654
|
GetOpenFile(sock, fptr);
|
611
|
-
|
655
|
+
io_set_nonblock(fptr, sock);
|
612
656
|
watcher.fiber = Qnil;
|
613
657
|
|
614
658
|
while (1) {
|
@@ -634,7 +678,7 @@ VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
|
|
634
678
|
fp->fd = fd;
|
635
679
|
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
636
680
|
rb_io_ascii8bit_binmode(socket);
|
637
|
-
|
681
|
+
io_set_nonblock(fp, socket);
|
638
682
|
rb_io_synchronized(fp);
|
639
683
|
|
640
684
|
rb_yield(socket);
|
@@ -662,7 +706,7 @@ error:
|
|
662
706
|
|
663
707
|
// GetLibevAgent(self, agent);
|
664
708
|
// GetOpenFile(sock, fptr);
|
665
|
-
//
|
709
|
+
// io_set_nonblock(fptr, sock);
|
666
710
|
// watcher.fiber = Qnil;
|
667
711
|
|
668
712
|
// addr.sin_family = AF_INET;
|
@@ -737,6 +781,7 @@ VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
|
|
737
781
|
ev_timer_start(agent->ev_loop, &watcher.timer);
|
738
782
|
|
739
783
|
switchpoint_result = libev_await(agent);
|
784
|
+
|
740
785
|
ev_timer_stop(agent->ev_loop, &watcher.timer);
|
741
786
|
|
742
787
|
TEST_RESUME_EXCEPTION(switchpoint_result);
|
@@ -785,6 +830,25 @@ struct ev_loop *LibevAgent_ev_loop(VALUE self) {
|
|
785
830
|
return agent->ev_loop;
|
786
831
|
}
|
787
832
|
|
833
|
+
void LibevAgent_async_callback(EV_P_ ev_async *w, int revents) { }
|
834
|
+
|
835
|
+
VALUE LibevAgent_wait_event(VALUE self, VALUE raise) {
|
836
|
+
struct LibevAgent_t *agent;
|
837
|
+
struct ev_async async;
|
838
|
+
VALUE switchpoint_result = Qnil;
|
839
|
+
GetLibevAgent(self, agent);
|
840
|
+
|
841
|
+
ev_async_init(&async, LibevAgent_async_callback);
|
842
|
+
ev_async_start(agent->ev_loop, &async);
|
843
|
+
|
844
|
+
switchpoint_result = libev_await(agent);
|
845
|
+
ev_async_stop(agent->ev_loop, &async);
|
846
|
+
|
847
|
+
if (RTEST(raise)) TEST_RESUME_EXCEPTION(switchpoint_result);
|
848
|
+
RB_GC_GUARD(switchpoint_result);
|
849
|
+
return switchpoint_result;
|
850
|
+
}
|
851
|
+
|
788
852
|
void Init_LibevAgent() {
|
789
853
|
rb_require("socket");
|
790
854
|
cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
|
@@ -812,4 +876,7 @@ void Init_LibevAgent() {
|
|
812
876
|
rb_define_method(cLibevAgent, "wait_io", LibevAgent_wait_io, 2);
|
813
877
|
rb_define_method(cLibevAgent, "sleep", LibevAgent_sleep, 1);
|
814
878
|
rb_define_method(cLibevAgent, "waitpid", LibevAgent_waitpid, 1);
|
879
|
+
rb_define_method(cLibevAgent, "wait_event", LibevAgent_wait_event, 1);
|
880
|
+
|
881
|
+
ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
|
815
882
|
}
|
data/ext/polyphony/polyphony.c
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
VALUE mPolyphony;
|
4
4
|
|
5
|
-
ID ID_await_no_raise;
|
6
5
|
ID ID_call;
|
7
6
|
ID ID_caller;
|
8
7
|
ID ID_clear;
|
@@ -54,7 +53,6 @@ void Init_Polyphony() {
|
|
54
53
|
rb_define_global_function("snooze", Polyphony_snooze, 0);
|
55
54
|
rb_define_global_function("suspend", Polyphony_suspend, 0);
|
56
55
|
|
57
|
-
ID_await_no_raise = rb_intern("await_no_raise");
|
58
56
|
ID_call = rb_intern("call");
|
59
57
|
ID_caller = rb_intern("caller");
|
60
58
|
ID_clear = rb_intern("clear");
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -19,10 +19,9 @@
|
|
19
19
|
}
|
20
20
|
|
21
21
|
extern VALUE mPolyphony;
|
22
|
-
extern VALUE
|
22
|
+
extern VALUE cQueue;
|
23
23
|
extern VALUE cEvent;
|
24
24
|
|
25
|
-
extern ID ID_await_no_raise;
|
26
25
|
extern ID ID_call;
|
27
26
|
extern ID ID_caller;
|
28
27
|
extern ID ID_clear;
|
@@ -75,15 +74,16 @@ VALUE LibevAgent_ref(VALUE self);
|
|
75
74
|
VALUE LibevAgent_unref(VALUE self);
|
76
75
|
int LibevAgent_ref_count(VALUE self);
|
77
76
|
void LibevAgent_reset_ref_count(VALUE self);
|
78
|
-
|
79
|
-
|
80
|
-
VALUE
|
81
|
-
VALUE
|
82
|
-
VALUE
|
83
|
-
VALUE
|
84
|
-
VALUE
|
85
|
-
|
86
|
-
|
77
|
+
VALUE LibevAgent_wait_event(VALUE self, VALUE raise);
|
78
|
+
|
79
|
+
VALUE Queue_push(VALUE self, VALUE value);
|
80
|
+
VALUE Queue_unshift(VALUE self, VALUE value);
|
81
|
+
VALUE Queue_shift(VALUE self);
|
82
|
+
VALUE Queue_shift_no_wait(VALUE self);
|
83
|
+
VALUE Queue_clear(VALUE self);
|
84
|
+
VALUE Queue_delete(VALUE self, VALUE value);
|
85
|
+
long Queue_len(VALUE self);
|
86
|
+
void Queue_trace(VALUE self);
|
87
87
|
|
88
88
|
VALUE Polyphony_snooze(VALUE self);
|
89
89
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
void Init_Fiber();
|
4
4
|
void Init_Polyphony();
|
5
5
|
void Init_LibevAgent();
|
6
|
-
void
|
6
|
+
void Init_Queue();
|
7
7
|
void Init_Thread();
|
8
8
|
void Init_Tracing();
|
9
9
|
|
@@ -12,7 +12,7 @@ void Init_polyphony_ext() {
|
|
12
12
|
|
13
13
|
Init_Polyphony();
|
14
14
|
Init_LibevAgent();
|
15
|
-
|
15
|
+
Init_Queue();
|
16
16
|
|
17
17
|
Init_Fiber();
|
18
18
|
Init_Thread();
|
@@ -0,0 +1,168 @@
|
|
1
|
+
#include "polyphony.h"
|
2
|
+
#include "ring_buffer.h"
|
3
|
+
|
4
|
+
typedef struct queue {
|
5
|
+
ring_buffer values;
|
6
|
+
ring_buffer shift_queue;
|
7
|
+
} Queue_t;
|
8
|
+
|
9
|
+
VALUE cQueue = Qnil;
|
10
|
+
|
11
|
+
static void Queue_mark(void *ptr) {
|
12
|
+
Queue_t *queue = ptr;
|
13
|
+
ring_buffer_mark(&queue->values);
|
14
|
+
ring_buffer_mark(&queue->shift_queue);
|
15
|
+
}
|
16
|
+
|
17
|
+
static void Queue_free(void *ptr) {
|
18
|
+
Queue_t *queue = ptr;
|
19
|
+
ring_buffer_free(&queue->values);
|
20
|
+
ring_buffer_free(&queue->shift_queue);
|
21
|
+
xfree(ptr);
|
22
|
+
}
|
23
|
+
|
24
|
+
static size_t Queue_size(const void *ptr) {
|
25
|
+
return sizeof(Queue_t);
|
26
|
+
}
|
27
|
+
|
28
|
+
static const rb_data_type_t Queue_type = {
|
29
|
+
"Queue",
|
30
|
+
{Queue_mark, Queue_free, Queue_size,},
|
31
|
+
0, 0, 0
|
32
|
+
};
|
33
|
+
|
34
|
+
static VALUE Queue_allocate(VALUE klass) {
|
35
|
+
Queue_t *queue;
|
36
|
+
|
37
|
+
queue = ALLOC(Queue_t);
|
38
|
+
return TypedData_Wrap_Struct(klass, &Queue_type, queue);
|
39
|
+
}
|
40
|
+
|
41
|
+
#define GetQueue(obj, queue) \
|
42
|
+
TypedData_Get_Struct((obj), Queue_t, &Queue_type, (queue))
|
43
|
+
|
44
|
+
static VALUE Queue_initialize(VALUE self) {
|
45
|
+
Queue_t *queue;
|
46
|
+
GetQueue(self, queue);
|
47
|
+
|
48
|
+
ring_buffer_init(&queue->values);
|
49
|
+
ring_buffer_init(&queue->shift_queue);
|
50
|
+
|
51
|
+
return self;
|
52
|
+
}
|
53
|
+
|
54
|
+
VALUE Queue_push(VALUE self, VALUE value) {
|
55
|
+
Queue_t *queue;
|
56
|
+
GetQueue(self, queue);
|
57
|
+
if (queue->shift_queue.count > 0) {
|
58
|
+
VALUE fiber = ring_buffer_shift(&queue->shift_queue);
|
59
|
+
if (fiber != Qnil) Fiber_make_runnable(fiber, Qnil);
|
60
|
+
}
|
61
|
+
ring_buffer_push(&queue->values, value);
|
62
|
+
return self;
|
63
|
+
}
|
64
|
+
|
65
|
+
VALUE Queue_unshift(VALUE self, VALUE value) {
|
66
|
+
Queue_t *queue;
|
67
|
+
GetQueue(self, queue);
|
68
|
+
if (queue->shift_queue.count > 0) {
|
69
|
+
VALUE fiber = ring_buffer_shift(&queue->shift_queue);
|
70
|
+
if (fiber != Qnil) Fiber_make_runnable(fiber, Qnil);
|
71
|
+
}
|
72
|
+
ring_buffer_unshift(&queue->values, value);
|
73
|
+
return self;
|
74
|
+
}
|
75
|
+
|
76
|
+
VALUE Queue_shift(VALUE self) {
|
77
|
+
Queue_t *queue;
|
78
|
+
GetQueue(self, queue);
|
79
|
+
|
80
|
+
if (queue->values.count == 0) {
|
81
|
+
VALUE agent = rb_ivar_get(rb_thread_current(), ID_ivar_agent);
|
82
|
+
VALUE fiber = rb_fiber_current();
|
83
|
+
VALUE switchpoint_result = Qnil;
|
84
|
+
ring_buffer_push(&queue->shift_queue, fiber);
|
85
|
+
switchpoint_result = LibevAgent_wait_event(agent, Qnil);
|
86
|
+
if (RTEST(rb_obj_is_kind_of(switchpoint_result, rb_eException))) {
|
87
|
+
ring_buffer_delete(&queue->shift_queue, fiber);
|
88
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
89
|
+
}
|
90
|
+
RB_GC_GUARD(agent);
|
91
|
+
RB_GC_GUARD(switchpoint_result);
|
92
|
+
}
|
93
|
+
|
94
|
+
return ring_buffer_shift(&queue->values);
|
95
|
+
}
|
96
|
+
|
97
|
+
VALUE Queue_shift_no_wait(VALUE self) {
|
98
|
+
Queue_t *queue;
|
99
|
+
GetQueue(self, queue);
|
100
|
+
|
101
|
+
return ring_buffer_shift(&queue->values);
|
102
|
+
}
|
103
|
+
|
104
|
+
VALUE Queue_delete(VALUE self, VALUE value) {
|
105
|
+
Queue_t *queue;
|
106
|
+
GetQueue(self, queue);
|
107
|
+
|
108
|
+
ring_buffer_delete(&queue->values, value);
|
109
|
+
return self;
|
110
|
+
}
|
111
|
+
|
112
|
+
VALUE Queue_clear(VALUE self) {
|
113
|
+
Queue_t *queue;
|
114
|
+
GetQueue(self, queue);
|
115
|
+
|
116
|
+
ring_buffer_clear(&queue->values);
|
117
|
+
return self;
|
118
|
+
}
|
119
|
+
|
120
|
+
long Queue_len(VALUE self) {
|
121
|
+
Queue_t *queue;
|
122
|
+
GetQueue(self, queue);
|
123
|
+
|
124
|
+
return queue->values.count;
|
125
|
+
}
|
126
|
+
|
127
|
+
VALUE Queue_shift_each(VALUE self) {
|
128
|
+
Queue_t *queue;
|
129
|
+
GetQueue(self, queue);
|
130
|
+
|
131
|
+
ring_buffer_shift_each(&queue->values);
|
132
|
+
return self;
|
133
|
+
}
|
134
|
+
|
135
|
+
VALUE Queue_shift_all(VALUE self) {
|
136
|
+
Queue_t *queue;
|
137
|
+
GetQueue(self, queue);
|
138
|
+
|
139
|
+
return ring_buffer_shift_all(&queue->values);
|
140
|
+
}
|
141
|
+
|
142
|
+
VALUE Queue_empty_p(VALUE self) {
|
143
|
+
Queue_t *queue;
|
144
|
+
GetQueue(self, queue);
|
145
|
+
|
146
|
+
return (queue->values.count == 0) ? Qtrue : Qfalse;
|
147
|
+
}
|
148
|
+
|
149
|
+
void Init_Queue() {
|
150
|
+
cQueue = rb_define_class_under(mPolyphony, "Queue", rb_cData);
|
151
|
+
rb_define_alloc_func(cQueue, Queue_allocate);
|
152
|
+
|
153
|
+
rb_define_method(cQueue, "initialize", Queue_initialize, 0);
|
154
|
+
rb_define_method(cQueue, "push", Queue_push, 1);
|
155
|
+
rb_define_method(cQueue, "<<", Queue_push, 1);
|
156
|
+
rb_define_method(cQueue, "unshift", Queue_unshift, 1);
|
157
|
+
|
158
|
+
rb_define_method(cQueue, "shift", Queue_shift, 0);
|
159
|
+
rb_define_method(cQueue, "pop", Queue_shift, 0);
|
160
|
+
rb_define_method(cQueue, "shift_no_wait", Queue_shift_no_wait, 0);
|
161
|
+
rb_define_method(cQueue, "delete", Queue_delete, 1);
|
162
|
+
|
163
|
+
rb_define_method(cQueue, "shift_each", Queue_shift_each, 0);
|
164
|
+
rb_define_method(cQueue, "shift_all", Queue_shift_all, 0);
|
165
|
+
rb_define_method(cQueue, "empty?", Queue_empty_p, 0);
|
166
|
+
}
|
167
|
+
|
168
|
+
|
data/ext/polyphony/ring_buffer.c
CHANGED
@@ -17,18 +17,7 @@ int ring_buffer_empty_p(ring_buffer *buffer) {
|
|
17
17
|
return buffer->count == 0;
|
18
18
|
}
|
19
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
20
|
VALUE ring_buffer_shift(ring_buffer *buffer) {
|
30
|
-
// TRACE_RING_BUFFER("ring_buffer_shift", buffer);
|
31
|
-
|
32
21
|
VALUE value;
|
33
22
|
if (buffer->count == 0) return Qnil;
|
34
23
|
|
@@ -40,11 +29,8 @@ VALUE ring_buffer_shift(ring_buffer *buffer) {
|
|
40
29
|
}
|
41
30
|
|
42
31
|
void ring_buffer_resize(ring_buffer *buffer) {
|
43
|
-
// TRACE_RING_BUFFER("ring_buffer_resize", buffer);
|
44
|
-
|
45
32
|
unsigned int old_size = buffer->size;
|
46
33
|
buffer->size = old_size == 1 ? 4 : old_size * 2;
|
47
|
-
// printf("new size: %d\n", buffer->size);
|
48
34
|
buffer->entries = realloc(buffer->entries, buffer->size * sizeof(VALUE));
|
49
35
|
for (unsigned int idx = 0; idx < buffer->head && idx < buffer->tail; idx++)
|
50
36
|
buffer->entries[old_size + idx] = buffer->entries[idx];
|
@@ -52,9 +38,6 @@ void ring_buffer_resize(ring_buffer *buffer) {
|
|
52
38
|
}
|
53
39
|
|
54
40
|
void ring_buffer_unshift(ring_buffer *buffer, VALUE value) {
|
55
|
-
// TRACE_RING_BUFFER("ring_buffer_unshift", buffer);
|
56
|
-
// INSPECT(value);
|
57
|
-
|
58
41
|
if (buffer->count == buffer->size) ring_buffer_resize(buffer);
|
59
42
|
|
60
43
|
buffer->head = (buffer->head - 1) % buffer->size;
|
@@ -63,8 +46,6 @@ void ring_buffer_unshift(ring_buffer *buffer, VALUE value) {
|
|
63
46
|
}
|
64
47
|
|
65
48
|
void ring_buffer_push(ring_buffer *buffer, VALUE value) {
|
66
|
-
// TRACE_RING_BUFFER("ring_buffer_push", buffer);
|
67
|
-
// INSPECT(value);
|
68
49
|
if (buffer->count == buffer->size) ring_buffer_resize(buffer);
|
69
50
|
|
70
51
|
buffer->entries[buffer->tail] = value;
|
@@ -78,8 +59,6 @@ void ring_buffer_mark(ring_buffer *buffer) {
|
|
78
59
|
}
|
79
60
|
|
80
61
|
void ring_buffer_shift_each(ring_buffer *buffer) {
|
81
|
-
// TRACE_RING_BUFFER("ring_buffer_shift_each", buffer);
|
82
|
-
|
83
62
|
for (unsigned int i = 0; i < buffer->count; i++)
|
84
63
|
rb_yield(buffer->entries[(buffer->head + i) % buffer->size]);
|
85
64
|
|
@@ -87,7 +66,6 @@ void ring_buffer_shift_each(ring_buffer *buffer) {
|
|
87
66
|
}
|
88
67
|
|
89
68
|
VALUE ring_buffer_shift_all(ring_buffer *buffer) {
|
90
|
-
// TRACE_RING_BUFFER("ring_buffer_all", buffer);
|
91
69
|
VALUE array = rb_ary_new_capa(buffer->count);
|
92
70
|
for (unsigned int i = 0; i < buffer->count; i++)
|
93
71
|
rb_ary_push(array, buffer->entries[(buffer->head + i) % buffer->size]);
|
@@ -104,7 +82,6 @@ void ring_buffer_delete_at(ring_buffer *buffer, unsigned int idx) {
|
|
104
82
|
}
|
105
83
|
|
106
84
|
void ring_buffer_delete(ring_buffer *buffer, VALUE value) {
|
107
|
-
// TRACE_RING_BUFFER("ring_buffer_delete", buffer);
|
108
85
|
for (unsigned int i = 0; i < buffer->count; i++) {
|
109
86
|
unsigned int idx = (buffer->head + i) % buffer->size;
|
110
87
|
if (buffer->entries[idx] == value) {
|
@@ -115,6 +92,5 @@ void ring_buffer_delete(ring_buffer *buffer, VALUE value) {
|
|
115
92
|
}
|
116
93
|
|
117
94
|
void ring_buffer_clear(ring_buffer *buffer) {
|
118
|
-
// TRACE_RING_BUFFER("ring_buffer_clear", buffer);
|
119
95
|
buffer->count = buffer->head = buffer->tail = 0;
|
120
96
|
}
|
data/ext/polyphony/thread.c
CHANGED
@@ -11,7 +11,7 @@ ID ID_runnable_next;
|
|
11
11
|
ID ID_stop;
|
12
12
|
|
13
13
|
static VALUE Thread_setup_fiber_scheduling(VALUE self) {
|
14
|
-
VALUE queue = rb_funcall(
|
14
|
+
VALUE queue = rb_funcall(cQueue, ID_new, 0);
|
15
15
|
|
16
16
|
rb_ivar_set(self, ID_ivar_main_fiber, rb_fiber_current());
|
17
17
|
rb_ivar_set(self, ID_run_queue, queue);
|
@@ -60,7 +60,7 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
|
60
60
|
}
|
61
61
|
|
62
62
|
queue = rb_ivar_get(self, ID_run_queue);
|
63
|
-
|
63
|
+
Queue_push(queue, fiber);
|
64
64
|
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
65
65
|
|
66
66
|
if (rb_thread_current() != self) {
|
@@ -87,13 +87,13 @@ VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value)
|
|
87
87
|
|
88
88
|
// if fiber is already scheduled, remove it from the run queue
|
89
89
|
if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
|
90
|
-
|
90
|
+
Queue_delete(queue, fiber);
|
91
91
|
} else {
|
92
92
|
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
93
93
|
}
|
94
94
|
|
95
95
|
// the fiber is given priority by putting it at the front of the run queue
|
96
|
-
|
96
|
+
Queue_unshift(queue, fiber);
|
97
97
|
|
98
98
|
if (rb_thread_current() != self) {
|
99
99
|
// if the fiber scheduling is done across threads, we need to make sure the
|
@@ -114,6 +114,7 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
114
114
|
VALUE value;
|
115
115
|
VALUE agent = rb_ivar_get(self, ID_ivar_agent);
|
116
116
|
int ref_count;
|
117
|
+
int agent_was_polled = 0;1;
|
117
118
|
|
118
119
|
if (__tracing_enabled__) {
|
119
120
|
if (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse) {
|
@@ -123,9 +124,9 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
123
124
|
|
124
125
|
ref_count = LibevAgent_ref_count(agent);
|
125
126
|
while (1) {
|
126
|
-
next_fiber =
|
127
|
+
next_fiber = Queue_shift_no_wait(queue);
|
127
128
|
if (next_fiber != Qnil) {
|
128
|
-
if (ref_count > 0) {
|
129
|
+
if (agent_was_polled == 0 && ref_count > 0) {
|
129
130
|
// this mechanism prevents event starvation in case the run queue never
|
130
131
|
// empties
|
131
132
|
LibevAgent_poll(agent, Qtrue, current_fiber, queue);
|
@@ -135,6 +136,7 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
135
136
|
if (ref_count == 0) break;
|
136
137
|
|
137
138
|
LibevAgent_poll(agent, Qnil, current_fiber, queue);
|
139
|
+
agent_was_polled = 1;
|
138
140
|
}
|
139
141
|
|
140
142
|
if (next_fiber == Qnil) return Qnil;
|
@@ -152,13 +154,13 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
152
154
|
|
153
155
|
VALUE Thread_run_queue_trace(VALUE self) {
|
154
156
|
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
155
|
-
|
157
|
+
Queue_trace(queue);
|
156
158
|
return self;
|
157
159
|
}
|
158
160
|
|
159
161
|
VALUE Thread_reset_fiber_scheduling(VALUE self) {
|
160
162
|
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
161
|
-
|
163
|
+
Queue_clear(queue);
|
162
164
|
Thread_fiber_reset_ref_count(self);
|
163
165
|
return self;
|
164
166
|
}
|
data/lib/polyphony.rb
CHANGED
data/lib/polyphony/event.rb
CHANGED
@@ -3,25 +3,15 @@
|
|
3
3
|
module Polyphony
|
4
4
|
# Event watcher for thread-safe synchronisation
|
5
5
|
class Event
|
6
|
-
def initialize
|
7
|
-
@i, @o = IO.pipe
|
8
|
-
end
|
9
|
-
|
10
6
|
def await
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@value
|
15
|
-
end
|
16
|
-
|
17
|
-
def await_no_raise
|
18
|
-
Thread.current.agent.read(@i, +'', 8192, false)
|
19
|
-
@value
|
7
|
+
@fiber = Fiber.current
|
8
|
+
Thread.current.agent.wait_event(true)
|
20
9
|
end
|
21
10
|
|
22
11
|
def signal(value = nil)
|
23
|
-
@value
|
24
|
-
|
12
|
+
@fiber&.schedule(value)
|
13
|
+
ensure
|
14
|
+
@fiber = nil
|
25
15
|
end
|
26
16
|
end
|
27
17
|
end
|
data/lib/polyphony/version.rb
CHANGED
data/test/helper.rb
CHANGED
data/test/test_agent.rb
CHANGED
data/test/test_event.rb
CHANGED
@@ -45,4 +45,15 @@ class EventTest < MiniTest::Test
|
|
45
45
|
t&.kill
|
46
46
|
t&.join
|
47
47
|
end
|
48
|
+
|
49
|
+
def test_exception_while_waiting_for_event
|
50
|
+
e = Polyphony::Event.new
|
51
|
+
|
52
|
+
f = spin { e.await }
|
53
|
+
g = spin { f.raise 'foo' }
|
54
|
+
|
55
|
+
assert_raises(RuntimeError) do
|
56
|
+
f.await
|
57
|
+
end
|
58
|
+
end
|
48
59
|
end
|
data/test/test_io.rb
CHANGED
@@ -91,6 +91,20 @@ class IOTest < MiniTest::Test
|
|
91
91
|
|
92
92
|
assert_raises(EOFError) { i.readpartial(1) }
|
93
93
|
end
|
94
|
+
|
95
|
+
# see https://github.com/digital-fabric/polyphony/issues/30
|
96
|
+
def test_reopened_tempfile
|
97
|
+
file = Tempfile.new
|
98
|
+
file << 'hello: world'
|
99
|
+
file.close
|
100
|
+
|
101
|
+
buf = nil
|
102
|
+
File.open(file, 'r:bom|utf-8') do |f|
|
103
|
+
buf = f.read(16384)
|
104
|
+
end
|
105
|
+
|
106
|
+
assert_equal 'hello: world', buf
|
107
|
+
end
|
94
108
|
end
|
95
109
|
|
96
110
|
class IOClassMethodsTest < MiniTest::Test
|
data/test/test_queue.rb
CHANGED
@@ -96,4 +96,17 @@ class QueueTest < MiniTest::Test
|
|
96
96
|
assert_nil f2.await
|
97
97
|
assert_equal :bar, f3.await
|
98
98
|
end
|
99
|
+
|
100
|
+
def test_fiber_removal_from_queue_simple
|
101
|
+
f1 = spin { @queue.shift }
|
102
|
+
|
103
|
+
# let fibers run
|
104
|
+
snooze
|
105
|
+
|
106
|
+
f1.stop
|
107
|
+
snooze
|
108
|
+
|
109
|
+
@queue << :foo
|
110
|
+
assert_nil f1.await
|
111
|
+
end
|
99
112
|
end
|
data/test/test_socket.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'helper'
|
4
|
-
require 'localhost/authority'
|
5
4
|
|
6
5
|
class SocketTest < MiniTest::Test
|
7
6
|
def setup
|
@@ -32,46 +31,4 @@ class SocketTest < MiniTest::Test
|
|
32
31
|
server_fiber&.await
|
33
32
|
server&.close
|
34
33
|
end
|
35
|
-
|
36
|
-
def test_openssl
|
37
|
-
authority = Localhost::Authority.fetch
|
38
|
-
context = authority.server_context
|
39
|
-
port = rand(1234..5678)
|
40
|
-
sock = TCPServer.new('127.0.0.1', port)
|
41
|
-
server = OpenSSL::SSL::SSLServer.new(sock, context)
|
42
|
-
|
43
|
-
server_fiber = spin do
|
44
|
-
while (socket = server.accept)
|
45
|
-
puts "accepted: #{socket.inspect}"
|
46
|
-
spin do
|
47
|
-
while (data = socket.gets(8192))
|
48
|
-
socket << data
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
snooze
|
55
|
-
p 1
|
56
|
-
sock = TCPSocket.new('127.0.0.1', port)
|
57
|
-
p 2
|
58
|
-
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
59
|
-
context = OpenSSL::SSL::SSLContext.new
|
60
|
-
context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
61
|
-
client = OpenSSL::SSL::SSLSocket.new(sock, context)
|
62
|
-
client.sync_close = true
|
63
|
-
client.hostname = 'localhost'
|
64
|
-
p 3
|
65
|
-
client.connect
|
66
|
-
p 4
|
67
|
-
client.write("GET / HTTP/1.0\r\nHost: realiteq.net\r\n\r\n")#("1234\n")
|
68
|
-
p 5
|
69
|
-
assert_equal "1234\n", client.readpartial(8192)
|
70
|
-
p 6
|
71
|
-
client.close
|
72
|
-
ensure
|
73
|
-
server_fiber&.stop
|
74
|
-
server_fiber&.await
|
75
|
-
server&.close
|
76
|
-
end
|
77
34
|
end
|
data/test/test_trace.rb
CHANGED
@@ -34,7 +34,8 @@ class TraceTest < MiniTest::Test
|
|
34
34
|
|
35
35
|
def test_2_fiber_trace
|
36
36
|
records = []
|
37
|
-
|
37
|
+
thread = Thread.current
|
38
|
+
t = Polyphony::Trace.new(:fiber_all) { |r| records << r if Thread.current == thread && r[:event] =~ /^fiber_/ }
|
38
39
|
t.enable
|
39
40
|
Polyphony.trace(true)
|
40
41
|
|
@@ -42,23 +43,23 @@ class TraceTest < MiniTest::Test
|
|
42
43
|
suspend
|
43
44
|
sleep 0
|
44
45
|
|
45
|
-
events = records.map { |r| [r[:fiber], r[:event]] }
|
46
|
+
events = records.map { |r| [r[:fiber] == f ? :f : :current, r[:event]] }
|
46
47
|
assert_equal [
|
47
|
-
[f, :fiber_create],
|
48
|
-
[f, :fiber_schedule],
|
49
|
-
[
|
50
|
-
[f, :fiber_run],
|
51
|
-
[f, :fiber_switchpoint],
|
52
|
-
[f, :fiber_ev_loop_enter],
|
53
|
-
[f, :fiber_schedule],
|
54
|
-
[f, :fiber_ev_loop_leave],
|
55
|
-
[f, :fiber_run],
|
56
|
-
[f, :fiber_terminate],
|
57
|
-
[
|
58
|
-
[
|
59
|
-
[
|
60
|
-
[
|
61
|
-
[
|
48
|
+
[:f, :fiber_create],
|
49
|
+
[:f, :fiber_schedule],
|
50
|
+
[:current, :fiber_switchpoint],
|
51
|
+
[:f, :fiber_run],
|
52
|
+
[:f, :fiber_switchpoint],
|
53
|
+
[:f, :fiber_ev_loop_enter],
|
54
|
+
[:f, :fiber_schedule],
|
55
|
+
[:f, :fiber_ev_loop_leave],
|
56
|
+
[:f, :fiber_run],
|
57
|
+
[:f, :fiber_terminate],
|
58
|
+
[:current, :fiber_switchpoint],
|
59
|
+
[:current, :fiber_ev_loop_enter],
|
60
|
+
[:current, :fiber_schedule],
|
61
|
+
[:current, :fiber_ev_loop_leave],
|
62
|
+
[:current, :fiber_run]
|
62
63
|
], events
|
63
64
|
ensure
|
64
65
|
t&.disable
|
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.8
|
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-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -354,6 +354,7 @@ files:
|
|
354
354
|
- examples/io/xx-tcpserver.rb
|
355
355
|
- examples/io/xx-tcpsocket.rb
|
356
356
|
- examples/io/xx-zip.rb
|
357
|
+
- examples/performance/fiber_transfer.rb
|
357
358
|
- examples/performance/fs_read.rb
|
358
359
|
- examples/performance/mem-usage.rb
|
359
360
|
- examples/performance/messaging.rb
|
@@ -392,10 +393,10 @@ files:
|
|
392
393
|
- ext/polyphony/libev.c
|
393
394
|
- ext/polyphony/libev.h
|
394
395
|
- ext/polyphony/libev_agent.c
|
395
|
-
- ext/polyphony/libev_queue.c
|
396
396
|
- ext/polyphony/polyphony.c
|
397
397
|
- ext/polyphony/polyphony.h
|
398
398
|
- ext/polyphony/polyphony_ext.c
|
399
|
+
- ext/polyphony/queue.c
|
399
400
|
- ext/polyphony/ring_buffer.c
|
400
401
|
- ext/polyphony/ring_buffer.h
|
401
402
|
- ext/polyphony/thread.c
|
@@ -474,7 +475,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
474
475
|
- !ruby/object:Gem::Version
|
475
476
|
version: '0'
|
476
477
|
requirements: []
|
477
|
-
rubygems_version: 3.
|
478
|
+
rubygems_version: 3.1.2
|
478
479
|
signing_key:
|
479
480
|
specification_version: 4
|
480
481
|
summary: Fine grained concurrency for Ruby
|
data/ext/polyphony/libev_queue.c
DELETED
@@ -1,288 +0,0 @@
|
|
1
|
-
#include "polyphony.h"
|
2
|
-
#include "ring_buffer.h"
|
3
|
-
|
4
|
-
struct async_watcher {
|
5
|
-
ev_async async;
|
6
|
-
struct ev_loop *ev_loop;
|
7
|
-
VALUE fiber;
|
8
|
-
};
|
9
|
-
|
10
|
-
struct async_watcher_queue {
|
11
|
-
struct async_watcher **queue;
|
12
|
-
unsigned int length;
|
13
|
-
unsigned int count;
|
14
|
-
unsigned int push_idx;
|
15
|
-
unsigned int shift_idx;
|
16
|
-
};
|
17
|
-
|
18
|
-
void async_watcher_queue_init(struct async_watcher_queue *queue) {
|
19
|
-
queue->length = 1;
|
20
|
-
queue->count = 0;
|
21
|
-
queue->queue = malloc(sizeof(struct async_watcher *) * queue->length);
|
22
|
-
queue->push_idx = 0;
|
23
|
-
queue->shift_idx = 0;
|
24
|
-
}
|
25
|
-
|
26
|
-
void async_watcher_queue_free(struct async_watcher_queue *queue) {
|
27
|
-
free(queue->queue);
|
28
|
-
}
|
29
|
-
|
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) {
|
43
|
-
if (queue->count == 0) {
|
44
|
-
queue->push_idx = 0;
|
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
|
-
}
|
55
|
-
}
|
56
|
-
queue->count++;
|
57
|
-
queue->queue[queue->push_idx++] = watcher;
|
58
|
-
}
|
59
|
-
|
60
|
-
struct async_watcher *async_watcher_queue_shift(struct async_watcher_queue *queue) {
|
61
|
-
if (queue->count == 0) return 0;
|
62
|
-
|
63
|
-
queue->count--;
|
64
|
-
|
65
|
-
return queue->queue[queue->shift_idx++];
|
66
|
-
}
|
67
|
-
|
68
|
-
void async_watcher_queue_remove_at_idx(struct async_watcher_queue *queue, unsigned int remove_idx) {
|
69
|
-
queue->count--;
|
70
|
-
queue->push_idx--;
|
71
|
-
if (remove_idx < queue->push_idx)
|
72
|
-
memmove(
|
73
|
-
queue->queue + remove_idx,
|
74
|
-
queue->queue + remove_idx + 1,
|
75
|
-
(queue->push_idx - remove_idx) * sizeof(struct async_watcher *)
|
76
|
-
);
|
77
|
-
}
|
78
|
-
|
79
|
-
void async_watcher_queue_remove_by_fiber(struct async_watcher_queue *queue, VALUE fiber) {
|
80
|
-
if (queue->count == 0) return;
|
81
|
-
|
82
|
-
for (unsigned idx = queue->shift_idx; idx < queue->push_idx; idx++) {
|
83
|
-
if (queue->queue[idx]->fiber == fiber) {
|
84
|
-
async_watcher_queue_remove_at_idx(queue, idx);
|
85
|
-
return;
|
86
|
-
}
|
87
|
-
}
|
88
|
-
}
|
89
|
-
|
90
|
-
typedef struct queue {
|
91
|
-
ring_buffer values;
|
92
|
-
struct async_watcher_queue shift_queue;
|
93
|
-
} LibevQueue_t;
|
94
|
-
|
95
|
-
VALUE cLibevQueue = Qnil;
|
96
|
-
|
97
|
-
static void LibevQueue_mark(void *ptr) {
|
98
|
-
LibevQueue_t *queue = ptr;
|
99
|
-
ring_buffer_mark(&queue->values);
|
100
|
-
}
|
101
|
-
|
102
|
-
static void LibevQueue_free(void *ptr) {
|
103
|
-
LibevQueue_t *queue = ptr;
|
104
|
-
ring_buffer_free(&queue->values);
|
105
|
-
async_watcher_queue_free(&queue->shift_queue);
|
106
|
-
xfree(ptr);
|
107
|
-
}
|
108
|
-
|
109
|
-
static size_t LibevQueue_size(const void *ptr) {
|
110
|
-
return sizeof(LibevQueue_t);
|
111
|
-
}
|
112
|
-
|
113
|
-
static const rb_data_type_t LibevQueue_type = {
|
114
|
-
"Queue",
|
115
|
-
{LibevQueue_mark, LibevQueue_free, LibevQueue_size,},
|
116
|
-
0, 0, 0
|
117
|
-
};
|
118
|
-
|
119
|
-
static VALUE LibevQueue_allocate(VALUE klass) {
|
120
|
-
LibevQueue_t *queue;
|
121
|
-
|
122
|
-
queue = ALLOC(LibevQueue_t);
|
123
|
-
return TypedData_Wrap_Struct(klass, &LibevQueue_type, queue);
|
124
|
-
}
|
125
|
-
|
126
|
-
#define GetQueue(obj, queue) \
|
127
|
-
TypedData_Get_Struct((obj), LibevQueue_t, &LibevQueue_type, (queue))
|
128
|
-
|
129
|
-
static VALUE LibevQueue_initialize(VALUE self) {
|
130
|
-
LibevQueue_t *queue;
|
131
|
-
GetQueue(self, queue);
|
132
|
-
|
133
|
-
ring_buffer_init(&queue->values);
|
134
|
-
async_watcher_queue_init(&queue->shift_queue);
|
135
|
-
|
136
|
-
return self;
|
137
|
-
}
|
138
|
-
|
139
|
-
VALUE LibevQueue_push(VALUE self, VALUE value) {
|
140
|
-
LibevQueue_t *queue;
|
141
|
-
GetQueue(self, queue);
|
142
|
-
if (queue->shift_queue.count > 0) {
|
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);
|
157
|
-
if (watcher) {
|
158
|
-
ev_async_send(watcher->ev_loop, &watcher->async);
|
159
|
-
}
|
160
|
-
}
|
161
|
-
ring_buffer_unshift(&queue->values, value);
|
162
|
-
return self;
|
163
|
-
}
|
164
|
-
|
165
|
-
struct ev_loop *LibevAgent_ev_loop(VALUE self);
|
166
|
-
|
167
|
-
void async_watcher_queue_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
|
168
|
-
struct async_watcher *watcher = (struct async_watcher *)ev_async;
|
169
|
-
Fiber_make_runnable(watcher->fiber, Qnil);
|
170
|
-
}
|
171
|
-
|
172
|
-
VALUE libev_agent_await(VALUE self);
|
173
|
-
|
174
|
-
VALUE LibevQueue_shift(VALUE self) {
|
175
|
-
LibevQueue_t *queue;
|
176
|
-
GetQueue(self, queue);
|
177
|
-
|
178
|
-
if (queue->values.count == 0) {
|
179
|
-
struct async_watcher watcher;
|
180
|
-
VALUE agent = rb_ivar_get(rb_thread_current(), ID_ivar_agent);
|
181
|
-
VALUE switchpoint_result = Qnil;
|
182
|
-
|
183
|
-
watcher.ev_loop = LibevAgent_ev_loop(agent);
|
184
|
-
watcher.fiber = rb_fiber_current();
|
185
|
-
async_watcher_queue_push(&queue->shift_queue, &watcher);
|
186
|
-
ev_async_init(&watcher.async, async_watcher_queue_callback);
|
187
|
-
ev_async_start(watcher.ev_loop, &watcher.async);
|
188
|
-
|
189
|
-
switchpoint_result = libev_agent_await(agent);
|
190
|
-
ev_async_stop(watcher.ev_loop, &watcher.async);
|
191
|
-
|
192
|
-
if (RTEST(rb_obj_is_kind_of(switchpoint_result, rb_eException))) {
|
193
|
-
async_watcher_queue_remove_by_fiber(&queue->shift_queue, watcher.fiber);
|
194
|
-
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
195
|
-
}
|
196
|
-
RB_GC_GUARD(watcher.fiber);
|
197
|
-
RB_GC_GUARD(agent);
|
198
|
-
RB_GC_GUARD(switchpoint_result);
|
199
|
-
}
|
200
|
-
|
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;
|
232
|
-
}
|
233
|
-
|
234
|
-
VALUE LibevQueue_shift_each(VALUE self) {
|
235
|
-
LibevQueue_t *queue;
|
236
|
-
GetQueue(self, queue);
|
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);
|
247
|
-
}
|
248
|
-
|
249
|
-
VALUE LibevQueue_empty_p(VALUE self) {
|
250
|
-
LibevQueue_t *queue;
|
251
|
-
GetQueue(self, queue);
|
252
|
-
|
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
|
-
);
|
267
|
-
}
|
268
|
-
|
269
|
-
void Init_LibevQueue() {
|
270
|
-
cLibevQueue = rb_define_class_under(mPolyphony, "LibevQueue", rb_cData);
|
271
|
-
rb_define_alloc_func(cLibevQueue, LibevQueue_allocate);
|
272
|
-
|
273
|
-
rb_define_method(cLibevQueue, "initialize", LibevQueue_initialize, 0);
|
274
|
-
rb_define_method(cLibevQueue, "push", LibevQueue_push, 1);
|
275
|
-
rb_define_method(cLibevQueue, "<<", LibevQueue_push, 1);
|
276
|
-
rb_define_method(cLibevQueue, "unshift", LibevQueue_unshift, 1);
|
277
|
-
|
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);
|
282
|
-
|
283
|
-
rb_define_method(cLibevQueue, "shift_each", LibevQueue_shift_each, 0);
|
284
|
-
rb_define_method(cLibevQueue, "shift_all", LibevQueue_shift_all, 0);
|
285
|
-
rb_define_method(cLibevQueue, "empty?", LibevQueue_empty_p, 0);
|
286
|
-
}
|
287
|
-
|
288
|
-
|