polyphony 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 +7 -0
- data/.gitbook.yaml +4 -0
- data/.github/workflows/test.yml +29 -0
- data/.gitignore +59 -0
- data/.rubocop.yml +175 -0
- data/CHANGELOG.md +393 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +141 -0
- data/LICENSE +21 -0
- data/README.md +51 -0
- data/Rakefile +26 -0
- data/TODO.md +201 -0
- data/bin/polyphony-debug +87 -0
- data/docs/_config.yml +64 -0
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/title.html +1 -0
- data/docs/_sass/custom/custom.scss +10 -0
- data/docs/_sass/overrides.scss +0 -0
- data/docs/_user-guide/all-about-timers.md +126 -0
- data/docs/_user-guide/index.md +9 -0
- data/docs/_user-guide/web-server.md +136 -0
- data/docs/api-reference/exception.md +27 -0
- data/docs/api-reference/fiber.md +425 -0
- data/docs/api-reference/index.md +9 -0
- data/docs/api-reference/io.md +36 -0
- data/docs/api-reference/object.md +99 -0
- data/docs/api-reference/polyphony-baseexception.md +33 -0
- data/docs/api-reference/polyphony-cancel.md +26 -0
- data/docs/api-reference/polyphony-moveon.md +24 -0
- data/docs/api-reference/polyphony-net.md +20 -0
- data/docs/api-reference/polyphony-process.md +28 -0
- data/docs/api-reference/polyphony-resourcepool.md +59 -0
- data/docs/api-reference/polyphony-restart.md +18 -0
- data/docs/api-reference/polyphony-terminate.md +18 -0
- data/docs/api-reference/polyphony-threadpool.md +67 -0
- data/docs/api-reference/polyphony-throttler.md +77 -0
- data/docs/api-reference/polyphony.md +36 -0
- data/docs/api-reference/thread.md +88 -0
- data/docs/assets/img/echo-fibers.svg +1 -0
- data/docs/assets/img/sleeping-fiber.svg +1 -0
- data/docs/faq.md +195 -0
- data/docs/favicon.ico +0 -0
- data/docs/getting-started/index.md +10 -0
- data/docs/getting-started/installing.md +34 -0
- data/docs/getting-started/overview.md +486 -0
- data/docs/getting-started/tutorial.md +359 -0
- data/docs/index.md +94 -0
- data/docs/main-concepts/concurrency.md +151 -0
- data/docs/main-concepts/design-principles.md +161 -0
- data/docs/main-concepts/exception-handling.md +291 -0
- data/docs/main-concepts/extending.md +89 -0
- data/docs/main-concepts/fiber-scheduling.md +197 -0
- data/docs/main-concepts/index.md +9 -0
- data/docs/polyphony-logo.png +0 -0
- data/examples/adapters/concurrent-ruby.rb +9 -0
- data/examples/adapters/pg_client.rb +36 -0
- data/examples/adapters/pg_notify.rb +35 -0
- data/examples/adapters/pg_pool.rb +43 -0
- data/examples/adapters/pg_transaction.rb +31 -0
- data/examples/adapters/redis_blpop.rb +12 -0
- data/examples/adapters/redis_channels.rb +122 -0
- data/examples/adapters/redis_client.rb +19 -0
- data/examples/adapters/redis_pubsub.rb +26 -0
- data/examples/adapters/redis_pubsub_perf.rb +68 -0
- data/examples/core/01-spinning-up-fibers.rb +18 -0
- data/examples/core/02-awaiting-fibers.rb +20 -0
- data/examples/core/03-interrupting.rb +39 -0
- data/examples/core/04-handling-signals.rb +19 -0
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-at_exit.rb +29 -0
- data/examples/core/xx-caller.rb +12 -0
- data/examples/core/xx-channels.rb +45 -0
- data/examples/core/xx-daemon.rb +14 -0
- data/examples/core/xx-deadlock.rb +8 -0
- data/examples/core/xx-deferring-an-operation.rb +14 -0
- data/examples/core/xx-erlang-style-genserver.rb +81 -0
- data/examples/core/xx-exception-backtrace.rb +40 -0
- data/examples/core/xx-fork-cleanup.rb +22 -0
- data/examples/core/xx-fork-spin.rb +42 -0
- data/examples/core/xx-fork-terminate.rb +27 -0
- data/examples/core/xx-forking.rb +24 -0
- data/examples/core/xx-move_on.rb +23 -0
- data/examples/core/xx-pingpong.rb +18 -0
- data/examples/core/xx-queue-async.rb +120 -0
- data/examples/core/xx-readpartial.rb +18 -0
- data/examples/core/xx-recurrent-timer.rb +12 -0
- data/examples/core/xx-resource_delegate.rb +31 -0
- data/examples/core/xx-signals.rb +16 -0
- data/examples/core/xx-sleep-forever.rb +9 -0
- data/examples/core/xx-sleeping.rb +25 -0
- data/examples/core/xx-snooze-starve.rb +16 -0
- data/examples/core/xx-spin-fork.rb +49 -0
- data/examples/core/xx-spin_error_backtrace.rb +33 -0
- data/examples/core/xx-state-machine.rb +51 -0
- data/examples/core/xx-stop.rb +20 -0
- data/examples/core/xx-supervise-process.rb +30 -0
- data/examples/core/xx-supervisors.rb +21 -0
- data/examples/core/xx-thread-selector-sleep.rb +51 -0
- data/examples/core/xx-thread-selector-snooze.rb +46 -0
- data/examples/core/xx-thread-sleep.rb +17 -0
- data/examples/core/xx-thread-snooze.rb +34 -0
- data/examples/core/xx-thread_pool.rb +17 -0
- data/examples/core/xx-throttling.rb +18 -0
- data/examples/core/xx-timeout.rb +10 -0
- data/examples/core/xx-timer-gc.rb +17 -0
- data/examples/core/xx-trace.rb +79 -0
- data/examples/core/xx-using-a-mutex.rb +21 -0
- data/examples/core/xx-worker-thread.rb +30 -0
- data/examples/io/tunnel.rb +48 -0
- data/examples/io/xx-backticks.rb +11 -0
- data/examples/io/xx-echo_client.rb +25 -0
- data/examples/io/xx-echo_client_from_stdin.rb +21 -0
- data/examples/io/xx-echo_pipe.rb +16 -0
- data/examples/io/xx-echo_server.rb +17 -0
- data/examples/io/xx-echo_server_with_timeout.rb +34 -0
- data/examples/io/xx-echo_stdin.rb +14 -0
- data/examples/io/xx-happy-eyeballs.rb +36 -0
- data/examples/io/xx-httparty.rb +38 -0
- data/examples/io/xx-irb.rb +17 -0
- data/examples/io/xx-net-http.rb +15 -0
- data/examples/io/xx-open.rb +16 -0
- data/examples/io/xx-switch.rb +15 -0
- data/examples/io/xx-system.rb +11 -0
- data/examples/io/xx-tcpserver.rb +15 -0
- data/examples/io/xx-tcpsocket.rb +18 -0
- data/examples/io/xx-zip.rb +19 -0
- data/examples/performance/fiber_transfer.rb +47 -0
- data/examples/performance/fs_read.rb +38 -0
- data/examples/performance/mem-usage.rb +56 -0
- data/examples/performance/messaging.rb +29 -0
- data/examples/performance/multi_snooze.rb +33 -0
- data/examples/performance/snooze.rb +39 -0
- data/examples/performance/snooze_raw.rb +39 -0
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +74 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +45 -0
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
- data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_multi.rb +36 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_threaded.rb +29 -0
- data/examples/performance/thread_pool_perf.rb +63 -0
- data/examples/performance/xx-array.rb +11 -0
- data/examples/performance/xx-fiber-switch.rb +9 -0
- data/examples/performance/xx-snooze.rb +15 -0
- data/examples/xx-spin.rb +32 -0
- data/ext/libev/Changes +548 -0
- data/ext/libev/LICENSE +37 -0
- data/ext/libev/README +59 -0
- data/ext/libev/README.embed +3 -0
- data/ext/libev/ev.c +5279 -0
- data/ext/libev/ev.h +856 -0
- data/ext/libev/ev_epoll.c +296 -0
- data/ext/libev/ev_kqueue.c +224 -0
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +156 -0
- data/ext/libev/ev_port.c +192 -0
- data/ext/libev/ev_select.c +316 -0
- data/ext/libev/ev_vars.h +215 -0
- data/ext/libev/ev_win32.c +162 -0
- data/ext/libev/ev_wrap.h +216 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/ext/polyphony/extconf.rb +20 -0
- data/ext/polyphony/fiber.c +109 -0
- data/ext/polyphony/libev.c +2 -0
- data/ext/polyphony/libev.h +9 -0
- data/ext/polyphony/libev_agent.c +882 -0
- data/ext/polyphony/polyphony.c +71 -0
- data/ext/polyphony/polyphony.h +97 -0
- data/ext/polyphony/polyphony_ext.c +21 -0
- data/ext/polyphony/queue.c +168 -0
- data/ext/polyphony/ring_buffer.c +96 -0
- data/ext/polyphony/ring_buffer.h +28 -0
- data/ext/polyphony/thread.c +208 -0
- data/ext/polyphony/tracing.c +11 -0
- data/lib/polyphony.rb +136 -0
- data/lib/polyphony/adapters/fs.rb +19 -0
- data/lib/polyphony/adapters/irb.rb +52 -0
- data/lib/polyphony/adapters/postgres.rb +110 -0
- data/lib/polyphony/adapters/process.rb +33 -0
- data/lib/polyphony/adapters/redis.rb +67 -0
- data/lib/polyphony/adapters/trace.rb +138 -0
- data/lib/polyphony/core/channel.rb +46 -0
- data/lib/polyphony/core/exceptions.rb +36 -0
- data/lib/polyphony/core/global_api.rb +124 -0
- data/lib/polyphony/core/resource_pool.rb +117 -0
- data/lib/polyphony/core/sync.rb +21 -0
- data/lib/polyphony/core/thread_pool.rb +64 -0
- data/lib/polyphony/core/throttler.rb +41 -0
- data/lib/polyphony/event.rb +17 -0
- data/lib/polyphony/extensions/core.rb +174 -0
- data/lib/polyphony/extensions/fiber.rb +379 -0
- data/lib/polyphony/extensions/io.rb +221 -0
- data/lib/polyphony/extensions/openssl.rb +81 -0
- data/lib/polyphony/extensions/socket.rb +150 -0
- data/lib/polyphony/extensions/thread.rb +108 -0
- data/lib/polyphony/net.rb +77 -0
- data/lib/polyphony/version.rb +5 -0
- data/polyphony.gemspec +40 -0
- data/test/coverage.rb +54 -0
- data/test/eg.rb +27 -0
- data/test/helper.rb +56 -0
- data/test/q.rb +24 -0
- data/test/run.rb +5 -0
- data/test/stress.rb +25 -0
- data/test/test_agent.rb +130 -0
- data/test/test_event.rb +59 -0
- data/test/test_ext.rb +196 -0
- data/test/test_fiber.rb +988 -0
- data/test/test_global_api.rb +352 -0
- data/test/test_io.rb +249 -0
- data/test/test_kernel.rb +57 -0
- data/test/test_process_supervision.rb +46 -0
- data/test/test_queue.rb +112 -0
- data/test/test_resource_pool.rb +138 -0
- data/test/test_signal.rb +100 -0
- data/test/test_socket.rb +34 -0
- data/test/test_supervise.rb +103 -0
- data/test/test_thread.rb +170 -0
- data/test/test_thread_pool.rb +101 -0
- data/test/test_throttler.rb +50 -0
- data/test/test_trace.rb +68 -0
- metadata +482 -0
@@ -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 */
|
@@ -0,0 +1,208 @@
|
|
1
|
+
#include "polyphony.h"
|
2
|
+
|
3
|
+
ID ID_deactivate_all_watchers_post_fork;
|
4
|
+
ID ID_ivar_agent;
|
5
|
+
ID ID_ivar_join_wait_queue;
|
6
|
+
ID ID_ivar_main_fiber;
|
7
|
+
ID ID_ivar_result;
|
8
|
+
ID ID_ivar_terminated;
|
9
|
+
ID ID_run_queue;
|
10
|
+
ID ID_runnable_next;
|
11
|
+
ID ID_stop;
|
12
|
+
|
13
|
+
static VALUE Thread_setup_fiber_scheduling(VALUE self) {
|
14
|
+
VALUE queue = rb_funcall(cQueue, ID_new, 0);
|
15
|
+
|
16
|
+
rb_ivar_set(self, ID_ivar_main_fiber, rb_fiber_current());
|
17
|
+
rb_ivar_set(self, ID_run_queue, queue);
|
18
|
+
|
19
|
+
return self;
|
20
|
+
}
|
21
|
+
|
22
|
+
int Thread_fiber_ref_count(VALUE self) {
|
23
|
+
VALUE agent = rb_ivar_get(self, ID_ivar_agent);
|
24
|
+
return NUM2INT(LibevAgent_ref_count(agent));
|
25
|
+
}
|
26
|
+
|
27
|
+
inline void Thread_fiber_reset_ref_count(VALUE self) {
|
28
|
+
VALUE agent = rb_ivar_get(self, ID_ivar_agent);
|
29
|
+
LibevAgent_reset_ref_count(agent);
|
30
|
+
}
|
31
|
+
|
32
|
+
static VALUE SYM_scheduled_fibers;
|
33
|
+
static VALUE SYM_pending_watchers;
|
34
|
+
|
35
|
+
static VALUE Thread_fiber_scheduling_stats(VALUE self) {
|
36
|
+
VALUE agent = rb_ivar_get(self,ID_ivar_agent);
|
37
|
+
VALUE stats = rb_hash_new();
|
38
|
+
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
39
|
+
long pending_count;
|
40
|
+
|
41
|
+
long scheduled_count = RARRAY_LEN(queue);
|
42
|
+
rb_hash_aset(stats, SYM_scheduled_fibers, INT2NUM(scheduled_count));
|
43
|
+
|
44
|
+
pending_count = LibevAgent_pending_count(agent);
|
45
|
+
rb_hash_aset(stats, SYM_pending_watchers, INT2NUM(pending_count));
|
46
|
+
|
47
|
+
return stats;
|
48
|
+
}
|
49
|
+
|
50
|
+
VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
51
|
+
VALUE queue;
|
52
|
+
|
53
|
+
if (rb_fiber_alive_p(fiber) != Qtrue) return self;
|
54
|
+
|
55
|
+
FIBER_TRACE(3, SYM_fiber_schedule, fiber, value);
|
56
|
+
// if fiber is already scheduled, just set the scheduled value, then return
|
57
|
+
rb_ivar_set(fiber, ID_runnable_value, value);
|
58
|
+
if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
|
59
|
+
return self;
|
60
|
+
}
|
61
|
+
|
62
|
+
queue = rb_ivar_get(self, ID_run_queue);
|
63
|
+
Queue_push(queue, fiber);
|
64
|
+
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
65
|
+
|
66
|
+
if (rb_thread_current() != self) {
|
67
|
+
// if the fiber scheduling is done across threads, we need to make sure the
|
68
|
+
// target thread is woken up in case it is in the middle of running its
|
69
|
+
// event selector. Otherwise it's gonna be stuck waiting for an event to
|
70
|
+
// happen, not knowing that it there's already a fiber ready to run in its
|
71
|
+
// run queue.
|
72
|
+
VALUE agent = rb_ivar_get(self,ID_ivar_agent);
|
73
|
+
LibevAgent_break(agent);
|
74
|
+
}
|
75
|
+
return self;
|
76
|
+
}
|
77
|
+
|
78
|
+
VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value) {
|
79
|
+
VALUE queue;
|
80
|
+
|
81
|
+
if (rb_fiber_alive_p(fiber) != Qtrue) return self;
|
82
|
+
|
83
|
+
FIBER_TRACE(3, SYM_fiber_schedule, fiber, value);
|
84
|
+
rb_ivar_set(fiber, ID_runnable_value, value);
|
85
|
+
|
86
|
+
queue = rb_ivar_get(self, ID_run_queue);
|
87
|
+
|
88
|
+
// if fiber is already scheduled, remove it from the run queue
|
89
|
+
if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
|
90
|
+
Queue_delete(queue, fiber);
|
91
|
+
} else {
|
92
|
+
rb_ivar_set(fiber, ID_runnable, Qtrue);
|
93
|
+
}
|
94
|
+
|
95
|
+
// the fiber is given priority by putting it at the front of the run queue
|
96
|
+
Queue_unshift(queue, fiber);
|
97
|
+
|
98
|
+
if (rb_thread_current() != self) {
|
99
|
+
// if the fiber scheduling is done across threads, we need to make sure the
|
100
|
+
// target thread is woken up in case it is in the middle of running its
|
101
|
+
// event loop. Otherwise it's gonna be stuck waiting for an event to
|
102
|
+
// happen, not knowing that it there's already a fiber ready to run in its
|
103
|
+
// run queue.
|
104
|
+
VALUE agent = rb_ivar_get(self, ID_ivar_agent);
|
105
|
+
LibevAgent_break(agent);
|
106
|
+
}
|
107
|
+
return self;
|
108
|
+
}
|
109
|
+
|
110
|
+
VALUE Thread_switch_fiber(VALUE self) {
|
111
|
+
VALUE current_fiber = rb_fiber_current();
|
112
|
+
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
113
|
+
VALUE next_fiber;
|
114
|
+
VALUE value;
|
115
|
+
VALUE agent = rb_ivar_get(self, ID_ivar_agent);
|
116
|
+
int ref_count;
|
117
|
+
int agent_was_polled = 0;1;
|
118
|
+
|
119
|
+
if (__tracing_enabled__) {
|
120
|
+
if (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse) {
|
121
|
+
rb_funcall(rb_cObject, ID_fiber_trace, 2, SYM_fiber_switchpoint, current_fiber);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
ref_count = LibevAgent_ref_count(agent);
|
126
|
+
while (1) {
|
127
|
+
next_fiber = Queue_shift_no_wait(queue);
|
128
|
+
if (next_fiber != Qnil) {
|
129
|
+
if (agent_was_polled == 0 && ref_count > 0) {
|
130
|
+
// this mechanism prevents event starvation in case the run queue never
|
131
|
+
// empties
|
132
|
+
LibevAgent_poll(agent, Qtrue, current_fiber, queue);
|
133
|
+
}
|
134
|
+
break;
|
135
|
+
}
|
136
|
+
if (ref_count == 0) break;
|
137
|
+
|
138
|
+
LibevAgent_poll(agent, Qnil, current_fiber, queue);
|
139
|
+
agent_was_polled = 1;
|
140
|
+
}
|
141
|
+
|
142
|
+
if (next_fiber == Qnil) return Qnil;
|
143
|
+
|
144
|
+
// run next fiber
|
145
|
+
value = rb_ivar_get(next_fiber, ID_runnable_value);
|
146
|
+
FIBER_TRACE(3, SYM_fiber_run, next_fiber, value);
|
147
|
+
|
148
|
+
rb_ivar_set(next_fiber, ID_runnable, Qnil);
|
149
|
+
RB_GC_GUARD(next_fiber);
|
150
|
+
RB_GC_GUARD(value);
|
151
|
+
return (next_fiber == current_fiber) ?
|
152
|
+
value : rb_funcall(next_fiber, ID_transfer, 1, value);
|
153
|
+
}
|
154
|
+
|
155
|
+
VALUE Thread_run_queue_trace(VALUE self) {
|
156
|
+
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
157
|
+
Queue_trace(queue);
|
158
|
+
return self;
|
159
|
+
}
|
160
|
+
|
161
|
+
VALUE Thread_reset_fiber_scheduling(VALUE self) {
|
162
|
+
VALUE queue = rb_ivar_get(self, ID_run_queue);
|
163
|
+
Queue_clear(queue);
|
164
|
+
Thread_fiber_reset_ref_count(self);
|
165
|
+
return self;
|
166
|
+
}
|
167
|
+
|
168
|
+
VALUE Thread_fiber_break_out_of_ev_loop(VALUE self, VALUE fiber, VALUE resume_obj) {
|
169
|
+
VALUE agent = rb_ivar_get(self, ID_ivar_agent);
|
170
|
+
if (fiber != Qnil) {
|
171
|
+
Thread_schedule_fiber_with_priority(self, fiber, resume_obj);
|
172
|
+
}
|
173
|
+
|
174
|
+
if (LibevAgent_break(agent) == Qnil) {
|
175
|
+
// we're not inside the ev_loop, so we just do a switchpoint
|
176
|
+
Thread_switch_fiber(self);
|
177
|
+
}
|
178
|
+
|
179
|
+
return self;
|
180
|
+
}
|
181
|
+
|
182
|
+
void Init_Thread() {
|
183
|
+
rb_define_method(rb_cThread, "setup_fiber_scheduling", Thread_setup_fiber_scheduling, 0);
|
184
|
+
rb_define_method(rb_cThread, "reset_fiber_scheduling", Thread_reset_fiber_scheduling, 0);
|
185
|
+
rb_define_method(rb_cThread, "fiber_scheduling_stats", Thread_fiber_scheduling_stats, 0);
|
186
|
+
rb_define_method(rb_cThread, "break_out_of_ev_loop", Thread_fiber_break_out_of_ev_loop, 2);
|
187
|
+
|
188
|
+
rb_define_method(rb_cThread, "schedule_fiber", Thread_schedule_fiber, 2);
|
189
|
+
rb_define_method(rb_cThread, "schedule_fiber_with_priority",
|
190
|
+
Thread_schedule_fiber_with_priority, 2);
|
191
|
+
rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
|
192
|
+
rb_define_method(rb_cThread, "run_queue_trace", Thread_run_queue_trace, 0);
|
193
|
+
|
194
|
+
ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
|
195
|
+
ID_ivar_agent = rb_intern("@agent");
|
196
|
+
ID_ivar_join_wait_queue = rb_intern("@join_wait_queue");
|
197
|
+
ID_ivar_main_fiber = rb_intern("@main_fiber");
|
198
|
+
ID_ivar_result = rb_intern("@result");
|
199
|
+
ID_ivar_terminated = rb_intern("@terminated");
|
200
|
+
ID_run_queue = rb_intern("run_queue");
|
201
|
+
ID_runnable_next = rb_intern("runnable_next");
|
202
|
+
ID_stop = rb_intern("stop");
|
203
|
+
|
204
|
+
SYM_scheduled_fibers = ID2SYM(rb_intern("scheduled_fibers"));
|
205
|
+
SYM_pending_watchers = ID2SYM(rb_intern("pending_watchers"));
|
206
|
+
rb_global_variable(&SYM_scheduled_fibers);
|
207
|
+
rb_global_variable(&SYM_pending_watchers);
|
208
|
+
}
|
data/lib/polyphony.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fiber'
|
4
|
+
require_relative './polyphony_ext'
|
5
|
+
|
6
|
+
module Polyphony
|
7
|
+
# replace core Queue class with our own
|
8
|
+
verbose = $VERBOSE
|
9
|
+
$VERBOSE = nil
|
10
|
+
Object.const_set(:Queue, Polyphony::Queue)
|
11
|
+
$VERBOSE = verbose
|
12
|
+
end
|
13
|
+
|
14
|
+
require_relative './polyphony/extensions/core'
|
15
|
+
require_relative './polyphony/extensions/thread'
|
16
|
+
require_relative './polyphony/extensions/fiber'
|
17
|
+
require_relative './polyphony/extensions/io'
|
18
|
+
|
19
|
+
Thread.current.setup_fiber_scheduling
|
20
|
+
Thread.current.agent = Polyphony::LibevAgent.new
|
21
|
+
|
22
|
+
require_relative './polyphony/core/global_api'
|
23
|
+
require_relative './polyphony/core/resource_pool'
|
24
|
+
require_relative './polyphony/net'
|
25
|
+
require_relative './polyphony/adapters/process'
|
26
|
+
require_relative './polyphony/event'
|
27
|
+
|
28
|
+
# Main Polyphony API
|
29
|
+
module Polyphony
|
30
|
+
class << self
|
31
|
+
def wait_for_signal(sig)
|
32
|
+
raise "should be reimplemented"
|
33
|
+
|
34
|
+
fiber = Fiber.current
|
35
|
+
# Polyphony.ref
|
36
|
+
old_trap = trap(sig) do
|
37
|
+
# Polyphony.unref
|
38
|
+
fiber.schedule(sig)
|
39
|
+
trap(sig, old_trap)
|
40
|
+
end
|
41
|
+
suspend
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def fork(&block)
|
46
|
+
Kernel.fork do
|
47
|
+
# # Since the fiber doing the fork will become the main fiber of the
|
48
|
+
# # forked process, we leave it behind by transferring to a new fiber
|
49
|
+
# # created in the context of the forked process, which rescues *all*
|
50
|
+
# # exceptions, including Interrupt and SystemExit.
|
51
|
+
spin_forked_block(&block).transfer
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def spin_forked_block(&block)
|
56
|
+
Fiber.new do
|
57
|
+
run_forked_block(&block)
|
58
|
+
rescue SystemExit
|
59
|
+
# fall through to ensure
|
60
|
+
rescue Exception => e
|
61
|
+
e.full_message
|
62
|
+
exit!
|
63
|
+
ensure
|
64
|
+
exit_forked_process
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def run_forked_block(&block)
|
69
|
+
# A race condition can arise if a TERM or INT signal is received before
|
70
|
+
# the forked process has finished initializing. To prevent this we restore
|
71
|
+
# the default signal handlers, and then reinstall the custom Polyphony
|
72
|
+
# handlers just before running the given block.
|
73
|
+
trap('SIGTERM', 'DEFAULT')
|
74
|
+
trap('SIGINT', 'DEFAULT')
|
75
|
+
|
76
|
+
Thread.current.setup
|
77
|
+
Fiber.current.setup_main_fiber
|
78
|
+
Thread.current.agent.post_fork
|
79
|
+
|
80
|
+
install_terminating_signal_handlers
|
81
|
+
|
82
|
+
block.()
|
83
|
+
end
|
84
|
+
|
85
|
+
def exit_forked_process
|
86
|
+
terminate_threads
|
87
|
+
Fiber.current.shutdown_all_children
|
88
|
+
|
89
|
+
# Since fork could be called from any fiber, we explicitly call exit here.
|
90
|
+
# Otherwise, the fiber might want to pass execution to another fiber that
|
91
|
+
# previously transferred execution to the forking fiber, but doesn't exist
|
92
|
+
# anymore...
|
93
|
+
exit
|
94
|
+
end
|
95
|
+
|
96
|
+
def watch_process(cmd = nil, &block)
|
97
|
+
Polyphony::Process.watch(cmd, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
def install_terminating_signal_handlers
|
101
|
+
trap('SIGTERM', SystemExit)
|
102
|
+
orig_trap('SIGINT') do
|
103
|
+
orig_trap('SIGINT') { exit! }
|
104
|
+
Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, Interrupt.new)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def terminate_threads
|
109
|
+
threads = Thread.list - [Thread.current]
|
110
|
+
return if threads.empty?
|
111
|
+
|
112
|
+
threads.each(&:kill)
|
113
|
+
threads.each(&:join)
|
114
|
+
end
|
115
|
+
|
116
|
+
attr_accessor :original_pid
|
117
|
+
|
118
|
+
def install_at_exit_handler
|
119
|
+
@original_pid = ::Process.pid
|
120
|
+
|
121
|
+
# This at_exit handler is needed only when the original process exits. Due to
|
122
|
+
# the behaviour of fibers on fork (and especially on exit from forked
|
123
|
+
# processes,) we use a separate mechanism to terminate fibers in forked
|
124
|
+
# processes (see Polyphony.fork).
|
125
|
+
at_exit do
|
126
|
+
next unless @original_pid == ::Process.pid
|
127
|
+
|
128
|
+
Polyphony.terminate_threads
|
129
|
+
Fiber.current.shutdown_all_children
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
Polyphony.install_terminating_signal_handlers
|
136
|
+
Polyphony.install_at_exit_handler
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
require_relative './core/thread_pool'
|
6
|
+
|
7
|
+
::File.singleton_class.instance_eval do
|
8
|
+
alias_method :orig_stat, :stat
|
9
|
+
def stat(path)
|
10
|
+
ThreadPool.process { orig_stat(path) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
::IO.singleton_class.instance_eval do
|
15
|
+
alias_method :orig_read, :read
|
16
|
+
def read(path)
|
17
|
+
ThreadPool.process { orig_read(path) }
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'polyphony'
|
4
|
+
|
5
|
+
if Object.constants.include?(:Reline)
|
6
|
+
class Reline::ANSI
|
7
|
+
def self.select(read_ios = [], write_ios = [], error_ios = [], timeout = nil)
|
8
|
+
p [:select, read_ios]
|
9
|
+
raise if read_ios.size > 1
|
10
|
+
raise if write_ios.size > 0
|
11
|
+
raise if error_ios.size > 0
|
12
|
+
|
13
|
+
fiber = Fiber.current
|
14
|
+
timer = spin do
|
15
|
+
sleep timeout
|
16
|
+
fiber.cancel
|
17
|
+
end
|
18
|
+
read_ios.each do |io|
|
19
|
+
Thread.current.agent.wait_io(io, false)
|
20
|
+
return [io]
|
21
|
+
end
|
22
|
+
rescue Polyphony::Cancel
|
23
|
+
return nil
|
24
|
+
ensure
|
25
|
+
timer.stop
|
26
|
+
end
|
27
|
+
end
|
28
|
+
else
|
29
|
+
# readline blocks the current thread, so we offload it to the blocking-ops
|
30
|
+
# thread pool. That way, the reactor loop can keep running while waiting for
|
31
|
+
# readline to return
|
32
|
+
module ::Readline
|
33
|
+
alias_method :orig_readline, :readline
|
34
|
+
|
35
|
+
Workers = Polyphony::ThreadPool.new
|
36
|
+
|
37
|
+
def readline(*args)
|
38
|
+
p :readline
|
39
|
+
# caller.each do |l|
|
40
|
+
# STDOUT.orig_puts l
|
41
|
+
# end
|
42
|
+
Workers.process { orig_readline(*args) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# RubyLex patches
|
47
|
+
class ::RubyLex
|
48
|
+
class TerminateLineInput2 < RuntimeError
|
49
|
+
end
|
50
|
+
const_set(:TerminateLineInput, TerminateLineInput2)
|
51
|
+
end
|
52
|
+
end
|