polyphony 0.27 → 0.28

Sign up to get free protection for your applications and to get access to all the features.
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