polyphony 0.45.4 → 0.45.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +3 -16
- data/examples/performance/multi_snooze.rb +0 -1
- data/ext/polyphony/backend.h +1 -1
- data/ext/polyphony/fiber.c +30 -13
- data/ext/polyphony/libev_backend.c +5 -4
- data/ext/polyphony/polyphony.c +4 -4
- data/ext/polyphony/polyphony.h +14 -2
- data/ext/polyphony/polyphony_ext.c +2 -0
- data/ext/polyphony/runqueue.c +102 -0
- data/ext/polyphony/runqueue_ring_buffer.c +85 -0
- data/ext/polyphony/runqueue_ring_buffer.h +31 -0
- data/ext/polyphony/thread.c +34 -82
- data/lib/polyphony/core/global_api.rb +2 -2
- data/lib/polyphony/core/sync.rb +7 -5
- data/lib/polyphony/extensions/fiber.rb +5 -5
- data/lib/polyphony/version.rb +1 -1
- data/test/test_global_api.rb +29 -0
- data/test/test_sync.rb +21 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe5d86b07fc6f29d01897a7790deac8f0544bb61dbd486fb8ea104dd315b0e80
|
4
|
+
data.tar.gz: 3e1086b9395d63835a09051c52b7f8496013f190149df35532333b6c27b74169
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6c7a78a46b9084f60ee838e82b7cde6f287eea2c500f779e5014103a400da98dc066adef58631033591a8de5278cc4f7b95ba8dcdac3ad22ecd68668a7d68e4
|
7
|
+
data.tar.gz: d32e7def9aa63ecb5c53b46020079e3384809ff786478e0b87fce1b141abb85d3c69d1f490b1d0a3bcbfaf972a3779939ac42bec512b12132d3b46b159754de5
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 0.45.5
|
2
|
+
|
3
|
+
* Fix compilation error (#43)
|
4
|
+
* Add support for resetting move_on_after, cancel_after timeouts
|
5
|
+
* Optimize anti-event starvation polling
|
6
|
+
* Implement optimized runqueue for better performance
|
7
|
+
* Schedule parent with priority on uncaught exception
|
8
|
+
* Fix race condition in `Mutex#synchronize` (#41)
|
9
|
+
|
1
10
|
## 0.45.4
|
2
11
|
|
3
12
|
* Improve signal trapping mechanism
|
data/Gemfile.lock
CHANGED
data/TODO.md
CHANGED
@@ -1,11 +1,6 @@
|
|
1
|
-
|
2
|
-
io_uring: some work has been done on an io_uring based scheduler here:
|
3
|
-
https://github.com/dsh0416/evt
|
4
|
-
|
5
|
-
This can serve as a starting point for doing stuff with io_uring
|
6
|
-
)
|
1
|
+
- io_uring
|
7
2
|
|
8
|
-
0.
|
3
|
+
0.46
|
9
4
|
|
10
5
|
- Adapter for io/console (what does `IO#raw` do?)
|
11
6
|
- Adapter for Pry and IRB (Which fixes #5 and #6)
|
@@ -14,7 +9,7 @@
|
|
14
9
|
- Fix backtrace for `Timeout.timeout` API (see timeout example).
|
15
10
|
- Check why worker-thread example doesn't work.
|
16
11
|
|
17
|
-
0.
|
12
|
+
0.47
|
18
13
|
|
19
14
|
- Debugging
|
20
15
|
- Eat your own dogfood: need a good tool to check what's going on when some
|
@@ -128,8 +123,6 @@
|
|
128
123
|
- discuss using `snooze` for ensuring responsiveness when executing CPU-bound work
|
129
124
|
|
130
125
|
|
131
|
-
## 0.47
|
132
|
-
|
133
126
|
### Some more API work, more docs
|
134
127
|
|
135
128
|
- sintra app with database access (postgresql)
|
@@ -141,14 +134,10 @@
|
|
141
134
|
- proceed from there
|
142
135
|
|
143
136
|
|
144
|
-
## 0.48
|
145
|
-
|
146
137
|
### Sinatra / Sidekiq
|
147
138
|
|
148
139
|
- Pull out redis/postgres code, put into new `polyphony-xxx` gems
|
149
140
|
|
150
|
-
## 0.49
|
151
|
-
|
152
141
|
### Testing && Docs
|
153
142
|
|
154
143
|
- More tests
|
@@ -159,8 +148,6 @@
|
|
159
148
|
- `IO.foreach`
|
160
149
|
- `Process.waitpid`
|
161
150
|
|
162
|
-
## 0.50 DNS
|
163
|
-
|
164
151
|
### DNS client
|
165
152
|
|
166
153
|
```ruby
|
data/ext/polyphony/backend.h
CHANGED
@@ -18,7 +18,7 @@
|
|
18
18
|
// VALUE LibevBackend_write(int argc, VALUE *argv, VALUE self);
|
19
19
|
|
20
20
|
typedef VALUE (* backend_pending_count_t)(VALUE self);
|
21
|
-
typedef VALUE (*backend_poll_t)(VALUE self, VALUE nowait, VALUE current_fiber, VALUE
|
21
|
+
typedef VALUE (*backend_poll_t)(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue);
|
22
22
|
typedef VALUE (* backend_ref_t)(VALUE self);
|
23
23
|
typedef int (* backend_ref_count_t)(VALUE self);
|
24
24
|
typedef void (* backend_reset_ref_count_t)(VALUE self);
|
data/ext/polyphony/fiber.c
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
ID ID_fiber_trace;
|
4
4
|
ID ID_ivar_auto_watcher;
|
5
5
|
ID ID_ivar_mailbox;
|
6
|
-
ID ID_ivar_result;
|
7
6
|
ID ID_ivar_waiting_fibers;
|
8
7
|
|
9
8
|
VALUE SYM_dead;
|
@@ -39,31 +38,49 @@ inline VALUE Fiber_auto_watcher(VALUE self) {
|
|
39
38
|
return watcher;
|
40
39
|
}
|
41
40
|
|
41
|
+
void Fiber_make_runnable(VALUE fiber, VALUE value) {
|
42
|
+
VALUE thread = rb_ivar_get(fiber, ID_ivar_thread);
|
43
|
+
if (thread == Qnil) {
|
44
|
+
// rb_raise(rb_eRuntimeError, "No thread set for fiber");
|
45
|
+
rb_warn("No thread set for fiber");
|
46
|
+
return;
|
47
|
+
}
|
48
|
+
|
49
|
+
Thread_schedule_fiber(thread, fiber, value);
|
50
|
+
}
|
51
|
+
|
52
|
+
void Fiber_make_runnable_with_priority(VALUE fiber, VALUE value) {
|
53
|
+
VALUE thread = rb_ivar_get(fiber, ID_ivar_thread);
|
54
|
+
if (thread == Qnil) {
|
55
|
+
// rb_raise(rb_eRuntimeError, "No thread set for fiber");
|
56
|
+
rb_warn("No thread set for fiber");
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
|
60
|
+
Thread_schedule_fiber_with_priority(thread, fiber, value);
|
61
|
+
}
|
62
|
+
|
42
63
|
static VALUE Fiber_schedule(int argc, VALUE *argv, VALUE self) {
|
43
64
|
VALUE value = (argc == 0) ? Qnil : argv[0];
|
44
65
|
Fiber_make_runnable(self, value);
|
45
66
|
return self;
|
46
67
|
}
|
47
68
|
|
69
|
+
static VALUE Fiber_schedule_with_priority(int argc, VALUE *argv, VALUE self) {
|
70
|
+
VALUE value = (argc == 0) ? Qnil : argv[0];
|
71
|
+
Fiber_make_runnable_with_priority(self, value);
|
72
|
+
return self;
|
73
|
+
}
|
74
|
+
|
48
75
|
static VALUE Fiber_state(VALUE self) {
|
49
76
|
if (!rb_fiber_alive_p(self) || (rb_ivar_get(self, ID_ivar_running) == Qfalse))
|
50
77
|
return SYM_dead;
|
51
78
|
if (rb_fiber_current() == self) return SYM_running;
|
52
|
-
if (rb_ivar_get(self,
|
79
|
+
if (rb_ivar_get(self, ID_ivar_runnable) != Qnil) return SYM_runnable;
|
53
80
|
|
54
81
|
return SYM_waiting;
|
55
82
|
}
|
56
83
|
|
57
|
-
void Fiber_make_runnable(VALUE fiber, VALUE value) {
|
58
|
-
VALUE thread = rb_ivar_get(fiber, ID_ivar_thread);
|
59
|
-
if (thread != Qnil) {
|
60
|
-
Thread_schedule_fiber(thread, fiber, value);
|
61
|
-
}
|
62
|
-
else {
|
63
|
-
rb_warn("No thread set for fiber (fiber, value, caller):");
|
64
|
-
}
|
65
|
-
}
|
66
|
-
|
67
84
|
VALUE Fiber_await(VALUE self) {
|
68
85
|
VALUE result;
|
69
86
|
|
@@ -119,6 +136,7 @@ void Init_Fiber() {
|
|
119
136
|
VALUE cFiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
|
120
137
|
rb_define_method(cFiber, "safe_transfer", Fiber_safe_transfer, -1);
|
121
138
|
rb_define_method(cFiber, "schedule", Fiber_schedule, -1);
|
139
|
+
rb_define_method(cFiber, "schedule_with_priority", Fiber_schedule_with_priority, -1);
|
122
140
|
rb_define_method(cFiber, "state", Fiber_state, 0);
|
123
141
|
rb_define_method(cFiber, "auto_watcher", Fiber_auto_watcher, 0);
|
124
142
|
|
@@ -143,7 +161,6 @@ void Init_Fiber() {
|
|
143
161
|
ID_fiber_trace = rb_intern("__fiber_trace__");
|
144
162
|
ID_ivar_auto_watcher = rb_intern("@auto_watcher");
|
145
163
|
ID_ivar_mailbox = rb_intern("@mailbox");
|
146
|
-
ID_ivar_result = rb_intern("@result");
|
147
164
|
ID_ivar_waiting_fibers = rb_intern("@waiting_fibers");
|
148
165
|
|
149
166
|
SYM_fiber_create = ID2SYM(rb_intern("fiber_create"));
|
@@ -126,16 +126,17 @@ VALUE LibevBackend_pending_count(VALUE self) {
|
|
126
126
|
return INT2NUM(count);
|
127
127
|
}
|
128
128
|
|
129
|
-
VALUE LibevBackend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE
|
129
|
+
VALUE LibevBackend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue) {
|
130
130
|
int is_nowait = nowait == Qtrue;
|
131
131
|
LibevBackend_t *backend;
|
132
132
|
GetLibevBackend(self, backend);
|
133
133
|
|
134
134
|
if (is_nowait) {
|
135
|
-
long runnable_count = Queue_len(queue);
|
136
135
|
backend->run_no_wait_count++;
|
137
|
-
if (backend->run_no_wait_count <
|
138
|
-
|
136
|
+
if (backend->run_no_wait_count < 10) return self;
|
137
|
+
|
138
|
+
long runnable_count = Runqueue_len(runqueue);
|
139
|
+
if (backend->run_no_wait_count < runnable_count) return self;
|
139
140
|
}
|
140
141
|
|
141
142
|
backend->run_no_wait_count = 0;
|
data/ext/polyphony/polyphony.c
CHANGED
@@ -9,10 +9,10 @@ ID ID_each;
|
|
9
9
|
ID ID_inspect;
|
10
10
|
ID ID_invoke;
|
11
11
|
ID ID_new;
|
12
|
+
ID ID_ivar_result;
|
13
|
+
ID ID_ivar_runnable;
|
12
14
|
ID ID_ivar_running;
|
13
15
|
ID ID_ivar_thread;
|
14
|
-
ID ID_runnable;
|
15
|
-
ID ID_runnable_value;
|
16
16
|
ID ID_size;
|
17
17
|
ID ID_signal;
|
18
18
|
ID ID_switch_fiber;
|
@@ -61,11 +61,11 @@ void Init_Polyphony() {
|
|
61
61
|
ID_each = rb_intern("each");
|
62
62
|
ID_inspect = rb_intern("inspect");
|
63
63
|
ID_invoke = rb_intern("invoke");
|
64
|
+
ID_ivar_result = rb_intern("@result");
|
65
|
+
ID_ivar_runnable = rb_intern("runnable");
|
64
66
|
ID_ivar_running = rb_intern("@running");
|
65
67
|
ID_ivar_thread = rb_intern("@thread");
|
66
68
|
ID_new = rb_intern("new");
|
67
|
-
ID_runnable = rb_intern("runnable");
|
68
|
-
ID_runnable_value = rb_intern("runnable_value");
|
69
69
|
ID_signal = rb_intern("signal");
|
70
70
|
ID_size = rb_intern("size");
|
71
71
|
ID_switch_fiber = rb_intern("switch_fiber");
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
#include "ruby/io.h"
|
6
6
|
#include "libev.h"
|
7
7
|
#include "backend.h"
|
8
|
+
#include "runqueue_ring_buffer.h"
|
8
9
|
|
9
10
|
// debugging
|
10
11
|
#define OBJ_ID(obj) (NUM2LONG(rb_funcall(obj, rb_intern("object_id"), 0)))
|
@@ -30,6 +31,7 @@ extern backend_interface_t backend_interface;
|
|
30
31
|
extern VALUE mPolyphony;
|
31
32
|
extern VALUE cQueue;
|
32
33
|
extern VALUE cEvent;
|
34
|
+
extern VALUE cRunqueue;
|
33
35
|
|
34
36
|
extern ID ID_call;
|
35
37
|
extern ID ID_caller;
|
@@ -39,12 +41,12 @@ extern ID ID_fiber_trace;
|
|
39
41
|
extern ID ID_inspect;
|
40
42
|
extern ID ID_invoke;
|
41
43
|
extern ID ID_ivar_backend;
|
44
|
+
extern ID ID_ivar_result;
|
45
|
+
extern ID ID_ivar_runnable;
|
42
46
|
extern ID ID_ivar_running;
|
43
47
|
extern ID ID_ivar_thread;
|
44
48
|
extern ID ID_new;
|
45
49
|
extern ID ID_raise;
|
46
|
-
extern ID ID_runnable;
|
47
|
-
extern ID ID_runnable_value;
|
48
50
|
extern ID ID_signal;
|
49
51
|
extern ID ID_size;
|
50
52
|
extern ID ID_switch_fiber;
|
@@ -79,7 +81,17 @@ VALUE Queue_delete(VALUE self, VALUE value);
|
|
79
81
|
long Queue_len(VALUE self);
|
80
82
|
void Queue_trace(VALUE self);
|
81
83
|
|
84
|
+
|
85
|
+
void Runqueue_push(VALUE self, VALUE fiber, VALUE value, int reschedule);
|
86
|
+
void Runqueue_unshift(VALUE self, VALUE fiber, VALUE value, int reschedule);
|
87
|
+
runqueue_entry Runqueue_shift(VALUE self);
|
88
|
+
void Runqueue_delete(VALUE self, VALUE fiber);
|
89
|
+
void Runqueue_clear(VALUE self);
|
90
|
+
long Runqueue_len(VALUE self);
|
91
|
+
int Runqueue_empty_p(VALUE self);
|
92
|
+
|
82
93
|
VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
|
94
|
+
VALUE Thread_schedule_fiber_with_priority(VALUE thread, VALUE fiber, VALUE value);
|
83
95
|
VALUE Thread_switch_fiber(VALUE thread);
|
84
96
|
|
85
97
|
#endif /* POLYPHONY_H */
|
@@ -5,6 +5,7 @@ void Init_Polyphony();
|
|
5
5
|
void Init_LibevBackend();
|
6
6
|
void Init_Queue();
|
7
7
|
void Init_Event();
|
8
|
+
void Init_Runqueue();
|
8
9
|
void Init_Thread();
|
9
10
|
void Init_Tracing();
|
10
11
|
|
@@ -16,6 +17,7 @@ void Init_polyphony_ext() {
|
|
16
17
|
Init_LibevBackend();
|
17
18
|
Init_Queue();
|
18
19
|
Init_Event();
|
20
|
+
Init_Runqueue();
|
19
21
|
Init_Fiber();
|
20
22
|
Init_Thread();
|
21
23
|
Init_Tracing();
|
@@ -0,0 +1,102 @@
|
|
1
|
+
#include "polyphony.h"
|
2
|
+
#include "runqueue_ring_buffer.h"
|
3
|
+
|
4
|
+
typedef struct queue {
|
5
|
+
runqueue_ring_buffer entries;
|
6
|
+
} Runqueue_t;
|
7
|
+
|
8
|
+
VALUE cRunqueue = Qnil;
|
9
|
+
|
10
|
+
static void Runqueue_mark(void *ptr) {
|
11
|
+
Runqueue_t *runqueue = ptr;
|
12
|
+
runqueue_ring_buffer_mark(&runqueue->entries);
|
13
|
+
}
|
14
|
+
|
15
|
+
static void Runqueue_free(void *ptr) {
|
16
|
+
Runqueue_t *runqueue = ptr;
|
17
|
+
runqueue_ring_buffer_free(&runqueue->entries);
|
18
|
+
xfree(ptr);
|
19
|
+
}
|
20
|
+
|
21
|
+
static size_t Runqueue_size(const void *ptr) {
|
22
|
+
return sizeof(Runqueue_t);
|
23
|
+
}
|
24
|
+
|
25
|
+
static const rb_data_type_t Runqueue_type = {
|
26
|
+
"Runqueue",
|
27
|
+
{Runqueue_mark, Runqueue_free, Runqueue_size,},
|
28
|
+
0, 0, 0
|
29
|
+
};
|
30
|
+
|
31
|
+
static VALUE Runqueue_allocate(VALUE klass) {
|
32
|
+
Runqueue_t *runqueue;
|
33
|
+
|
34
|
+
runqueue = ALLOC(Runqueue_t);
|
35
|
+
return TypedData_Wrap_Struct(klass, &Runqueue_type, runqueue);
|
36
|
+
}
|
37
|
+
|
38
|
+
#define GetRunqueue(obj, runqueue) \
|
39
|
+
TypedData_Get_Struct((obj), Runqueue_t, &Runqueue_type, (runqueue))
|
40
|
+
|
41
|
+
static VALUE Runqueue_initialize(VALUE self) {
|
42
|
+
Runqueue_t *runqueue;
|
43
|
+
GetRunqueue(self, runqueue);
|
44
|
+
|
45
|
+
runqueue_ring_buffer_init(&runqueue->entries);
|
46
|
+
|
47
|
+
return self;
|
48
|
+
}
|
49
|
+
|
50
|
+
void Runqueue_push(VALUE self, VALUE fiber, VALUE value, int reschedule) {
|
51
|
+
Runqueue_t *runqueue;
|
52
|
+
GetRunqueue(self, runqueue);
|
53
|
+
|
54
|
+
if (reschedule) runqueue_ring_buffer_delete(&runqueue->entries, fiber);
|
55
|
+
runqueue_ring_buffer_push(&runqueue->entries, fiber, value);
|
56
|
+
}
|
57
|
+
|
58
|
+
void Runqueue_unshift(VALUE self, VALUE fiber, VALUE value, int reschedule) {
|
59
|
+
Runqueue_t *runqueue;
|
60
|
+
GetRunqueue(self, runqueue);
|
61
|
+
if (reschedule) runqueue_ring_buffer_delete(&runqueue->entries, fiber);
|
62
|
+
runqueue_ring_buffer_unshift(&runqueue->entries, fiber, value);
|
63
|
+
}
|
64
|
+
|
65
|
+
runqueue_entry Runqueue_shift(VALUE self) {
|
66
|
+
Runqueue_t *runqueue;
|
67
|
+
GetRunqueue(self, runqueue);
|
68
|
+
return runqueue_ring_buffer_shift(&runqueue->entries);
|
69
|
+
}
|
70
|
+
|
71
|
+
void Runqueue_delete(VALUE self, VALUE fiber) {
|
72
|
+
Runqueue_t *runqueue;
|
73
|
+
GetRunqueue(self, runqueue);
|
74
|
+
runqueue_ring_buffer_delete(&runqueue->entries, fiber);
|
75
|
+
}
|
76
|
+
|
77
|
+
void Runqueue_clear(VALUE self) {
|
78
|
+
Runqueue_t *runqueue;
|
79
|
+
GetRunqueue(self, runqueue);
|
80
|
+
runqueue_ring_buffer_clear(&runqueue->entries);
|
81
|
+
}
|
82
|
+
|
83
|
+
long Runqueue_len(VALUE self) {
|
84
|
+
Runqueue_t *runqueue;
|
85
|
+
GetRunqueue(self, runqueue);
|
86
|
+
|
87
|
+
return runqueue->entries.count;
|
88
|
+
}
|
89
|
+
|
90
|
+
int Runqueue_empty_p(VALUE self) {
|
91
|
+
Runqueue_t *runqueue;
|
92
|
+
GetRunqueue(self, runqueue);
|
93
|
+
|
94
|
+
return (runqueue->entries.count == 0);
|
95
|
+
}
|
96
|
+
|
97
|
+
void Init_Runqueue() {
|
98
|
+
cRunqueue = rb_define_class_under(mPolyphony, "Runqueue", rb_cData);
|
99
|
+
rb_define_alloc_func(cRunqueue, Runqueue_allocate);
|
100
|
+
|
101
|
+
rb_define_method(cRunqueue, "initialize", Runqueue_initialize, 0);
|
102
|
+
}
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#include "polyphony.h"
|
2
|
+
#include "runqueue_ring_buffer.h"
|
3
|
+
|
4
|
+
void runqueue_ring_buffer_init(runqueue_ring_buffer *buffer) {
|
5
|
+
buffer->size = 1;
|
6
|
+
buffer->count = 0;
|
7
|
+
buffer->entries = malloc(buffer->size * sizeof(runqueue_entry));
|
8
|
+
buffer->head = 0;
|
9
|
+
buffer->tail = 0;
|
10
|
+
}
|
11
|
+
|
12
|
+
void runqueue_ring_buffer_free(runqueue_ring_buffer *buffer) {
|
13
|
+
free(buffer->entries);
|
14
|
+
}
|
15
|
+
|
16
|
+
int runqueue_ring_buffer_empty_p(runqueue_ring_buffer *buffer) {
|
17
|
+
return buffer->count == 0;
|
18
|
+
}
|
19
|
+
|
20
|
+
static runqueue_entry nil_runqueue_entry = {(Qnil), (Qnil)};
|
21
|
+
|
22
|
+
runqueue_entry runqueue_ring_buffer_shift(runqueue_ring_buffer *buffer) {
|
23
|
+
if (buffer->count == 0) return nil_runqueue_entry;
|
24
|
+
|
25
|
+
runqueue_entry value = buffer->entries[buffer->head];
|
26
|
+
buffer->head = (buffer->head + 1) % buffer->size;
|
27
|
+
buffer->count--;
|
28
|
+
return value;
|
29
|
+
}
|
30
|
+
|
31
|
+
void runqueue_ring_buffer_resize(runqueue_ring_buffer *buffer) {
|
32
|
+
unsigned int old_size = buffer->size;
|
33
|
+
buffer->size = old_size == 1 ? 4 : old_size * 2;
|
34
|
+
buffer->entries = realloc(buffer->entries, buffer->size * sizeof(runqueue_entry));
|
35
|
+
for (unsigned int idx = 0; idx < buffer->head && idx < buffer->tail; idx++)
|
36
|
+
buffer->entries[old_size + idx] = buffer->entries[idx];
|
37
|
+
buffer->tail = buffer->head + buffer->count;
|
38
|
+
}
|
39
|
+
|
40
|
+
void runqueue_ring_buffer_unshift(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value) {
|
41
|
+
if (buffer->count == buffer->size) runqueue_ring_buffer_resize(buffer);
|
42
|
+
|
43
|
+
buffer->head = (buffer->head - 1) % buffer->size;
|
44
|
+
buffer->entries[buffer->head].fiber = fiber;
|
45
|
+
buffer->entries[buffer->head].value = value;
|
46
|
+
buffer->count++;
|
47
|
+
}
|
48
|
+
|
49
|
+
void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value) {
|
50
|
+
if (buffer->count == buffer->size) runqueue_ring_buffer_resize(buffer);
|
51
|
+
|
52
|
+
buffer->entries[buffer->tail].fiber = fiber;
|
53
|
+
buffer->entries[buffer->tail].value = value;
|
54
|
+
buffer->tail = (buffer->tail + 1) % buffer->size;
|
55
|
+
buffer->count++;
|
56
|
+
}
|
57
|
+
|
58
|
+
void runqueue_ring_buffer_mark(runqueue_ring_buffer *buffer) {
|
59
|
+
for (unsigned int i = 0; i < buffer->count; i++) {
|
60
|
+
rb_gc_mark(buffer->entries[(buffer->head + i) % buffer->size].fiber);
|
61
|
+
rb_gc_mark(buffer->entries[(buffer->head + i) % buffer->size].value);
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
void runqueue_ring_buffer_delete_at(runqueue_ring_buffer *buffer, unsigned int idx) {
|
66
|
+
for (unsigned int idx2 = idx; idx2 != buffer->tail; idx2 = (idx2 + 1) % buffer->size) {
|
67
|
+
buffer->entries[idx2] = buffer->entries[(idx2 + 1) % buffer->size];
|
68
|
+
}
|
69
|
+
buffer->count--;
|
70
|
+
buffer->tail = (buffer->tail - 1) % buffer->size;
|
71
|
+
}
|
72
|
+
|
73
|
+
void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber) {
|
74
|
+
for (unsigned int i = 0; i < buffer->count; i++) {
|
75
|
+
unsigned int idx = (buffer->head + i) % buffer->size;
|
76
|
+
if (buffer->entries[idx].fiber == fiber) {
|
77
|
+
runqueue_ring_buffer_delete_at(buffer, idx);
|
78
|
+
return;
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
void runqueue_ring_buffer_clear(runqueue_ring_buffer *buffer) {
|
84
|
+
buffer->count = buffer->head = buffer->tail = 0;
|
85
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#ifndef RUNQUEUE_RING_BUFFER_H
|
2
|
+
#define RUNQUEUE_RING_BUFFER_H
|
3
|
+
|
4
|
+
#include "ruby.h"
|
5
|
+
|
6
|
+
typedef struct runqueue_entry {
|
7
|
+
VALUE fiber;
|
8
|
+
VALUE value;
|
9
|
+
} runqueue_entry;
|
10
|
+
|
11
|
+
typedef struct runqueue_ring_buffer {
|
12
|
+
runqueue_entry *entries;
|
13
|
+
unsigned int size;
|
14
|
+
unsigned int count;
|
15
|
+
unsigned int head;
|
16
|
+
unsigned int tail;
|
17
|
+
} runqueue_ring_buffer;
|
18
|
+
|
19
|
+
void runqueue_ring_buffer_init(runqueue_ring_buffer *buffer);
|
20
|
+
void runqueue_ring_buffer_free(runqueue_ring_buffer *buffer);
|
21
|
+
void runqueue_ring_buffer_mark(runqueue_ring_buffer *buffer);
|
22
|
+
int runqueue_ring_buffer_empty_p(runqueue_ring_buffer *buffer);
|
23
|
+
void runqueue_ring_buffer_clear(runqueue_ring_buffer *buffer);
|
24
|
+
|
25
|
+
runqueue_entry runqueue_ring_buffer_shift(runqueue_ring_buffer *buffer);
|
26
|
+
void runqueue_ring_buffer_unshift(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value);
|
27
|
+
void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value);
|
28
|
+
|
29
|
+
void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber);
|
30
|
+
|
31
|
+
#endif /* RUNQUEUE_RING_BUFFER_H */
|
data/ext/polyphony/thread.c
CHANGED
@@ -4,17 +4,15 @@ ID ID_deactivate_all_watchers_post_fork;
|
|
4
4
|
ID ID_ivar_backend;
|
5
5
|
ID ID_ivar_join_wait_queue;
|
6
6
|
ID ID_ivar_main_fiber;
|
7
|
-
ID ID_ivar_result;
|
8
7
|
ID ID_ivar_terminated;
|
9
|
-
ID
|
10
|
-
ID ID_runnable_next;
|
8
|
+
ID ID_ivar_runqueue;
|
11
9
|
ID ID_stop;
|
12
10
|
|
13
11
|
static VALUE Thread_setup_fiber_scheduling(VALUE self) {
|
14
|
-
VALUE
|
12
|
+
VALUE runqueue = rb_funcall(cRunqueue, ID_new, 0);
|
15
13
|
|
16
14
|
rb_ivar_set(self, ID_ivar_main_fiber, rb_fiber_current());
|
17
|
-
rb_ivar_set(self,
|
15
|
+
rb_ivar_set(self, ID_ivar_runqueue, runqueue);
|
18
16
|
|
19
17
|
return self;
|
20
18
|
}
|
@@ -35,10 +33,10 @@ static VALUE SYM_pending_watchers;
|
|
35
33
|
static VALUE Thread_fiber_scheduling_stats(VALUE self) {
|
36
34
|
VALUE backend = rb_ivar_get(self,ID_ivar_backend);
|
37
35
|
VALUE stats = rb_hash_new();
|
38
|
-
VALUE
|
36
|
+
VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
|
39
37
|
long pending_count;
|
40
38
|
|
41
|
-
long scheduled_count =
|
39
|
+
long scheduled_count = Runqueue_len(runqueue);
|
42
40
|
rb_hash_aset(stats, SYM_scheduled_fibers, INT2NUM(scheduled_count));
|
43
41
|
|
44
42
|
pending_count = __BACKEND__.pending_count(backend);
|
@@ -47,30 +45,18 @@ static VALUE Thread_fiber_scheduling_stats(VALUE self) {
|
|
47
45
|
return stats;
|
48
46
|
}
|
49
47
|
|
50
|
-
|
51
|
-
VALUE
|
52
|
-
|
53
|
-
if (rb_fiber_alive_p(fiber) != Qtrue) return self;
|
54
|
-
|
55
|
-
int already_runnable = rb_ivar_get(fiber, ID_runnable) != Qnil;
|
48
|
+
void schedule_fiber(VALUE self, VALUE fiber, VALUE value, int prioritize) {
|
49
|
+
VALUE runqueue;
|
50
|
+
int already_runnable;
|
56
51
|
|
57
|
-
if (
|
58
|
-
|
59
|
-
|
60
|
-
// If the fiber is already runnable and the runnable value is an exception,
|
61
|
-
// we don't update the value, in order to prevent a race condition where
|
62
|
-
// exceptions will be lost (see issue #33)
|
63
|
-
if (TEST_EXCEPTION(current_runnable_value)) return self;
|
64
|
-
}
|
52
|
+
if (rb_fiber_alive_p(fiber) != Qtrue) return;
|
53
|
+
already_runnable = rb_ivar_get(fiber, ID_ivar_runnable) != Qnil;
|
65
54
|
|
66
|
-
rb_ivar_set(fiber, ID_runnable_value, value);
|
67
55
|
COND_TRACE(3, SYM_fiber_schedule, fiber, value);
|
68
|
-
|
56
|
+
runqueue = rb_ivar_get(self, ID_ivar_runqueue);
|
57
|
+
(prioritize ? Runqueue_unshift : Runqueue_push)(runqueue, fiber, value, already_runnable);
|
69
58
|
if (!already_runnable) {
|
70
|
-
|
71
|
-
Queue_push(queue, fiber);
|
72
|
-
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
73
|
-
|
59
|
+
rb_ivar_set(fiber, ID_ivar_runnable, Qtrue);
|
74
60
|
if (rb_thread_current() != self) {
|
75
61
|
// If the fiber scheduling is done across threads, we need to make sure the
|
76
62
|
// target thread is woken up in case it is in the middle of running its
|
@@ -81,46 +67,22 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
|
81
67
|
__BACKEND__.wakeup(backend);
|
82
68
|
}
|
83
69
|
}
|
70
|
+
}
|
71
|
+
|
72
|
+
VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
73
|
+
schedule_fiber(self, fiber, value, 0);
|
84
74
|
return self;
|
85
75
|
}
|
86
76
|
|
87
77
|
VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value) {
|
88
|
-
|
89
|
-
|
90
|
-
if (rb_fiber_alive_p(fiber) != Qtrue) return self;
|
91
|
-
|
92
|
-
COND_TRACE(3, SYM_fiber_schedule, fiber, value);
|
93
|
-
rb_ivar_set(fiber, ID_runnable_value, value);
|
94
|
-
|
95
|
-
queue = rb_ivar_get(self, ID_run_queue);
|
96
|
-
|
97
|
-
// if fiber is already scheduled, remove it from the run queue
|
98
|
-
if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
|
99
|
-
Queue_delete(queue, fiber);
|
100
|
-
} else {
|
101
|
-
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
102
|
-
}
|
103
|
-
|
104
|
-
// the fiber is given priority by putting it at the front of the run queue
|
105
|
-
Queue_unshift(queue, fiber);
|
106
|
-
|
107
|
-
if (rb_thread_current() != self) {
|
108
|
-
// if the fiber scheduling is done across threads, we need to make sure the
|
109
|
-
// target thread is woken up in case it is in the middle of running its
|
110
|
-
// event loop. Otherwise it's gonna be stuck waiting for an event to
|
111
|
-
// happen, not knowing that it there's already a fiber ready to run in its
|
112
|
-
// run queue.
|
113
|
-
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
114
|
-
__BACKEND__.wakeup(backend);
|
115
|
-
}
|
78
|
+
schedule_fiber(self, fiber, value, 1);
|
116
79
|
return self;
|
117
80
|
}
|
118
81
|
|
119
82
|
VALUE Thread_switch_fiber(VALUE self) {
|
120
83
|
VALUE current_fiber = rb_fiber_current();
|
121
|
-
VALUE
|
122
|
-
|
123
|
-
VALUE value;
|
84
|
+
VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
|
85
|
+
runqueue_entry next;
|
124
86
|
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
125
87
|
int ref_count;
|
126
88
|
int backend_was_polled = 0;
|
@@ -130,42 +92,35 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
130
92
|
|
131
93
|
ref_count = __BACKEND__.ref_count(backend);
|
132
94
|
while (1) {
|
133
|
-
|
134
|
-
if (
|
95
|
+
next = Runqueue_shift(runqueue);
|
96
|
+
if (next.fiber != Qnil) {
|
135
97
|
if (backend_was_polled == 0 && ref_count > 0) {
|
136
98
|
// this prevents event starvation in case the run queue never empties
|
137
|
-
__BACKEND__.poll(backend, Qtrue, current_fiber,
|
99
|
+
__BACKEND__.poll(backend, Qtrue, current_fiber, runqueue);
|
138
100
|
}
|
139
101
|
break;
|
140
102
|
}
|
141
103
|
if (ref_count == 0) break;
|
142
104
|
|
143
|
-
__BACKEND__.poll(backend, Qnil, current_fiber,
|
105
|
+
__BACKEND__.poll(backend, Qnil, current_fiber, runqueue);
|
144
106
|
backend_was_polled = 1;
|
145
107
|
}
|
146
108
|
|
147
|
-
if (
|
109
|
+
if (next.fiber == Qnil) return Qnil;
|
148
110
|
|
149
111
|
// run next fiber
|
150
|
-
|
151
|
-
COND_TRACE(3, SYM_fiber_run, next_fiber, value);
|
152
|
-
|
153
|
-
rb_ivar_set(next_fiber, ID_runnable, Qnil);
|
154
|
-
RB_GC_GUARD(next_fiber);
|
155
|
-
RB_GC_GUARD(value);
|
156
|
-
return (next_fiber == current_fiber) ?
|
157
|
-
value : rb_funcall(next_fiber, ID_transfer, 1, value);
|
158
|
-
}
|
112
|
+
COND_TRACE(3, SYM_fiber_run, next.fiber, next.value);
|
159
113
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
return
|
114
|
+
rb_ivar_set(next.fiber, ID_ivar_runnable, Qnil);
|
115
|
+
RB_GC_GUARD(next.fiber);
|
116
|
+
RB_GC_GUARD(next.value);
|
117
|
+
return (next.fiber == current_fiber) ?
|
118
|
+
next.value : rb_funcall(next.fiber, ID_transfer, 1, next.value);
|
164
119
|
}
|
165
120
|
|
166
121
|
VALUE Thread_reset_fiber_scheduling(VALUE self) {
|
167
|
-
VALUE queue = rb_ivar_get(self,
|
168
|
-
|
122
|
+
VALUE queue = rb_ivar_get(self, ID_ivar_runqueue);
|
123
|
+
Runqueue_clear(queue);
|
169
124
|
Thread_fiber_reset_ref_count(self);
|
170
125
|
return self;
|
171
126
|
}
|
@@ -199,7 +154,6 @@ void Init_Thread() {
|
|
199
154
|
rb_define_method(rb_cThread, "schedule_fiber_with_priority",
|
200
155
|
Thread_schedule_fiber_with_priority, 2);
|
201
156
|
rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
|
202
|
-
rb_define_method(rb_cThread, "run_queue_trace", Thread_run_queue_trace, 0);
|
203
157
|
|
204
158
|
rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
|
205
159
|
|
@@ -207,10 +161,8 @@ void Init_Thread() {
|
|
207
161
|
ID_ivar_backend = rb_intern("@backend");
|
208
162
|
ID_ivar_join_wait_queue = rb_intern("@join_wait_queue");
|
209
163
|
ID_ivar_main_fiber = rb_intern("@main_fiber");
|
210
|
-
|
164
|
+
ID_ivar_runqueue = rb_intern("@runqueue");
|
211
165
|
ID_ivar_terminated = rb_intern("@terminated");
|
212
|
-
ID_run_queue = rb_intern("run_queue");
|
213
|
-
ID_runnable_next = rb_intern("runnable_next");
|
214
166
|
ID_stop = rb_intern("stop");
|
215
167
|
|
216
168
|
SYM_scheduled_fibers = ID2SYM(rb_intern("scheduled_fibers"));
|
@@ -32,7 +32,7 @@ module Polyphony
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def cancel_after_wrap_block(canceller, &block)
|
35
|
-
block.call
|
35
|
+
block.call(canceller)
|
36
36
|
ensure
|
37
37
|
canceller.stop
|
38
38
|
end
|
@@ -81,7 +81,7 @@ module Polyphony
|
|
81
81
|
sleep interval
|
82
82
|
fiber.schedule Polyphony::MoveOn.new(with_value)
|
83
83
|
end
|
84
|
-
block.call
|
84
|
+
block.call(canceller)
|
85
85
|
rescue Polyphony::MoveOn => e
|
86
86
|
e.value
|
87
87
|
ensure
|
data/lib/polyphony/core/sync.rb
CHANGED
@@ -16,11 +16,13 @@ module Polyphony
|
|
16
16
|
|
17
17
|
def synchronize_not_holding
|
18
18
|
@token = @store.shift
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
begin
|
20
|
+
@holding_fiber = Fiber.current
|
21
|
+
yield
|
22
|
+
ensure
|
23
|
+
@holding_fiber = nil
|
24
|
+
@store << @token if @token
|
25
|
+
end
|
24
26
|
end
|
25
27
|
|
26
28
|
def conditional_release
|
@@ -187,9 +187,9 @@ module Polyphony
|
|
187
187
|
(@children ||= {}).keys
|
188
188
|
end
|
189
189
|
|
190
|
-
def spin(tag = nil, orig_caller = Kernel.caller,
|
190
|
+
def spin(tag = nil, orig_caller = Kernel.caller, &block)
|
191
191
|
f = Fiber.new { |v| f.run(v) }
|
192
|
-
f.prepare(tag, block, orig_caller, self
|
192
|
+
f.prepare(tag, block, orig_caller, self)
|
193
193
|
(@children ||= {})[f] = true
|
194
194
|
f
|
195
195
|
end
|
@@ -227,14 +227,14 @@ module Polyphony
|
|
227
227
|
|
228
228
|
# Fiber life cycle methods
|
229
229
|
module FiberLifeCycle
|
230
|
-
def prepare(tag, block, caller, parent
|
230
|
+
def prepare(tag, block, caller, parent)
|
231
231
|
@thread = Thread.current
|
232
232
|
@tag = tag
|
233
233
|
@parent = parent
|
234
234
|
@caller = caller
|
235
235
|
@block = block
|
236
236
|
__fiber_trace__(:fiber_create, self)
|
237
|
-
schedule
|
237
|
+
schedule
|
238
238
|
end
|
239
239
|
|
240
240
|
def run(first_value)
|
@@ -308,7 +308,7 @@ module Polyphony
|
|
308
308
|
@waiting_fibers&.each_key { |f| f.schedule(result) }
|
309
309
|
|
310
310
|
# propagate unaught exception to parent
|
311
|
-
@parent&.
|
311
|
+
@parent&.schedule_with_priority(result) if uncaught_exception && !@waiting_fibers
|
312
312
|
end
|
313
313
|
|
314
314
|
def when_done(&block)
|
data/lib/polyphony/version.rb
CHANGED
data/test/test_global_api.rb
CHANGED
@@ -122,6 +122,21 @@ class MoveOnAfterTest < MiniTest::Test
|
|
122
122
|
assert_equal :bar, v
|
123
123
|
end
|
124
124
|
|
125
|
+
def test_move_on_after_with_reset
|
126
|
+
t0 = Time.now
|
127
|
+
v = move_on_after(0.01, with_value: :moved_on) do |timeout|
|
128
|
+
sleep 0.007
|
129
|
+
timeout.reset
|
130
|
+
sleep 0.007
|
131
|
+
nil
|
132
|
+
end
|
133
|
+
t1 = Time.now
|
134
|
+
|
135
|
+
assert_nil v
|
136
|
+
assert t1 - t0 >= 0.014
|
137
|
+
assert t1 - t0 < 0.02
|
138
|
+
end
|
139
|
+
|
125
140
|
def test_move_on_after_without_block
|
126
141
|
t0 = Time.now
|
127
142
|
f = move_on_after(0.01, with_value: 'foo')
|
@@ -160,6 +175,20 @@ class CancelAfterTest < MiniTest::Test
|
|
160
175
|
assert t1 - t0 < 0.1
|
161
176
|
end
|
162
177
|
|
178
|
+
def test_cancel_after_with_reset
|
179
|
+
t0 = Time.now
|
180
|
+
cancel_after(0.01) do |f|
|
181
|
+
assert_kind_of Fiber, f
|
182
|
+
assert_equal Fiber.current, f.parent
|
183
|
+
sleep 0.007
|
184
|
+
f.reset
|
185
|
+
sleep 0.007
|
186
|
+
end
|
187
|
+
t1 = Time.now
|
188
|
+
assert t1 - t0 >= 0.014
|
189
|
+
assert t1 - t0 < 0.02
|
190
|
+
end
|
191
|
+
|
163
192
|
class CustomException < Exception
|
164
193
|
end
|
165
194
|
|
data/test/test_sync.rb
CHANGED
@@ -20,6 +20,27 @@ class MutexTest < MiniTest::Test
|
|
20
20
|
assert_equal ['>> 1', '<< 1', '>> 2', '<< 2', '>> 3', '<< 3'], buf
|
21
21
|
end
|
22
22
|
|
23
|
+
def test_mutex_race_condition
|
24
|
+
lock = Polyphony::Mutex.new
|
25
|
+
buf = []
|
26
|
+
f1 = spin do
|
27
|
+
lock.synchronize { buf << 1; snooze; lock.synchronize { buf << 1.1 }; snooze }
|
28
|
+
end
|
29
|
+
f2 = spin do
|
30
|
+
lock.synchronize { buf << 2 }
|
31
|
+
end
|
32
|
+
f3 = spin do
|
33
|
+
lock.synchronize { buf << 3 }
|
34
|
+
end
|
35
|
+
|
36
|
+
snooze
|
37
|
+
f2.terminate
|
38
|
+
|
39
|
+
f3.await
|
40
|
+
|
41
|
+
assert_equal [1, 1.1, 3], buf
|
42
|
+
end
|
43
|
+
|
23
44
|
def test_condition_variable
|
24
45
|
buf = []
|
25
46
|
lock1 = Polyphony::Mutex.new
|
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.45.
|
4
|
+
version: 0.45.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-
|
11
|
+
date: 2020-10-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -428,6 +428,9 @@ files:
|
|
428
428
|
- ext/polyphony/queue.c
|
429
429
|
- ext/polyphony/ring_buffer.c
|
430
430
|
- ext/polyphony/ring_buffer.h
|
431
|
+
- ext/polyphony/runqueue.c
|
432
|
+
- ext/polyphony/runqueue_ring_buffer.c
|
433
|
+
- ext/polyphony/runqueue_ring_buffer.h
|
431
434
|
- ext/polyphony/thread.c
|
432
435
|
- ext/polyphony/tracing.c
|
433
436
|
- lib/polyphony.rb
|
@@ -507,7 +510,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
507
510
|
- !ruby/object:Gem::Version
|
508
511
|
version: '0'
|
509
512
|
requirements: []
|
510
|
-
rubygems_version: 3.1.
|
513
|
+
rubygems_version: 3.1.4
|
511
514
|
signing_key:
|
512
515
|
specification_version: 4
|
513
516
|
summary: Fine grained concurrency for Ruby
|