polyphony 0.43.8 → 0.43.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bea457e28d23f96570d448855d00cf76250a55fcb02e12c4305cb551cb55faf4
4
- data.tar.gz: dc97409e61ce82c20eef25101a2c53046635461ef81302e54b121e5bb9c25aa1
3
+ metadata.gz: 14930ef46f39537ad6f2233b23691a05d910ecf6c22cd8429a8a82dcf4f2af51
4
+ data.tar.gz: a2c53e7082c35025cce3024113162ed5e67032eed82d49c363516441c3293898
5
5
  SHA512:
6
- metadata.gz: 53d345ee472bc77fc993880a1a725064bb934ec2789fa72bd97ec07b558942369860fb7077e0fcff6c95cfcff0c0d712090f70b1d9ff7f652160d15ddfc77de4
7
- data.tar.gz: 513a79eeb8a7766078d159cf85d367a6f3f50a0870825275408081d9cfe4e446e7f0b0120bd712f6281ccd0787779ec464b20c9e17c3c60178ceb39c061033c7
6
+ metadata.gz: ac9d80b5a0fad4b4d164bed60c42d386fc49e978b381a2a9fd6174bf616cb5a97cb4232c317c62ac0439f534d753e5c9a1e9cbb98be41145a9d288d6d719adcc
7
+ data.tar.gz: 95a8d13b133a0256ffb71df70e4348845dbcbe0c848a94feb0ceaf205e274f918d84529ef7d0773884e51f2e504625a96a4fdf548b17f03fa408c39f763609b6
@@ -1,3 +1,11 @@
1
+ ## 0.43.9 2020-07-22
2
+
3
+ * Rewrite `Channel` using `Queue`
4
+ * Rewrite `Mutex` using `Queue`
5
+ * Reimplement `Event` in C to prevent cross-thread race condition
6
+ * Reimplement `ResourcePool` using `Queue`
7
+ * Implement `Queue#size`
8
+
1
9
  ## 0.43.8 2020-07-21
2
10
 
3
11
  * Rename `LibevQueue` to `Queue`
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.43.8)
4
+ polyphony (0.43.9)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,10 +1,25 @@
1
- <p align="center"><img src="docs/polyphony-logo.png" /></p>
1
+ <h1 align="center">
2
+ <a href="https://digital-fabric.github.io/polyphony/">
3
+ <img src="docs/polyphony-logo.png" alt="Polyphony">
4
+ </a>
5
+ <br>
6
+ Polyphony
7
+ <br>
8
+ </h1>
2
9
 
3
- # Polyphony - Fine-Grained Concurrency for Ruby
10
+ <h4 align="center">Fine-Grained Concurrency for Ruby</h4>
4
11
 
5
- [![Gem Version](https://badge.fury.io/rb/polyphony.svg)](http://rubygems.org/gems/polyphony)
6
- [![Modulation Test](https://github.com/digital-fabric/polyphony/workflows/Tests/badge.svg)](https://github.com/digital-fabric/polyphony/actions?query=workflow%3ATests)
7
- [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/polyphony/blob/master/LICENSE)
12
+ <p align="center">
13
+ <a href="http://rubygems.org/gems/polyphony">
14
+ <img src="https://badge.fury.io/rb/polyphony.svg" alt="Ruby gem">
15
+ </a>
16
+ <a href="https://github.com/digital-fabric/polyphony/actions?query=workflow%3ATests">
17
+ <img src="https://github.com/digital-fabric/polyphony/workflows/Tests/badge.svg" alt="Tests">
18
+ </a>
19
+ <a href="https://github.com/digital-fabric/polyphony/blob/master/LICENSE">
20
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
21
+ </a>
22
+ </p>
8
23
 
9
24
  [DOCS](https://digital-fabric.github.io/polyphony/) |
10
25
  [EXAMPLES](examples)
data/TODO.md CHANGED
@@ -1,5 +1,5 @@
1
1
  - Implement `LibevAgent#connect` API
2
- - Reimplement ResourcePool, Channel, Mutex using Queue
2
+
3
3
  -- Add `Fiber#schedule_with_priority` method, aliased by `Fiber#wakeup`
4
4
  - Implement agent interface is virtual function table
5
5
  - Implement proxy agent for plugging in a user-provided agent class
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ `rake recompile`
5
+
6
+ count = ARGV[0] ? ARGV[0].to_i : 100
7
+
8
+ TEST_CMD = 'ruby test/run.rb'
9
+
10
+ def run_test(count)
11
+ puts "#{count}: running tests..."
12
+ system(TEST_CMD)
13
+ return if $?.exitstatus == 0
14
+
15
+ puts "Failure after #{count} tests"
16
+ exit!
17
+ end
18
+
19
+ trap('INT') { exit! }
20
+ t0 = Time.now
21
+ count.times { |i| run_test(i + 1) }
22
+ elapsed = Time.now - t0
23
+ puts format(
24
+ "Successfully ran %d tests in %f seconds (%f per test)",
25
+ count,
26
+ elapsed,
27
+ elapsed / count
28
+ )
@@ -2,10 +2,12 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
+ require 'polyphony/core/channel'
5
6
 
6
7
  def echo(cin, cout)
7
8
  puts 'start echoer'
8
9
  while (msg = cin.receive)
10
+ puts "echoer received #{msg}"
9
11
  cout << "you said: #{msg}"
10
12
  end
11
13
  ensure
@@ -20,7 +22,7 @@ spin do
20
22
  puts 'start receiver'
21
23
  while (msg = chan2.receive)
22
24
  puts msg
23
- $main.resume if msg =~ /world/
25
+ $main.schedule if msg =~ /world/
24
26
  end
25
27
  ensure
26
28
  puts 'receiver stopped'
@@ -42,4 +44,4 @@ $main = spin do
42
44
  puts "done #{Time.now - t0}"
43
45
  end
44
46
 
45
- suspend
47
+ $main.await
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
+ require 'polyphony/core/sync'
5
6
 
6
7
  def loop_it(number, lock)
7
8
  loop do
@@ -13,7 +14,7 @@ def loop_it(number, lock)
13
14
  end
14
15
  end
15
16
 
16
- lock = Polyphony::Sync::Mutex.new
17
+ lock = Polyphony::Mutex.new
17
18
  spin { loop_it(1, lock) }
18
19
  spin { loop_it(2, lock) }
19
20
  spin { loop_it(3, lock) }
@@ -0,0 +1,86 @@
1
+ #include "polyphony.h"
2
+ #include "ring_buffer.h"
3
+
4
+ typedef struct event {
5
+ VALUE waiting_fiber;
6
+ } Event_t;
7
+
8
+ VALUE cEvent = Qnil;
9
+
10
+ static void Event_mark(void *ptr) {
11
+ Event_t *event = ptr;
12
+ rb_gc_mark(event->waiting_fiber);
13
+ }
14
+
15
+ static void Event_free(void *ptr) {
16
+ xfree(ptr);
17
+ }
18
+
19
+ static size_t Event_size(const void *ptr) {
20
+ return sizeof(Event_t);
21
+ }
22
+
23
+ static const rb_data_type_t Event_type = {
24
+ "Event",
25
+ {Event_mark, Event_free, Event_size,},
26
+ 0, 0, 0
27
+ };
28
+
29
+ static VALUE Event_allocate(VALUE klass) {
30
+ Event_t *event;
31
+
32
+ event = ALLOC(Event_t);
33
+ return TypedData_Wrap_Struct(klass, &Event_type, event);
34
+ }
35
+
36
+ #define GetEvent(obj, event) \
37
+ TypedData_Get_Struct((obj), Event_t, &Event_type, (event))
38
+
39
+ static VALUE Event_initialize(VALUE self) {
40
+ Event_t *event;
41
+ GetEvent(self, event);
42
+
43
+ event->waiting_fiber = Qnil;
44
+
45
+ return self;
46
+ }
47
+
48
+ VALUE Event_signal(int argc, VALUE *argv, VALUE self) {
49
+ VALUE value = argc > 0 ? argv[0] : Qnil;
50
+ Event_t *event;
51
+ GetEvent(self, event);
52
+
53
+ if (event->waiting_fiber != Qnil) {
54
+ Fiber_make_runnable(event->waiting_fiber, value);
55
+ event->waiting_fiber = Qnil;
56
+ }
57
+ return self;
58
+ }
59
+
60
+ VALUE Event_await(VALUE self) {
61
+ Event_t *event;
62
+ GetEvent(self, event);
63
+
64
+ if (event->waiting_fiber != Qnil)
65
+ rb_raise(rb_eRuntimeError, "Event is already awaited by another fiber");
66
+
67
+ VALUE agent = rb_ivar_get(rb_thread_current(), ID_ivar_agent);
68
+ event->waiting_fiber = rb_fiber_current();
69
+ VALUE switchpoint_result = LibevAgent_wait_event(agent, Qnil);
70
+ event->waiting_fiber = Qnil;
71
+
72
+ TEST_RESUME_EXCEPTION(switchpoint_result);
73
+ RB_GC_GUARD(agent);
74
+ RB_GC_GUARD(switchpoint_result);
75
+
76
+ return switchpoint_result;
77
+ }
78
+
79
+ void Init_Event() {
80
+ cEvent = rb_define_class_under(mPolyphony, "Event", rb_cData);
81
+ rb_define_alloc_func(cEvent, Event_allocate);
82
+
83
+ rb_define_method(cEvent, "initialize", Event_initialize, 0);
84
+ rb_define_method(cEvent, "await", Event_await, 0);
85
+ rb_define_method(cEvent, "signal", Event_signal, -1);
86
+ }
@@ -9,8 +9,6 @@ ID ID_trace_runnable;
9
9
  ID ID_trace_terminate;
10
10
  ID ID_trace_wait;
11
11
 
12
- VALUE cEvent = Qnil;
13
-
14
12
  VALUE SYM_dead;
15
13
  VALUE SYM_running;
16
14
  VALUE SYM_runnable;
@@ -35,9 +33,6 @@ static VALUE Fiber_safe_transfer(int argc, VALUE *argv, VALUE self) {
35
33
 
36
34
  inline VALUE Fiber_auto_watcher(VALUE self) {
37
35
  VALUE watcher;
38
- if (cEvent == Qnil) {
39
- cEvent = rb_const_get(mPolyphony, rb_intern("Event"));
40
- }
41
36
 
42
37
  watcher = rb_ivar_get(self, ID_ivar_auto_watcher);
43
38
  if (watcher == Qnil) {
@@ -4,6 +4,7 @@ void Init_Fiber();
4
4
  void Init_Polyphony();
5
5
  void Init_LibevAgent();
6
6
  void Init_Queue();
7
+ void Init_Event();
7
8
  void Init_Thread();
8
9
  void Init_Tracing();
9
10
 
@@ -13,6 +14,7 @@ void Init_polyphony_ext() {
13
14
  Init_Polyphony();
14
15
  Init_LibevAgent();
15
16
  Init_Queue();
17
+ Init_Event();
16
18
 
17
19
  Init_Fiber();
18
20
  Init_Thread();
@@ -139,6 +139,18 @@ VALUE Queue_shift_all(VALUE self) {
139
139
  return ring_buffer_shift_all(&queue->values);
140
140
  }
141
141
 
142
+ VALUE Queue_flush_waiters(VALUE self, VALUE value) {
143
+ Queue_t *queue;
144
+ GetQueue(self, queue);
145
+
146
+ while(1) {
147
+ VALUE fiber = ring_buffer_shift(&queue->shift_queue);
148
+ if (fiber == Qnil) return self;
149
+
150
+ Fiber_make_runnable(fiber, value);
151
+ }
152
+ }
153
+
142
154
  VALUE Queue_empty_p(VALUE self) {
143
155
  Queue_t *queue;
144
156
  GetQueue(self, queue);
@@ -146,6 +158,13 @@ VALUE Queue_empty_p(VALUE self) {
146
158
  return (queue->values.count == 0) ? Qtrue : Qfalse;
147
159
  }
148
160
 
161
+ VALUE Queue_size_m(VALUE self) {
162
+ Queue_t *queue;
163
+ GetQueue(self, queue);
164
+
165
+ return INT2NUM(queue->values.count);
166
+ }
167
+
149
168
  void Init_Queue() {
150
169
  cQueue = rb_define_class_under(mPolyphony, "Queue", rb_cData);
151
170
  rb_define_alloc_func(cQueue, Queue_allocate);
@@ -162,7 +181,7 @@ void Init_Queue() {
162
181
 
163
182
  rb_define_method(cQueue, "shift_each", Queue_shift_each, 0);
164
183
  rb_define_method(cQueue, "shift_all", Queue_shift_all, 0);
184
+ rb_define_method(cQueue, "flush_waiters", Queue_flush_waiters, 1);
165
185
  rb_define_method(cQueue, "empty?", Queue_empty_p, 0);
186
+ rb_define_method(cQueue, "size", Queue_size_m, 0);
166
187
  }
167
-
168
-
@@ -53,11 +53,9 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
53
53
  if (rb_fiber_alive_p(fiber) != Qtrue) return self;
54
54
 
55
55
  FIBER_TRACE(3, SYM_fiber_schedule, fiber, value);
56
- // if fiber is already scheduled, just set the scheduled value, then return
57
56
  rb_ivar_set(fiber, ID_runnable_value, value);
58
- if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
59
- return self;
60
- }
57
+ // if fiber is already scheduled, just set the scheduled value, then return
58
+ if (rb_ivar_get(fiber, ID_runnable) != Qnil) return self;
61
59
 
62
60
  queue = rb_ivar_get(self, ID_run_queue);
63
61
  Queue_push(queue, fiber);
@@ -23,7 +23,6 @@ require_relative './polyphony/core/global_api'
23
23
  require_relative './polyphony/core/resource_pool'
24
24
  require_relative './polyphony/net'
25
25
  require_relative './polyphony/adapters/process'
26
- require_relative './polyphony/event'
27
26
 
28
27
  # Main Polyphony API
29
28
  module Polyphony
@@ -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
@@ -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.delete fiber
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.43.8'
4
+ VERSION = '0.43.9'
5
5
  end
@@ -34,6 +34,7 @@ class EventTest < MiniTest::Test
34
34
  }
35
35
  }
36
36
  snooze
37
+
37
38
  t = Thread.new do
38
39
  orig_sleep 0.001
39
40
  3.times { a.signal }
@@ -109,4 +109,24 @@ class QueueTest < MiniTest::Test
109
109
  @queue << :foo
110
110
  assert_nil f1.await
111
111
  end
112
+
113
+ def test_queue_size
114
+ assert_equal 0, @queue.size
115
+
116
+ @queue.push 1
117
+
118
+ assert_equal 1, @queue.size
119
+
120
+ @queue.push 2
121
+
122
+ assert_equal 2, @queue.size
123
+
124
+ @queue.shift
125
+
126
+ assert_equal 1, @queue.size
127
+
128
+ @queue.shift
129
+
130
+ assert_equal 0, @queue.size
131
+ end
112
132
  end
@@ -37,49 +37,6 @@ class ResourcePoolTest < MiniTest::Test
37
37
  assert_equal 2, pool.size
38
38
  end
39
39
 
40
- def test_discard
41
- resources = [+'a', +'b']
42
- pool = Polyphony::ResourcePool.new(limit: 2) { resources.shift }
43
-
44
- results = []
45
- 4.times {
46
- spin {
47
- snooze
48
- pool.acquire { |resource|
49
- results << resource
50
- resource.__discard__ if resource == 'b'
51
- snooze
52
- }
53
- }
54
- }
55
- 6.times { snooze }
56
-
57
- assert_equal ['a', 'b', 'a', 'a'], results
58
- assert_equal 1, pool.size
59
- end
60
-
61
- def test_add
62
- resources = [+'a', +'b']
63
- pool = Polyphony::ResourcePool.new(limit: 2) { resources.shift }
64
-
65
- pool << +'c'
66
-
67
- results = []
68
- 4.times {
69
- spin {
70
- snooze
71
- pool.acquire { |resource|
72
- results << resource
73
- resource.__discard__ if resource == 'b'
74
- snooze
75
- }
76
- }
77
- }
78
- 6.times { snooze }
79
-
80
- assert_equal ['c', 'a', 'c', 'a'], results
81
- end
82
-
83
40
  def test_single_resource_limit
84
41
  resources = [+'a', +'b']
85
42
  pool = Polyphony::ResourcePool.new(limit: 1) { resources.shift }
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.43.8
4
+ version: 0.43.9
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-07-21 00:00:00.000000000 Z
11
+ date: 2020-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -241,6 +241,7 @@ files:
241
241
  - Rakefile
242
242
  - TODO.md
243
243
  - bin/polyphony-debug
244
+ - bin/stress.rb
244
245
  - docs/_config.yml
245
246
  - docs/_includes/head.html
246
247
  - docs/_includes/title.html
@@ -388,6 +389,7 @@ files:
388
389
  - ext/libev/ev_win32.c
389
390
  - ext/libev/ev_wrap.h
390
391
  - ext/libev/test_libev_win32.c
392
+ - ext/polyphony/event.c
391
393
  - ext/polyphony/extconf.rb
392
394
  - ext/polyphony/fiber.c
393
395
  - ext/polyphony/libev.c
@@ -415,7 +417,6 @@ files:
415
417
  - lib/polyphony/core/sync.rb
416
418
  - lib/polyphony/core/thread_pool.rb
417
419
  - lib/polyphony/core/throttler.rb
418
- - lib/polyphony/event.rb
419
420
  - lib/polyphony/extensions/core.rb
420
421
  - lib/polyphony/extensions/fiber.rb
421
422
  - lib/polyphony/extensions/io.rb
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Polyphony
4
- # Event watcher for thread-safe synchronisation
5
- class Event
6
- def await
7
- @fiber = Fiber.current
8
- Thread.current.agent.wait_event(true)
9
- end
10
-
11
- def signal(value = nil)
12
- @fiber&.schedule(value)
13
- ensure
14
- @fiber = nil
15
- end
16
- end
17
- end