polyphony 0.27 → 0.28

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/CHANGELOG.md +13 -0
  4. data/Gemfile +12 -1
  5. data/Gemfile.lock +83 -5
  6. data/Rakefile +4 -0
  7. data/TODO.md +11 -20
  8. data/docs/_config.yml +15 -0
  9. data/docs/_includes/nav.html +47 -0
  10. data/docs/_sass/custom/custom.scss +5 -0
  11. data/docs/_sass/overrides.scss +45 -0
  12. data/docs/assets/img/echo-fibers.svg +1 -0
  13. data/docs/assets/img/sleeping-fiber.svg +1 -0
  14. data/docs/faq.md +182 -0
  15. data/docs/getting-started/installing.md +10 -2
  16. data/docs/getting-started/tutorial.md +333 -26
  17. data/docs/getting-started.md +10 -0
  18. data/docs/index.md +91 -0
  19. data/docs/technical-overview/concurrency.md +78 -16
  20. data/docs/technical-overview/design-principles.md +7 -0
  21. data/docs/technical-overview/exception-handling.md +57 -9
  22. data/docs/technical-overview/extending.md +7 -0
  23. data/docs/technical-overview/fiber-scheduling.md +128 -18
  24. data/docs/technical-overview.md +10 -0
  25. data/docs/user-guide/web-server.md +7 -0
  26. data/docs/user-guide.md +10 -0
  27. data/examples/core/xx-deadlock.rb +8 -0
  28. data/examples/core/xx-state-machine.rb +51 -0
  29. data/examples/core/xx-trace.rb +80 -0
  30. data/examples/interfaces/pg_notify.rb +35 -0
  31. data/examples/io/xx-httparty.rb +31 -6
  32. data/examples/io/xx-irb.rb +1 -11
  33. data/examples/io/xx-switch.rb +15 -0
  34. data/ext/gyro/gyro.c +77 -38
  35. data/ext/gyro/gyro.h +15 -5
  36. data/ext/gyro/gyro_ext.c +3 -0
  37. data/ext/gyro/thread.c +47 -32
  38. data/ext/gyro/tracing.c +11 -0
  39. data/lib/polyphony/core/global_api.rb +11 -4
  40. data/lib/polyphony/core/supervisor.rb +1 -0
  41. data/lib/polyphony/core/thread_pool.rb +44 -35
  42. data/lib/polyphony/extensions/fiber.rb +19 -9
  43. data/lib/polyphony/extensions/io.rb +14 -14
  44. data/lib/polyphony/extensions/socket.rb +3 -3
  45. data/lib/polyphony/irb.rb +13 -0
  46. data/lib/polyphony/postgres.rb +15 -0
  47. data/lib/polyphony/trace.rb +98 -0
  48. data/lib/polyphony/version.rb +1 -1
  49. data/lib/polyphony.rb +1 -0
  50. data/polyphony.gemspec +21 -12
  51. data/test/helper.rb +3 -2
  52. data/test/test_fiber.rb +53 -3
  53. data/test/test_global_api.rb +12 -0
  54. data/test/test_gyro.rb +2 -2
  55. data/test/test_supervisor.rb +12 -0
  56. data/test/test_thread.rb +12 -0
  57. data/test/test_thread_pool.rb +75 -0
  58. data/test/test_throttler.rb +6 -0
  59. data/test/test_trace.rb +66 -0
  60. metadata +99 -9
  61. data/docs/README.md +0 -36
  62. data/docs/summary.md +0 -60
  63. data/docs/technical-overview/faq.md +0 -97
data/ext/gyro/gyro.c CHANGED
@@ -6,12 +6,13 @@ ID ID_call;
6
6
  ID ID_caller;
7
7
  ID ID_clear;
8
8
  ID ID_each;
9
+ ID ID_fiber_trace;
9
10
  ID ID_inspect;
10
11
  ID ID_new;
11
12
  ID ID_raise;
12
13
  ID ID_ivar_running;
13
- ID ID_scheduled;
14
- ID ID_scheduled_value;
14
+ ID ID_runnable;
15
+ ID ID_runnable_value;
15
16
  ID ID_size;
16
17
  ID ID_signal_bang;
17
18
  ID ID_switch_fiber;
@@ -20,14 +21,30 @@ ID ID_R;
20
21
  ID ID_W;
21
22
  ID ID_RW;
22
23
 
24
+ ID ID_trace_ev_loop_enter;
25
+ ID ID_trace_ev_loop_leave;
26
+ ID ID_trace_run;
27
+ ID ID_trace_runnable;
28
+ ID ID_trace_terminate;
29
+ ID ID_trace_wait;
30
+
23
31
  ID ID_empty;
24
32
  ID ID_pop;
25
33
  ID ID_push;
26
34
 
27
35
  VALUE SYM_dead;
28
36
  VALUE SYM_running;
29
- VALUE SYM_scheduled;
30
- VALUE SYM_suspended;
37
+ VALUE SYM_runnable;
38
+ VALUE SYM_waiting;
39
+
40
+ VALUE SYM_fiber_create;
41
+ VALUE SYM_fiber_ev_loop_enter;
42
+ VALUE SYM_fiber_ev_loop_leave;
43
+ VALUE SYM_fiber_run;
44
+ VALUE SYM_fiber_schedule;
45
+ VALUE SYM_fiber_switchpoint;
46
+ VALUE SYM_fiber_terminate;
47
+
31
48
 
32
49
  // static VALUE Gyro_break_set(VALUE self) {
33
50
  // break_flag = 1;
@@ -64,7 +81,7 @@ static VALUE Gyro_unref(VALUE self) {
64
81
  }
65
82
 
66
83
  static VALUE Gyro_suspend(VALUE self) {
67
- rb_ivar_set(self, ID_scheduled_value, Qnil);
84
+ rb_ivar_set(self, ID_runnable_value, Qnil);
68
85
  VALUE ret = Thread_switch_fiber(rb_thread_current());
69
86
 
70
87
  if (RTEST(rb_obj_is_kind_of(ret, rb_eException))) {
@@ -94,19 +111,21 @@ static VALUE Fiber_state(VALUE self) {
94
111
  if (!rb_fiber_alive_p(self) || (rb_ivar_get(self, ID_ivar_running) == Qfalse))
95
112
  return SYM_dead;
96
113
  if (rb_fiber_current() == self) return SYM_running;
97
- if (rb_ivar_get(self, ID_scheduled) != Qnil) return SYM_scheduled;
114
+ if (rb_ivar_get(self, ID_runnable) != Qnil) return SYM_runnable;
98
115
 
99
- return SYM_suspended;
116
+ return SYM_waiting;
100
117
  }
101
118
 
102
119
  inline void Gyro_schedule_fiber(VALUE fiber, VALUE value) {
103
- rb_ivar_set(fiber, ID_scheduled_value, value);
104
- // if fiber is already scheduled, we just set the scheduled value, then return
105
- if (rb_ivar_get(fiber, ID_scheduled) != Qnil)
106
- return;
120
+ if (__tracing_enabled__) {
121
+ rb_funcall(rb_cObject, ID_fiber_trace, 3, SYM_fiber_schedule, fiber, value);
122
+ }
123
+ Thread_schedule_fiber(rb_thread_current(), fiber, value);
124
+ }
107
125
 
108
- rb_ivar_set(fiber, ID_scheduled, Qtrue);
109
- Thread_schedule_fiber(rb_thread_current(), fiber);
126
+ VALUE Gyro_trace(VALUE self, VALUE enabled) {
127
+ __tracing_enabled__ = RTEST(enabled) ? 1 : 0;
128
+ return Qnil;
110
129
  }
111
130
 
112
131
  void Init_Gyro() {
@@ -115,6 +134,7 @@ void Init_Gyro() {
115
134
  rb_define_singleton_method(mGyro, "post_fork", Gyro_post_fork, 0);
116
135
  rb_define_singleton_method(mGyro, "ref", Gyro_ref, 0);
117
136
  rb_define_singleton_method(mGyro, "unref", Gyro_unref, 0);
137
+ rb_define_singleton_method(mGyro, "trace", Gyro_trace, 1);
118
138
 
119
139
  // rb_define_singleton_method(mGyro, "break!", Gyro_break_set, 0);
120
140
  // rb_define_singleton_method(mGyro, "break?", Gyro_break_get, 0);
@@ -127,34 +147,53 @@ void Init_Gyro() {
127
147
  rb_define_method(cFiber, "schedule", Fiber_schedule, -1);
128
148
  rb_define_method(cFiber, "state", Fiber_state, 0);
129
149
 
130
- ID_call = rb_intern("call");
131
- ID_caller = rb_intern("caller");
132
- ID_clear = rb_intern("clear");
133
- ID_each = rb_intern("each");
134
- ID_inspect = rb_intern("inspect");
135
- ID_new = rb_intern("new");
136
- ID_raise = rb_intern("raise");
137
- ID_ivar_running = rb_intern("@running");
138
- ID_scheduled = rb_intern("scheduled");
139
- ID_scheduled_value = rb_intern("scheduled_value");
140
- ID_size = rb_intern("size");
141
- ID_signal_bang = rb_intern("signal!");
142
- ID_switch_fiber = rb_intern("switch_fiber");
143
- ID_transfer = rb_intern("transfer");
144
- ID_R = rb_intern("r");
145
- ID_W = rb_intern("w");
146
- ID_RW = rb_intern("rw");
147
-
148
- ID_empty = rb_intern("empty?");
149
- ID_pop = rb_intern("pop");
150
- ID_push = rb_intern("push");
150
+ ID_call = rb_intern("call");
151
+ ID_caller = rb_intern("caller");
152
+ ID_clear = rb_intern("clear");
153
+ ID_each = rb_intern("each");
154
+ ID_empty = rb_intern("empty?");
155
+ ID_inspect = rb_intern("inspect");
156
+ ID_ivar_running = rb_intern("@running");
157
+ ID_new = rb_intern("new");
158
+ ID_pop = rb_intern("pop");
159
+ ID_push = rb_intern("push");
160
+ ID_raise = rb_intern("raise");
161
+ ID_runnable = rb_intern("runnable");
162
+ ID_runnable_value = rb_intern("runnable_value");
163
+ ID_signal_bang = rb_intern("signal!");
164
+ ID_size = rb_intern("size");
165
+ ID_switch_fiber = rb_intern("switch_fiber");
166
+ ID_transfer = rb_intern("transfer");
167
+
168
+ ID_R = rb_intern("r");
169
+ ID_RW = rb_intern("rw");
170
+ ID_W = rb_intern("w");
171
+
172
+ ID_fiber_trace = rb_intern("__fiber_trace__");
173
+
174
+ #define GLOBAL_SYM(sym) var = ID2SYM(rb_intern(sym)); rb_global_variable(sym)
151
175
 
152
176
  SYM_dead = ID2SYM(rb_intern("dead"));
153
177
  SYM_running = ID2SYM(rb_intern("running"));
154
- SYM_scheduled = ID2SYM(rb_intern("scheduled"));
155
- SYM_suspended = ID2SYM(rb_intern("suspended"));
178
+ SYM_runnable = ID2SYM(rb_intern("runnable"));
179
+ SYM_waiting = ID2SYM(rb_intern("waiting"));
156
180
  rb_global_variable(&SYM_dead);
157
181
  rb_global_variable(&SYM_running);
158
- rb_global_variable(&SYM_scheduled);
159
- rb_global_variable(&SYM_suspended);
182
+ rb_global_variable(&SYM_runnable);
183
+ rb_global_variable(&SYM_waiting);
184
+
185
+ SYM_fiber_create = ID2SYM(rb_intern("fiber_create"));
186
+ SYM_fiber_ev_loop_enter = ID2SYM(rb_intern("fiber_ev_loop_enter"));
187
+ SYM_fiber_ev_loop_leave = ID2SYM(rb_intern("fiber_ev_loop_leave"));
188
+ SYM_fiber_run = ID2SYM(rb_intern("fiber_run"));
189
+ SYM_fiber_schedule = ID2SYM(rb_intern("fiber_schedule"));
190
+ SYM_fiber_switchpoint = ID2SYM(rb_intern("fiber_switchpoint"));
191
+ SYM_fiber_terminate = ID2SYM(rb_intern("fiber_terminate"));
192
+ rb_global_variable(&SYM_fiber_create);
193
+ rb_global_variable(&SYM_fiber_ev_loop_enter);
194
+ rb_global_variable(&SYM_fiber_ev_loop_leave);
195
+ rb_global_variable(&SYM_fiber_run);
196
+ rb_global_variable(&SYM_fiber_schedule);
197
+ rb_global_variable(&SYM_fiber_switchpoint);
198
+ rb_global_variable(&SYM_fiber_terminate);
160
199
  }
data/ext/gyro/gyro.h CHANGED
@@ -44,7 +44,7 @@ VALUE Thread_ref(VALUE thread);
44
44
  VALUE Thread_unref(VALUE thread);
45
45
  VALUE Thread_switch_fiber(VALUE thread);
46
46
  VALUE Fiber_await();
47
- VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber);
47
+ VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
48
48
  VALUE Thread_post_fork(VALUE thread);
49
49
 
50
50
 
@@ -58,17 +58,17 @@ extern VALUE cGyro_Queue;
58
58
  extern VALUE cGyro_Selector;
59
59
  extern VALUE cGyro_Timer;
60
60
 
61
- extern VALUE Gyro_reactor_fiber;
62
- extern VALUE Gyro_root_fiber;
63
-
64
61
  extern ID ID_call;
65
62
  extern ID ID_caller;
66
63
  extern ID ID_clear;
67
64
  extern ID ID_each;
65
+ extern ID ID_fiber_trace;
68
66
  extern ID ID_inspect;
67
+ extern ID ID_ivar_running;
69
68
  extern ID ID_new;
70
69
  extern ID ID_raise;
71
- extern ID ID_scheduled_value;
70
+ extern ID ID_runnable;
71
+ extern ID ID_runnable_value;
72
72
  extern ID ID_signal_bang;
73
73
  extern ID ID_size;
74
74
  extern ID ID_switch_fiber;
@@ -77,4 +77,14 @@ extern ID ID_R;
77
77
  extern ID ID_W;
78
78
  extern ID ID_RW;
79
79
 
80
+ extern VALUE SYM_fiber_create;
81
+ extern VALUE SYM_fiber_ev_loop_enter;
82
+ extern VALUE SYM_fiber_ev_loop_leave;
83
+ extern VALUE SYM_fiber_run;
84
+ extern VALUE SYM_fiber_schedule;
85
+ extern VALUE SYM_fiber_switchpoint;
86
+ extern VALUE SYM_fiber_terminate;
87
+
88
+ extern int __tracing_enabled__;
89
+
80
90
  #endif /* RUBY_EV_H */
data/ext/gyro/gyro_ext.c CHANGED
@@ -10,6 +10,7 @@ void Init_Gyro_Signal();
10
10
  void Init_Gyro_Timer();
11
11
  void Init_Socket();
12
12
  void Init_Thread();
13
+ void Init_Tracing();
13
14
 
14
15
  void Init_gyro_ext() {
15
16
  ev_set_allocator(xrealloc);
@@ -25,4 +26,6 @@ void Init_gyro_ext() {
25
26
 
26
27
  Init_Socket();
27
28
  Init_Thread();
29
+
30
+ Init_Tracing();
28
31
  }
data/ext/gyro/thread.c CHANGED
@@ -3,18 +3,18 @@
3
3
  static VALUE cQueue;
4
4
 
5
5
  static ID ID_create_event_selector;
6
- static ID ID_ivar_event_selector;
7
- static ID ID_ivar_event_selector_proc;
6
+ static ID ID_empty;
8
7
  static ID ID_fiber_ref_count;
9
- static ID ID_run_queue;
8
+ static ID ID_ivar_event_selector_proc;
9
+ static ID ID_ivar_event_selector;
10
10
  static ID ID_ivar_main_fiber;
11
- static ID ID_stop;
12
-
13
- static ID ID_scheduled;
14
-
15
- static ID ID_empty;
16
11
  static ID ID_pop;
17
12
  static ID ID_push;
13
+ static ID ID_run_queue;
14
+ // static ID ID_run_queue_head;
15
+ // static ID ID_run_queue_tail;
16
+ static ID ID_runnable_next;
17
+ static ID ID_stop;
18
18
 
19
19
  VALUE event_selector_factory_proc(RB_BLOCK_CALL_FUNC_ARGLIST(args, klass)) {
20
20
  return rb_funcall(klass, ID_new, 1, rb_ary_entry(args, 0));
@@ -97,44 +97,59 @@ static VALUE Thread_fiber_scheduling_stats(VALUE self) {
97
97
  return stats;
98
98
  }
99
99
 
100
- inline VALUE Thread_schedule_fiber(VALUE self, VALUE fiber) {
101
- VALUE queue = rb_ivar_get(self, ID_run_queue);
102
- rb_ary_push(queue, fiber);
100
+ inline VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
101
+ // if fiber is already scheduled, just set the scheduled value, then return
102
+ rb_ivar_set(fiber, ID_runnable_value, value);
103
+ if (rb_ivar_get(fiber, ID_runnable) == Qnil) {
104
+ VALUE queue = rb_ivar_get(self, ID_run_queue);
105
+ rb_ary_push(queue, fiber);
106
+ rb_ivar_set(fiber, ID_runnable, Qtrue);
107
+ }
103
108
  return self;
104
109
  }
105
110
 
106
111
  VALUE Thread_switch_fiber(VALUE self) {
112
+ VALUE current_fiber;
113
+ if (__tracing_enabled__) {
114
+ current_fiber = rb_fiber_current();
115
+ if (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse) {
116
+ rb_funcall(rb_cObject, ID_fiber_trace, 2, SYM_fiber_switchpoint, current_fiber);
117
+ }
118
+ }
107
119
  VALUE queue = rb_ivar_get(self, ID_run_queue);
108
120
  VALUE selector = rb_ivar_get(self, ID_ivar_event_selector);
109
- long scheduled_count;
121
+ VALUE next_fiber;
110
122
 
111
123
  while (1) {
112
- scheduled_count = RARRAY_LEN(queue);
124
+ next_fiber = rb_ary_shift(queue);
113
125
  // if (break_flag != 0) {
114
126
  // return Qnil;
115
127
  // }
116
- if ((scheduled_count > 0) || (Thread_fiber_ref_count(self) == 0)) {
128
+ if ((next_fiber != Qnil) || (Thread_fiber_ref_count(self) == 0)) {
117
129
  break;
118
130
  }
119
131
 
132
+ if (__tracing_enabled__) {
133
+ rb_funcall(rb_cObject, ID_fiber_trace, 2, SYM_fiber_ev_loop_enter, current_fiber);
134
+ }
120
135
  Gyro_Selector_run(selector);
136
+ if (__tracing_enabled__) {
137
+ rb_funcall(rb_cObject, ID_fiber_trace, 2, SYM_fiber_ev_loop_leave, current_fiber);
138
+ }
121
139
  }
122
140
 
123
- VALUE next_fiber;
124
- // while (1) {
125
- if (scheduled_count == 0) {
126
- return Qnil;
127
- }
128
- next_fiber = rb_ary_shift(queue);
129
- // break;
130
- // if (rb_fiber_alive_p(next_fiber) == Qtrue) {
131
- // break;
132
- // }
133
- // }
141
+ if (next_fiber == Qnil) {
142
+ return Qnil;
143
+ }
134
144
 
135
145
  // run next fiber
136
- VALUE value = rb_ivar_get(next_fiber, ID_scheduled_value);
137
- rb_ivar_set(next_fiber, ID_scheduled, Qnil);
146
+ VALUE value = rb_ivar_get(next_fiber, ID_runnable_value);
147
+
148
+ if (__tracing_enabled__) {
149
+ rb_funcall(rb_cObject, ID_fiber_trace, 3, SYM_fiber_run, next_fiber, value);
150
+ }
151
+
152
+ rb_ivar_set(next_fiber, ID_runnable, Qnil);
138
153
  RB_GC_GUARD(next_fiber);
139
154
  RB_GC_GUARD(value);
140
155
  return rb_funcall(next_fiber, ID_transfer, 1, value);
@@ -180,20 +195,20 @@ void Init_Thread() {
180
195
  rb_define_method(rb_cThread, "reset_fiber_scheduling", Thread_reset_fiber_scheduling, 0);
181
196
  rb_define_method(rb_cThread, "fiber_scheduling_stats", Thread_fiber_scheduling_stats, 0);
182
197
 
183
- rb_define_method(rb_cThread, "schedule_fiber", Thread_schedule_fiber, 1);
198
+ rb_define_method(rb_cThread, "schedule_fiber", Thread_schedule_fiber, 2);
184
199
  rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
185
200
 
186
201
 
187
202
  ID_create_event_selector = rb_intern("create_event_selector");
203
+ ID_empty = rb_intern("empty?");
204
+ ID_fiber_ref_count = rb_intern("fiber_ref_count");
188
205
  ID_ivar_event_selector = rb_intern("@event_selector");
189
206
  ID_ivar_event_selector_proc = rb_intern("@event_selector_proc");
190
207
  ID_ivar_main_fiber = rb_intern("@main_fiber");
191
- ID_fiber_ref_count = rb_intern("fiber_ref_count");
192
- ID_run_queue = rb_intern("run_queue");
193
- ID_scheduled = rb_intern("scheduled");
194
- ID_empty = rb_intern("empty?");
195
208
  ID_pop = rb_intern("pop");
196
209
  ID_push = rb_intern("push");
210
+ ID_run_queue = rb_intern("run_queue");
211
+ ID_runnable_next = rb_intern("runnable_next");
197
212
  ID_stop = rb_intern("stop");
198
213
 
199
214
  SYM_scheduled_fibers = ID2SYM(rb_intern("scheduled_fibers"));
@@ -0,0 +1,11 @@
1
+ #include "gyro.h"
2
+
3
+ int __tracing_enabled__ = 0;
4
+
5
+ VALUE __fiber_trace__(int argc, VALUE *argv, VALUE self) {
6
+ return rb_ary_new4(argc, argv);
7
+ }
8
+
9
+ void Init_Tracing() {
10
+ rb_define_global_function("__fiber_trace__", __fiber_trace__, -1);
11
+ }
@@ -29,8 +29,8 @@ module API
29
29
  canceller.stop
30
30
  end
31
31
 
32
- def spin(&block)
33
- Fiber.spin(caller, &block)
32
+ def spin(tag = nil, &block)
33
+ Fiber.spin(tag, caller, &block)
34
34
  end
35
35
  alias_method :defer, :spin
36
36
 
@@ -66,17 +66,24 @@ module API
66
66
  end
67
67
 
68
68
  def sleep(duration = nil)
69
- return suspend unless duration
69
+ return sleep_forever unless duration
70
70
 
71
71
  timer = Gyro::Timer.new(duration, 0)
72
72
  timer.await
73
73
  end
74
74
 
75
+ def sleep_forever
76
+ Thread.current.fiber_ref
77
+ suspend
78
+ ensure
79
+ Thread.current.fiber_unref
80
+ end
81
+
75
82
  def supervise(&block)
76
83
  Supervisor.new.await(&block)
77
84
  end
78
85
 
79
- def throttled_loop(rarote, count: nil, &block)
86
+ def throttled_loop(rate, count: nil, &block)
80
87
  throttler = Throttler.new(rate)
81
88
  if count
82
89
  count.times { throttler.(&block) }
@@ -10,6 +10,7 @@ class Supervisor
10
10
  def initialize
11
11
  @fibers = []
12
12
  @pending = {}
13
+ yield(self) if block_given?
13
14
  end
14
15
 
15
16
  def await(&block)
@@ -1,38 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :process, :setup, :size=, :busy?
4
-
5
- @size = 10
6
-
7
- def process(&block)
8
- setup unless @task_queue
9
-
10
- watcher = Gyro::Async.new
11
- @task_queue << [block, watcher]
12
- watcher.await
13
- end
14
-
15
- def size=(size)
16
- @size = size
17
- end
18
-
19
- def busy?
20
- !@queue.empty?
21
- end
22
-
23
- def setup
24
- @task_queue = ::Queue.new
25
- @threads = (1..@size).map { Thread.new { thread_loop } }
26
- end
27
-
28
- def thread_loop
29
- loop { run_queued_task }
30
- end
31
-
32
- def run_queued_task
33
- (block, watcher) = @task_queue.pop
34
- result = block.()
35
- watcher.signal!(result)
36
- rescue Exception => e
37
- watcher.signal!(e)
3
+ export_default :ThreadPool
4
+
5
+ require 'etc'
6
+
7
+ # Implements a pool of threads
8
+ class ThreadPool
9
+ attr_reader :size
10
+
11
+ def initialize(size = Etc.nprocessors)
12
+ @size = size
13
+ @task_queue = ::Queue.new
14
+ @threads = (1..@size).map { Thread.new { thread_loop } }
15
+ end
16
+
17
+ def process(&block)
18
+ setup unless @task_queue
19
+
20
+ watcher = Gyro::Async.new
21
+ @task_queue << [block, watcher]
22
+ watcher.await
23
+ end
24
+
25
+ def cast(&block)
26
+ setup unless @task_queue
27
+
28
+ @task_queue << [block, nil]
29
+ self
30
+ end
31
+
32
+ def busy?
33
+ !@task_queue.empty?
34
+ end
35
+
36
+ def thread_loop
37
+ loop { run_queued_task }
38
+ end
39
+
40
+ def run_queued_task
41
+ (block, watcher) = @task_queue.pop
42
+ result = block.()
43
+ watcher&.signal!(result)
44
+ rescue Exception => e
45
+ watcher ? watcher.signal!(e) : raise(e)
46
+ end
38
47
  end
@@ -104,13 +104,18 @@ class ::Fiber
104
104
  @running_fibers_map.size
105
105
  end
106
106
 
107
- def self.spin(orig_caller = caller, &block)
107
+ def self.spin(tag = nil, orig_caller = caller, &block)
108
108
  f = new { |v| f.run(v) }
109
- f.setup(block, orig_caller)
109
+ f.setup(tag, block, orig_caller)
110
110
  f
111
111
  end
112
112
 
113
- def setup(block, caller)
113
+ attr_accessor :tag
114
+ Fiber.current.tag = :main
115
+
116
+ def setup(tag, block, caller)
117
+ __fiber_trace__(:fiber_create, self)
118
+ @tag = tag
114
119
  @calling_fiber = Fiber.current
115
120
  @caller = caller
116
121
  @block = block
@@ -120,19 +125,24 @@ class ::Fiber
120
125
  def run(first_value)
121
126
  Kernel.raise first_value if first_value.is_a?(Exception)
122
127
 
123
- @running = true
124
- self.class.map[self] = true
125
- result = @block.(first_value)
126
- finish_execution(result)
127
- rescue Exceptions::MoveOn => e
128
- finish_execution(e.value)
128
+ start_execution(first_value)
129
129
  rescue ::Interrupt, ::SystemExit => e
130
130
  Thread.current.main_fiber.transfer e.class.new
131
+ rescue Exceptions::MoveOn => e
132
+ finish_execution(e.value)
131
133
  rescue Exception => e
132
134
  finish_execution(e, true)
133
135
  end
134
136
 
137
+ def start_execution(first_value)
138
+ @running = true
139
+ self.class.map[self] = true
140
+ result = @block.(first_value)
141
+ finish_execution(result)
142
+ end
143
+
135
144
  def finish_execution(result, uncaught_exception = false)
145
+ __fiber_trace__(:fiber_terminate, self, result)
136
146
  @result = result
137
147
  @running = false
138
148
  self.class.map.delete(self)
@@ -23,20 +23,20 @@ class ::IO
23
23
 
24
24
  EMPTY_HASH = {}.freeze
25
25
 
26
- alias_method :orig_foreach, :foreach
27
- def foreach(_name, _sep = $/, _limit = nil, _getline_args = EMPTY_HASH,
28
- &_block)
29
- # IO.orig_read(name).each_line(&block)
30
- raise NotImplementedError
31
-
32
- # if sep.is_a?(Integer)
33
- # sep = $/
34
- # limit = sep
35
- # end
36
- # File.open(name, 'r') do |f|
37
- # f.each_line(sep, limit, getline_args, &block)
38
- # end
39
- end
26
+ # alias_method :orig_foreach, :foreach
27
+ # def foreach(_name, _sep = $/, _limit = nil, _getline_args = EMPTY_HASH,
28
+ # &_block)
29
+ # # IO.orig_read(name).each_line(&block)
30
+ # raise NotImplementedError
31
+
32
+ # # if sep.is_a?(Integer)
33
+ # # sep = $/
34
+ # # limit = sep
35
+ # # end
36
+ # # File.open(name, 'r') do |f|
37
+ # # f.each_line(sep, limit, getline_args, &block)
38
+ # # end
39
+ # end
40
40
 
41
41
  alias_method :orig_read, :read
42
42
  def read(name, length = nil, offset = nil, opt = EMPTY_HASH)
@@ -10,7 +10,7 @@ class ::Socket
10
10
 
11
11
  def connect(remotesockaddr)
12
12
  loop do
13
- result = connect_nonblock(remotesockaddr, NO_EXCEPTION)
13
+ result = connect_nonblock(remotesockaddr, **NO_EXCEPTION)
14
14
  return if result == 0
15
15
 
16
16
  result == :wait_writable ? write_watcher.await : (raise IOError)
@@ -20,7 +20,7 @@ class ::Socket
20
20
  def recv(maxlen, flags = 0, outbuf = nil)
21
21
  outbuf ||= +''
22
22
  loop do
23
- result = recv_nonblock(maxlen, flags, outbuf, NO_EXCEPTION)
23
+ result = recv_nonblock(maxlen, flags, outbuf, **NO_EXCEPTION)
24
24
  raise IOError unless result
25
25
 
26
26
  result == :wait_readable ? read_watcher.await : (return result)
@@ -30,7 +30,7 @@ class ::Socket
30
30
  def recvfrom(maxlen, flags = 0)
31
31
  @read_buffer ||= +''
32
32
  loop do
33
- result = recvfrom_nonblock(maxlen, flags, @read_buffer, NO_EXCEPTION)
33
+ result = recvfrom_nonblock(maxlen, flags, @read_buffer, **NO_EXCEPTION)
34
34
  raise IOError unless result
35
35
 
36
36
  result == :wait_readable ? read_watcher.await : (return result)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'polyphony'
4
+
5
+ # readline blocks the current thread, so we offload it to the blocking-ops
6
+ # thread pool. That way, the reactor loop can keep running while waiting for
7
+ # readline to return
8
+ module ::Readline
9
+ alias_method :orig_readline, :readline
10
+ def readline(*args)
11
+ Polyphony::ThreadPool.process { orig_readline(*args) }
12
+ end
13
+ end
@@ -91,4 +91,19 @@ class ::PG::Connection
91
91
  end
92
92
 
93
93
  self.async_api = true
94
+
95
+ def wait_for_notify(timeout = nil, &block)
96
+ return move_on_after(timeout) { wait_for_notify(&block) } if timeout
97
+
98
+ loop do
99
+ socket_io.read_watcher.await
100
+ consume_input
101
+ notice = notifies
102
+ next unless notice
103
+
104
+ values = notice.values
105
+ block&.(*values)
106
+ return values.first
107
+ end
108
+ end
94
109
  end