polyphony 0.43.3 → 0.43.9

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +44 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +21 -4
  6. data/TODO.md +1 -2
  7. data/bin/stress.rb +28 -0
  8. data/docs/_includes/head.html +40 -0
  9. data/docs/_includes/title.html +1 -0
  10. data/docs/_user-guide/web-server.md +11 -11
  11. data/docs/getting-started/overview.md +4 -4
  12. data/docs/index.md +4 -3
  13. data/docs/main-concepts/design-principles.md +23 -34
  14. data/docs/main-concepts/fiber-scheduling.md +1 -1
  15. data/docs/polyphony-logo.png +0 -0
  16. data/examples/core/xx-channels.rb +4 -2
  17. data/examples/core/xx-using-a-mutex.rb +2 -1
  18. data/examples/io/xx-happy-eyeballs.rb +21 -22
  19. data/examples/io/xx-zip.rb +19 -0
  20. data/examples/performance/fiber_transfer.rb +47 -0
  21. data/examples/performance/mem-usage.rb +34 -28
  22. data/examples/performance/messaging.rb +29 -0
  23. data/examples/performance/multi_snooze.rb +11 -9
  24. data/examples/xx-spin.rb +32 -0
  25. data/ext/polyphony/event.c +86 -0
  26. data/ext/polyphony/fiber.c +0 -5
  27. data/ext/polyphony/libev_agent.c +181 -24
  28. data/ext/polyphony/polyphony.c +0 -2
  29. data/ext/polyphony/polyphony.h +14 -7
  30. data/ext/polyphony/polyphony_ext.c +4 -2
  31. data/ext/polyphony/queue.c +187 -0
  32. data/ext/polyphony/ring_buffer.c +96 -0
  33. data/ext/polyphony/ring_buffer.h +28 -0
  34. data/ext/polyphony/thread.c +18 -12
  35. data/lib/polyphony.rb +5 -14
  36. data/lib/polyphony/core/channel.rb +3 -34
  37. data/lib/polyphony/core/global_api.rb +1 -1
  38. data/lib/polyphony/core/resource_pool.rb +13 -75
  39. data/lib/polyphony/core/sync.rb +12 -9
  40. data/lib/polyphony/core/thread_pool.rb +1 -1
  41. data/lib/polyphony/extensions/core.rb +34 -0
  42. data/lib/polyphony/extensions/fiber.rb +9 -2
  43. data/lib/polyphony/extensions/io.rb +17 -16
  44. data/lib/polyphony/extensions/openssl.rb +8 -0
  45. data/lib/polyphony/extensions/socket.rb +12 -0
  46. data/lib/polyphony/version.rb +1 -1
  47. data/test/helper.rb +1 -1
  48. data/test/q.rb +24 -0
  49. data/test/test_agent.rb +1 -1
  50. data/test/test_event.rb +12 -0
  51. data/test/test_global_api.rb +2 -2
  52. data/test/test_io.rb +24 -2
  53. data/test/test_queue.rb +59 -1
  54. data/test/test_resource_pool.rb +0 -43
  55. data/test/test_trace.rb +18 -17
  56. metadata +15 -5
  57. data/ext/polyphony/libev_queue.c +0 -217
  58. data/lib/polyphony/event.rb +0 -27
@@ -0,0 +1,96 @@
1
+ #include "polyphony.h"
2
+ #include "ring_buffer.h"
3
+
4
+ void ring_buffer_init(ring_buffer *buffer) {
5
+ buffer->size = 1;
6
+ buffer->count = 0;
7
+ buffer->entries = malloc(buffer->size * sizeof(VALUE));
8
+ buffer->head = 0;
9
+ buffer->tail = 0;
10
+ }
11
+
12
+ void ring_buffer_free(ring_buffer *buffer) {
13
+ free(buffer->entries);
14
+ }
15
+
16
+ int ring_buffer_empty_p(ring_buffer *buffer) {
17
+ return buffer->count == 0;
18
+ }
19
+
20
+ VALUE ring_buffer_shift(ring_buffer *buffer) {
21
+ VALUE value;
22
+ if (buffer->count == 0) return Qnil;
23
+
24
+ value = buffer->entries[buffer->head];
25
+ buffer->head = (buffer->head + 1) % buffer->size;
26
+ buffer->count--;
27
+ // INSPECT(value);
28
+ return value;
29
+ }
30
+
31
+ void ring_buffer_resize(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(VALUE));
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 ring_buffer_unshift(ring_buffer *buffer, VALUE value) {
41
+ if (buffer->count == buffer->size) ring_buffer_resize(buffer);
42
+
43
+ buffer->head = (buffer->head - 1) % buffer->size;
44
+ buffer->entries[buffer->head] = value;
45
+ buffer->count++;
46
+ }
47
+
48
+ void ring_buffer_push(ring_buffer *buffer, VALUE value) {
49
+ if (buffer->count == buffer->size) ring_buffer_resize(buffer);
50
+
51
+ buffer->entries[buffer->tail] = value;
52
+ buffer->tail = (buffer->tail + 1) % buffer->size;
53
+ buffer->count++;
54
+ }
55
+
56
+ void ring_buffer_mark(ring_buffer *buffer) {
57
+ for (unsigned int i = 0; i < buffer->count; i++)
58
+ rb_gc_mark(buffer->entries[(buffer->head + i) % buffer->size]);
59
+ }
60
+
61
+ void ring_buffer_shift_each(ring_buffer *buffer) {
62
+ for (unsigned int i = 0; i < buffer->count; i++)
63
+ rb_yield(buffer->entries[(buffer->head + i) % buffer->size]);
64
+
65
+ buffer->count = buffer->head = buffer->tail = 0;
66
+ }
67
+
68
+ VALUE ring_buffer_shift_all(ring_buffer *buffer) {
69
+ VALUE array = rb_ary_new_capa(buffer->count);
70
+ for (unsigned int i = 0; i < buffer->count; i++)
71
+ rb_ary_push(array, buffer->entries[(buffer->head + i) % buffer->size]);
72
+ buffer->count = buffer->head = buffer->tail = 0;
73
+ return array;
74
+ }
75
+
76
+ void ring_buffer_delete_at(ring_buffer *buffer, unsigned int idx) {
77
+ for (unsigned int idx2 = idx; idx2 != buffer->tail; idx2 = (idx2 + 1) % buffer->size) {
78
+ buffer->entries[idx2] = buffer->entries[(idx2 + 1) % buffer->size];
79
+ }
80
+ buffer->count--;
81
+ buffer->tail = (buffer->tail - 1) % buffer->size;
82
+ }
83
+
84
+ void ring_buffer_delete(ring_buffer *buffer, VALUE value) {
85
+ for (unsigned int i = 0; i < buffer->count; i++) {
86
+ unsigned int idx = (buffer->head + i) % buffer->size;
87
+ if (buffer->entries[idx] == value) {
88
+ ring_buffer_delete_at(buffer, idx);
89
+ return;
90
+ }
91
+ }
92
+ }
93
+
94
+ void ring_buffer_clear(ring_buffer *buffer) {
95
+ buffer->count = buffer->head = buffer->tail = 0;
96
+ }
@@ -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 */
@@ -11,10 +11,9 @@ ID ID_runnable_next;
11
11
  ID ID_stop;
12
12
 
13
13
  static VALUE Thread_setup_fiber_scheduling(VALUE self) {
14
- VALUE queue;
14
+ VALUE queue = rb_funcall(cQueue, ID_new, 0);
15
15
 
16
16
  rb_ivar_set(self, ID_ivar_main_fiber, rb_fiber_current());
17
- queue = rb_ary_new();
18
17
  rb_ivar_set(self, ID_run_queue, queue);
19
18
 
20
19
  return self;
@@ -54,14 +53,12 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
54
53
  if (rb_fiber_alive_p(fiber) != Qtrue) return self;
55
54
 
56
55
  FIBER_TRACE(3, SYM_fiber_schedule, fiber, value);
57
- // if fiber is already scheduled, just set the scheduled value, then return
58
56
  rb_ivar_set(fiber, ID_runnable_value, value);
59
- if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
60
- return self;
61
- }
57
+ // if fiber is already scheduled, just set the scheduled value, then return
58
+ if (rb_ivar_get(fiber, ID_runnable) != Qnil) return self;
62
59
 
63
60
  queue = rb_ivar_get(self, ID_run_queue);
64
- rb_ary_push(queue, fiber);
61
+ Queue_push(queue, fiber);
65
62
  rb_ivar_set(fiber, ID_runnable, Qtrue);
66
63
 
67
64
  if (rb_thread_current() != self) {
@@ -88,13 +85,13 @@ VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value)
88
85
 
89
86
  // if fiber is already scheduled, remove it from the run queue
90
87
  if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
91
- rb_ary_delete(queue, fiber);
88
+ Queue_delete(queue, fiber);
92
89
  } else {
93
90
  rb_ivar_set(fiber, ID_runnable, Qtrue);
94
91
  }
95
92
 
96
93
  // the fiber is given priority by putting it at the front of the run queue
97
- rb_ary_unshift(queue, fiber);
94
+ Queue_unshift(queue, fiber);
98
95
 
99
96
  if (rb_thread_current() != self) {
100
97
  // if the fiber scheduling is done across threads, we need to make sure the
@@ -115,6 +112,7 @@ VALUE Thread_switch_fiber(VALUE self) {
115
112
  VALUE value;
116
113
  VALUE agent = rb_ivar_get(self, ID_ivar_agent);
117
114
  int ref_count;
115
+ int agent_was_polled = 0;1;
118
116
 
119
117
  if (__tracing_enabled__) {
120
118
  if (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse) {
@@ -124,9 +122,9 @@ VALUE Thread_switch_fiber(VALUE self) {
124
122
 
125
123
  ref_count = LibevAgent_ref_count(agent);
126
124
  while (1) {
127
- next_fiber = rb_ary_shift(queue);
125
+ next_fiber = Queue_shift_no_wait(queue);
128
126
  if (next_fiber != Qnil) {
129
- if (ref_count > 0) {
127
+ if (agent_was_polled == 0 && ref_count > 0) {
130
128
  // this mechanism prevents event starvation in case the run queue never
131
129
  // empties
132
130
  LibevAgent_poll(agent, Qtrue, current_fiber, queue);
@@ -136,6 +134,7 @@ VALUE Thread_switch_fiber(VALUE self) {
136
134
  if (ref_count == 0) break;
137
135
 
138
136
  LibevAgent_poll(agent, Qnil, current_fiber, queue);
137
+ agent_was_polled = 1;
139
138
  }
140
139
 
141
140
  if (next_fiber == Qnil) return Qnil;
@@ -151,9 +150,15 @@ VALUE Thread_switch_fiber(VALUE self) {
151
150
  value : rb_funcall(next_fiber, ID_transfer, 1, value);
152
151
  }
153
152
 
153
+ VALUE Thread_run_queue_trace(VALUE self) {
154
+ VALUE queue = rb_ivar_get(self, ID_run_queue);
155
+ Queue_trace(queue);
156
+ return self;
157
+ }
158
+
154
159
  VALUE Thread_reset_fiber_scheduling(VALUE self) {
155
160
  VALUE queue = rb_ivar_get(self, ID_run_queue);
156
- rb_ary_clear(queue);
161
+ Queue_clear(queue);
157
162
  Thread_fiber_reset_ref_count(self);
158
163
  return self;
159
164
  }
@@ -182,6 +187,7 @@ void Init_Thread() {
182
187
  rb_define_method(rb_cThread, "schedule_fiber_with_priority",
183
188
  Thread_schedule_fiber_with_priority, 2);
184
189
  rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
190
+ rb_define_method(rb_cThread, "run_queue_trace", Thread_run_queue_trace, 0);
185
191
 
186
192
  ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
187
193
  ID_ivar_agent = rb_intern("@agent");
@@ -4,9 +4,6 @@ require 'fiber'
4
4
  require_relative './polyphony_ext'
5
5
 
6
6
  module Polyphony
7
- # Map Queue to Libev queue implementation
8
- Queue = LibevQueue
9
-
10
7
  # replace core Queue class with our own
11
8
  verbose = $VERBOSE
12
9
  $VERBOSE = nil
@@ -26,7 +23,6 @@ require_relative './polyphony/core/global_api'
26
23
  require_relative './polyphony/core/resource_pool'
27
24
  require_relative './polyphony/net'
28
25
  require_relative './polyphony/adapters/process'
29
- require_relative './polyphony/event'
30
26
 
31
27
  # Main Polyphony API
32
28
  module Polyphony
@@ -100,17 +96,12 @@ module Polyphony
100
96
  Polyphony::Process.watch(cmd, &block)
101
97
  end
102
98
 
103
- def emit_signal_exception(exception, fiber = Thread.main.main_fiber)
104
- Thread.current.break_out_of_ev_loop(fiber, exception)
105
- end
106
-
107
- def install_terminating_signal_handler(signal, exception_class)
108
- trap(signal) { emit_signal_exception(exception_class.new) }
109
- end
110
-
111
99
  def install_terminating_signal_handlers
112
- install_terminating_signal_handler('SIGTERM', ::SystemExit)
113
- install_terminating_signal_handler('SIGINT', ::Interrupt)
100
+ trap('SIGTERM', SystemExit)
101
+ orig_trap('SIGINT') do
102
+ orig_trap('SIGINT') { exit! }
103
+ Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, Interrupt.new)
104
+ end
114
105
  end
115
106
 
116
107
  def terminate_threads
@@ -5,42 +5,11 @@ require_relative './exceptions'
5
5
  module Polyphony
6
6
  # Implements a unidirectional communication channel along the lines of Go
7
7
  # (buffered) channels.
8
- class Channel
9
- def initialize
10
- @payload_queue = []
11
- @waiting_queue = []
12
- end
8
+ class Channel < Polyphony::Queue
9
+ alias_method :receive, :shift
13
10
 
14
11
  def close
15
- stop = Polyphony::MoveOn.new
16
- @waiting_queue.slice(0..-1).each { |f| f.schedule(stop) }
17
- end
18
-
19
- def <<(value)
20
- if @waiting_queue.empty?
21
- @payload_queue << value
22
- else
23
- @waiting_queue.shift&.schedule(value)
24
- end
25
- snooze
26
- end
27
-
28
- def receive
29
- Thread.current.agent.ref
30
- if @payload_queue.empty?
31
- @waiting_queue << Fiber.current
32
- suspend
33
- else
34
- receive_from_queue
35
- end
36
- ensure
37
- Thread.current.agent.unref
38
- end
39
-
40
- def receive_from_queue
41
- payload = @payload_queue.shift
42
- snooze
43
- payload
12
+ flush_waiters(Polyphony::MoveOn.new)
44
13
  end
45
14
  end
46
15
  end
@@ -103,7 +103,7 @@ module Polyphony
103
103
 
104
104
  def sleep_forever
105
105
  Thread.current.agent.ref
106
- suspend
106
+ loop { sleep 60 }
107
107
  ensure
108
108
  Thread.current.agent.unref
109
109
  end
@@ -10,13 +10,10 @@ module Polyphony
10
10
  # @param &block [Proc] allocator block
11
11
  def initialize(opts, &block)
12
12
  @allocator = block
13
-
14
- @stock = []
15
- @queue = []
16
- @acquired_resources = {}
17
-
18
13
  @limit = opts[:limit] || 4
19
14
  @size = 0
15
+ @stock = Polyphony::Queue.new
16
+ @acquired_resources = {}
20
17
  end
21
18
 
22
19
  def available
@@ -25,58 +22,17 @@ module Polyphony
25
22
 
26
23
  def acquire
27
24
  fiber = Fiber.current
28
- if @acquired_resources[fiber]
29
- yield @acquired_resources[fiber]
30
- else
31
- begin
32
- Thread.current.agent.ref
33
- resource = wait_for_resource
34
- return unless resource
35
-
36
- @acquired_resources[fiber] = resource
37
- yield resource
38
- ensure
39
- @acquired_resources[fiber] = nil
40
- Thread.current.agent.unref
41
- release(resource) if resource
42
- end
43
- end
44
- end
45
-
46
- def wait_for_resource
47
- fiber = Fiber.current
48
- @queue << fiber
49
- ready_resource = from_stock
50
- return ready_resource if ready_resource
25
+ return @acquired_resources[fiber] if @acquired_resources[fiber]
51
26
 
52
- suspend
27
+ add_to_stock if @size < @limit && @stock.empty?
28
+ resource = @stock.shift
29
+ @acquired_resources[fiber] = resource
30
+ yield resource
53
31
  ensure
54
- @queue.delete(fiber)
55
- end
56
-
57
- def release(resource)
58
- if resource.__discarded__
59
- @size -= 1
60
- elsif resource
61
- return_to_stock(resource)
62
- dequeue
63
- end
64
- end
65
-
66
- def dequeue
67
- return if @queue.empty? || @stock.empty?
68
-
69
- @queue.shift.schedule(@stock.shift)
32
+ @acquired_resources.delete(fiber)
33
+ @stock.push resource if resource
70
34
  end
71
-
72
- def return_to_stock(resource)
73
- @stock << resource
74
- end
75
-
76
- def from_stock
77
- @stock.shift || (@size < @limit && allocate)
78
- end
79
-
35
+
80
36
  def method_missing(sym, *args, &block)
81
37
  acquire { |r| r.send(sym, *args, &block) }
82
38
  end
@@ -85,33 +41,15 @@ module Polyphony
85
41
  true
86
42
  end
87
43
 
88
- # Extension to allow discarding of resources
89
- module ResourceExtensions
90
- def __discarded__
91
- @__discarded__
92
- end
93
-
94
- def __discard__
95
- @__discarded__ = true
96
- end
97
- end
98
-
99
44
  # Allocates a resource
100
45
  # @return [any] allocated resource
101
- def allocate
102
- @size += 1
103
- @allocator.().tap { |r| r.extend ResourceExtensions }
104
- end
105
-
106
- def <<(resource)
46
+ def add_to_stock
107
47
  @size += 1
108
- resource.extend ResourceExtensions
109
- @stock << resource
110
- dequeue
48
+ @stock << @allocator.call
111
49
  end
112
50
 
113
51
  def preheat!
114
- (@limit - @size).times { @stock << allocate }
52
+ add_to_stock while @size < @limit
115
53
  end
116
54
  end
117
55
  end
@@ -4,18 +4,21 @@ module Polyphony
4
4
  # Implements mutex lock for synchronizing access to a shared resource
5
5
  class Mutex
6
6
  def initialize
7
- @waiting_fibers = Polyphony::Queue.new
7
+ @store = Queue.new
8
+ @store << :token
8
9
  end
9
10
 
10
11
  def synchronize
11
- fiber = Fiber.current
12
- @waiting_fibers << fiber
13
- suspend if @waiting_fibers.size > 1
14
- yield
15
- ensure
16
- @waiting_fibers.delete(fiber)
17
- @waiting_fibers.first&.schedule
18
- snooze
12
+ return yield if @holding_fiber == Fiber.current
13
+
14
+ begin
15
+ token = @store.shift
16
+ @holding_fiber = Fiber.current
17
+ yield
18
+ ensure
19
+ @holding_fiber = nil
20
+ @store << token
21
+ end
19
22
  end
20
23
  end
21
24
  end