polyphony 0.66 → 0.67

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8e2aa3d011286dda93222164cbf66f1ec4c18b9c212ee7a70dbebd4bc523943
4
- data.tar.gz: 99b5fb95c682dfad8df33f07d98bfab7f2109b9d4ec0e24720869c2d331f23fb
3
+ metadata.gz: 6b8553a2f84e79520ce53412ee6891873c9a9d25e0789e82b26a7153d3d7ae67
4
+ data.tar.gz: 6889ef71f11d79d6fbb82adc77447f9b3526a29858f2aa3c9e51ee9098add2c7
5
5
  SHA512:
6
- metadata.gz: 27c65b6e340d1aad41d38b4b322b98d282f96fe6d136c897ab8de14651b2da3cfc392650600d63983f76b5fb1dc70e4e49116efb289a790a509bec63caa9b9e4
7
- data.tar.gz: 94e62ac7fbcb6d085af7475d987eca45be191aaf7654c3bacb07c4f7793d450281d3da1b08b12636a49398e97c1043c3a2cd628018925bdc082e02a0859743bc
6
+ metadata.gz: 91814f767f74c673681a2215f82a302f3aab5ced6ae7197720e474b8c1ad6846eb10001580c1f11d5b62bd0ed7563422fd71f0fb5728eb0b3fcb4ebd36d2038c
7
+ data.tar.gz: 74e1c7ed34b4f212ca1ffd5881da3e9f689c3934d0f3680da2396d7444b5a214a610ccc779c1ef59713116f0a8240491dc60f32b5d5e30de5092ddc41b8c9f18
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.67 2021-08-06
2
+
3
+ - Improve fiber monitoring
4
+ - Add fiber parking (a parked fiber is prevented from running). This is in
5
+ preparation for the upcoming work on an integrated debugger.
6
+
1
7
  ## 0.66 2021-08-01
2
8
 
3
9
  - Fix all splicing APIs on non-linux OSes (#63)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.66)
4
+ polyphony (0.67)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/TODO.md CHANGED
@@ -1,3 +1,13 @@
1
+ - io_uring backend:
2
+ - if `io_uring_get_sqe` returns null, call `io_uring_submit`, (snooze fiber)?
3
+ and try again
4
+
5
+ - Tracing:
6
+ - Emit events on I/O ops, e.g.:
7
+ - [:op_read_submit, id, io, len]
8
+ - [:op_read_complete, id, io, len, buffer]
9
+ - Prevent tracing while an event is being emitted (to allow the trace proc to perform I/O)
10
+
1
11
  - Add support for IPv6:
2
12
  https://www.reddit.com/r/ruby/comments/lyen23/understanding_ipv6_and_why_its_important_to_you/
3
13
 
@@ -14,47 +24,8 @@
14
24
  - `IO#gets_loop`, `Socket#gets_loop`, `OpenSSL::Socket#gets_loop` (medium effort)
15
25
  - `Fiber#receive_loop` (very little effort, should be implemented in C)
16
26
 
17
-
18
- - Add `Backend#splice`, `Backend#splice_to_eof` for implementing stuff like proxying:
19
-
20
- ```ruby
21
- def two_way_proxy(socket1, socket2)
22
- backend = Thread.current.backend
23
- f1 = spin { backend.splice_to_eof(socket1, socket2) }
24
- f2 = spin { backend.splice_to_eof(socket2, socket1) }
25
- Fiber.await(f1, f2)
26
- end
27
- ```
28
-
29
27
  - Add support for `close` to io_uring backend
30
28
 
31
- - Add support for submission of multiple requests to io_uring backend:
32
-
33
- ```ruby
34
- Thread.current.backend.submit(
35
- [:send, sock, chunk_header(len)],
36
- [:splice, file, sock, len]
37
- )
38
- ```
39
-
40
- Full example (for writing chunks from a file to an HTTP response):
41
-
42
- ```ruby
43
- def serve_io(io)
44
- i, o = IO.pipe
45
- backend = Thread.current.backend
46
- while true
47
- len = o.splice(io, 8192)
48
- break if len == 0
49
-
50
- backend.submit(
51
- [:write, sock, chunk_header(len)],
52
- [:splice, i, sock, len]
53
- )
54
- end
55
- end
56
- ```
57
-
58
29
  - Graceful shutdown again:
59
30
  - What happens to children when doing a graceful shutdown?
60
31
  - What are the implications of passing graceful shutdown flag to children?
@@ -92,7 +63,6 @@
92
63
 
93
64
  -----------------------------------------------------
94
65
 
95
- - Add `Backend#splice(in, out, nbytes)` API
96
66
  - Adapter for io/console (what does `IO#raw` do?)
97
67
  - Adapter for Pry and IRB (Which fixes #5 and #6)
98
68
  - allow backend selection at runtime
data/bin/pdbg ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'polyphony'
6
+
7
+ UNIX_SOCKET_PATH = '/tmp/pdbg.sock'
8
+
9
+ cmd = ARGV.join(' ')
10
+ injected_lib_path = File.expand_path('../lib/polyphony/debugger/server_inject.rb', __dir__)
11
+ p cmd
12
+ pid = fork { exec("env POLYPHONY_DEBUG_SOCKET_PATH=#{UNIX_SOCKET_PATH} ruby #{cmd}") }
13
+ puts "Started debugged process (#{pid})"
14
+
15
+ sleep 3
16
+ socket = UNIXSocket.new(UNIX_SOCKET_PATH)
17
+ socket.puts 'pdbg'
18
+ response = socket.gets
19
+ if response.chomp == 'pdbg'
20
+ puts 'Connected to process'
21
+ end
22
+ loop do
23
+ status = socket.gets
24
+ puts status
25
+
26
+ STDOUT << "> "
27
+ cmd = STDIN.gets
28
+ puts '-' * 40
29
+ socket.puts cmd
30
+ end
@@ -2,7 +2,11 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
+ require 'polyphony/extensions/debug'
5
6
 
7
+ Exception.__disable_sanitized_backtrace__ = true
8
+
9
+ puts '----- start await example ------'
6
10
  sleeper = spin do
7
11
  puts 'going to sleep'
8
12
  sleep 1
@@ -17,4 +21,8 @@ waiter = spin do
17
21
  puts 'done waiting'
18
22
  end
19
23
 
20
- waiter.await
24
+ trace :before_await
25
+
26
+ sleep 2
27
+ waiter.await
28
+ trace :after_await
@@ -7,6 +7,7 @@
7
7
 
8
8
  inline void backend_base_initialize(struct Backend_base *base) {
9
9
  runqueue_initialize(&base->runqueue);
10
+ runqueue_initialize(&base->parked_runqueue);
10
11
  base->currently_polling = 0;
11
12
  base->op_count = 0;
12
13
  base->switch_count = 0;
@@ -20,12 +21,14 @@ inline void backend_base_initialize(struct Backend_base *base) {
20
21
 
21
22
  inline void backend_base_finalize(struct Backend_base *base) {
22
23
  runqueue_finalize(&base->runqueue);
24
+ runqueue_finalize(&base->parked_runqueue);
23
25
  }
24
26
 
25
27
  inline void backend_base_mark(struct Backend_base *base) {
26
28
  if (base->idle_proc != Qnil) rb_gc_mark(base->idle_proc);
27
29
  if (base->trace_proc != Qnil) rb_gc_mark(base->trace_proc);
28
30
  runqueue_mark(&base->runqueue);
31
+ runqueue_mark(&base->parked_runqueue);
29
32
  }
30
33
 
31
34
  const unsigned int ANTI_STARVE_SWITCH_COUNT_THRESHOLD = 64;
@@ -91,7 +94,10 @@ void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_bas
91
94
 
92
95
  COND_TRACE(base, 4, SYM_fiber_schedule, fiber, value, prioritize ? Qtrue : Qfalse);
93
96
 
94
- (prioritize ? runqueue_unshift : runqueue_push)(&base->runqueue, fiber, value, already_runnable);
97
+ runqueue_t *runqueue = rb_ivar_get(fiber, ID_ivar_parked) == Qtrue ?
98
+ &base->parked_runqueue : &base->runqueue;
99
+
100
+ (prioritize ? runqueue_unshift : runqueue_push)(runqueue, fiber, value, already_runnable);
95
101
  if (!already_runnable) {
96
102
  rb_ivar_set(fiber, ID_ivar_runnable, Qtrue);
97
103
  if (rb_thread_current() != thread) {
@@ -105,6 +111,13 @@ void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_bas
105
111
  }
106
112
  }
107
113
 
114
+ inline void backend_base_park_fiber(struct Backend_base *base, VALUE fiber) {
115
+ runqueue_migrate(&base->runqueue, &base->parked_runqueue, fiber);
116
+ }
117
+
118
+ inline void backend_base_unpark_fiber(struct Backend_base *base, VALUE fiber) {
119
+ runqueue_migrate(&base->parked_runqueue, &base->runqueue, fiber);
120
+ }
108
121
 
109
122
  inline void backend_trace(struct Backend_base *base, int argc, VALUE *argv) {
110
123
  if (base->trace_proc == Qnil) return;
@@ -17,6 +17,7 @@ struct backend_stats {
17
17
 
18
18
  struct Backend_base {
19
19
  runqueue_t runqueue;
20
+ runqueue_t parked_runqueue;
20
21
  unsigned int currently_polling;
21
22
  unsigned int op_count;
22
23
  unsigned int switch_count;
@@ -33,6 +34,8 @@ void backend_base_finalize(struct Backend_base *base);
33
34
  void backend_base_mark(struct Backend_base *base);
34
35
  VALUE backend_base_switch_fiber(VALUE backend, struct Backend_base *base);
35
36
  void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_base *base, VALUE fiber, VALUE value, int prioritize);
37
+ void backend_base_park_fiber(struct Backend_base *base, VALUE fiber);
38
+ void backend_base_unpark_fiber(struct Backend_base *base, VALUE fiber);
36
39
  void backend_trace(struct Backend_base *base, int argc, VALUE *argv);
37
40
  struct backend_stats backend_base_stats(struct Backend_base *base);
38
41
 
@@ -104,7 +107,6 @@ VALUE Backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags);
104
107
  VALUE Backend_stats(VALUE self);
105
108
  void backend_run_idle_tasks(struct Backend_base *base);
106
109
  void io_verify_blocking_mode(rb_io_t *fptr, VALUE io, VALUE blocking);
107
-
108
110
  void backend_setup_stats_symbols();
109
111
 
110
112
  #endif /* BACKEND_COMMON_H */
@@ -229,7 +229,7 @@ inline void Backend_unschedule_fiber(VALUE self, VALUE fiber) {
229
229
  Backend_t *backend;
230
230
  GetBackend(self, backend);
231
231
 
232
- runqueue_delete(&backend->base.runqueue, fiber);
232
+ runqueue_delete(&backend->base.runqueue, fiber);
233
233
  }
234
234
 
235
235
  inline VALUE Backend_switch_fiber(VALUE self) {
@@ -1474,6 +1474,20 @@ VALUE Backend_trace_proc_set(VALUE self, VALUE block) {
1474
1474
  return self;
1475
1475
  }
1476
1476
 
1477
+ void Backend_park_fiber(VALUE self, VALUE fiber) {
1478
+ Backend_t *backend;
1479
+ GetBackend(self, backend);
1480
+
1481
+ backend_base_park_fiber(&backend->base, fiber);
1482
+ }
1483
+
1484
+ void Backend_unpark_fiber(VALUE self, VALUE fiber) {
1485
+ Backend_t *backend;
1486
+ GetBackend(self, backend);
1487
+
1488
+ backend_base_unpark_fiber(&backend->base, fiber);
1489
+ }
1490
+
1477
1491
  void Init_Backend() {
1478
1492
  VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1479
1493
  rb_define_alloc_func(cBackend, Backend_allocate);
@@ -1553,6 +1553,20 @@ VALUE Backend_trace_proc_set(VALUE self, VALUE block) {
1553
1553
  return self;
1554
1554
  }
1555
1555
 
1556
+ void Backend_park_fiber(VALUE self, VALUE fiber) {
1557
+ Backend_t *backend;
1558
+ GetBackend(self, backend);
1559
+
1560
+ backend_base_park_fiber(&backend->base, fiber);
1561
+ }
1562
+
1563
+ void Backend_unpark_fiber(VALUE self, VALUE fiber) {
1564
+ Backend_t *backend;
1565
+ GetBackend(self, backend);
1566
+
1567
+ backend_base_unpark_fiber(&backend->base, fiber);
1568
+ }
1569
+
1556
1570
  void Init_Backend() {
1557
1571
  ev_set_allocator(xrealloc);
1558
1572
 
@@ -114,6 +114,22 @@ VALUE Fiber_receive_all_pending(VALUE self) {
114
114
  return (mailbox == Qnil) ? rb_ary_new() : Queue_shift_all(mailbox);
115
115
  }
116
116
 
117
+ VALUE Fiber_park(VALUE self) {
118
+ rb_ivar_set(self, ID_ivar_parked, Qtrue);
119
+ Backend_park_fiber(BACKEND(), self);
120
+ return self;
121
+ }
122
+
123
+ VALUE Fiber_unpark(VALUE self) {
124
+ rb_ivar_set(self, ID_ivar_parked, Qnil);
125
+ Backend_unpark_fiber(BACKEND(), self);
126
+ return self;
127
+ }
128
+
129
+ VALUE Fiber_parked_p(VALUE self) {
130
+ return rb_ivar_get(self, ID_ivar_parked);
131
+ }
132
+
117
133
  void Init_Fiber() {
118
134
  VALUE cFiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
119
135
  rb_define_method(cFiber, "safe_transfer", Fiber_safe_transfer, -1);
@@ -128,6 +144,10 @@ void Init_Fiber() {
128
144
  rb_define_method(cFiber, "receive_all_pending", Fiber_receive_all_pending, 0);
129
145
  rb_define_method(cFiber, "mailbox", Fiber_mailbox, 0);
130
146
 
147
+ rb_define_method(cFiber, "__park__", Fiber_park, 0);
148
+ rb_define_method(cFiber, "__unpark__", Fiber_unpark, 0);
149
+ rb_define_method(cFiber, "__parked__?", Fiber_parked_p, 0);
150
+
131
151
  SYM_dead = ID2SYM(rb_intern("dead"));
132
152
  SYM_running = ID2SYM(rb_intern("running"));
133
153
  SYM_runnable = ID2SYM(rb_intern("runnable"));
@@ -12,6 +12,7 @@ ID ID_invoke;
12
12
  ID ID_new;
13
13
  ID ID_ivar_blocking_mode;
14
14
  ID ID_ivar_io;
15
+ ID ID_ivar_parked;
15
16
  ID ID_ivar_runnable;
16
17
  ID ID_ivar_running;
17
18
  ID ID_ivar_thread;
@@ -160,6 +161,7 @@ void Init_Polyphony() {
160
161
  ID_invoke = rb_intern("invoke");
161
162
  ID_ivar_blocking_mode = rb_intern("@blocking_mode");
162
163
  ID_ivar_io = rb_intern("@io");
164
+ ID_ivar_parked = rb_intern("@parked");
163
165
  ID_ivar_runnable = rb_intern("@runnable");
164
166
  ID_ivar_running = rb_intern("@running");
165
167
  ID_ivar_thread = rb_intern("@thread");
@@ -44,6 +44,7 @@ extern ID ID_invoke;
44
44
  extern ID ID_ivar_backend;
45
45
  extern ID ID_ivar_blocking_mode;
46
46
  extern ID ID_ivar_io;
47
+ extern ID ID_ivar_parked;
47
48
  extern ID ID_ivar_runnable;
48
49
  extern ID ID_ivar_running;
49
50
  extern ID ID_ivar_thread;
@@ -115,9 +116,11 @@ VALUE Backend_run_idle_tasks(VALUE self);
115
116
  VALUE Backend_switch_fiber(VALUE self);
116
117
  void Backend_schedule_fiber(VALUE thread, VALUE self, VALUE fiber, VALUE value, int prioritize);
117
118
  void Backend_unschedule_fiber(VALUE self, VALUE fiber);
119
+ void Backend_park_fiber(VALUE self, VALUE fiber);
120
+ void Backend_unpark_fiber(VALUE self, VALUE fiber);
118
121
 
119
- VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
120
- VALUE Thread_schedule_fiber_with_priority(VALUE thread, VALUE fiber, VALUE value);
122
+ void Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
123
+ void Thread_schedule_fiber_with_priority(VALUE thread, VALUE fiber, VALUE value);
121
124
  VALUE Thread_switch_fiber(VALUE thread);
122
125
 
123
126
  VALUE Polyphony_snooze(VALUE self);
@@ -40,6 +40,10 @@ inline int runqueue_index_of(runqueue_t *runqueue, VALUE fiber) {
40
40
  return runqueue_ring_buffer_index_of(&runqueue->entries, fiber);
41
41
  }
42
42
 
43
+ inline void runqueue_migrate(runqueue_t *src, runqueue_t *dest, VALUE fiber) {
44
+ runqueue_ring_buffer_migrate(&src->entries, &dest->entries, fiber);
45
+ }
46
+
43
47
  inline void runqueue_clear(runqueue_t *runqueue) {
44
48
  runqueue_ring_buffer_clear(&runqueue->entries);
45
49
  }
@@ -18,6 +18,7 @@ void runqueue_unshift(runqueue_t *runqueue, VALUE fiber, VALUE value, int resche
18
18
  runqueue_entry runqueue_shift(runqueue_t *runqueue);
19
19
  void runqueue_delete(runqueue_t *runqueue, VALUE fiber);
20
20
  int runqueue_index_of(runqueue_t *runqueue, VALUE fiber);
21
+ void runqueue_migrate(runqueue_t *src, runqueue_t *dest, VALUE fiber);
21
22
  void runqueue_clear(runqueue_t *runqueue);
22
23
  unsigned int runqueue_size(runqueue_t *runqueue);
23
24
  unsigned int runqueue_len(runqueue_t *runqueue);
@@ -1,7 +1,7 @@
1
1
  #include "polyphony.h"
2
2
  #include "runqueue_ring_buffer.h"
3
3
 
4
- void runqueue_ring_buffer_init(runqueue_ring_buffer *buffer) {
4
+ inline void runqueue_ring_buffer_init(runqueue_ring_buffer *buffer) {
5
5
  buffer->size = 1;
6
6
  buffer->count = 0;
7
7
  buffer->entries = malloc(buffer->size * sizeof(runqueue_entry));
@@ -9,17 +9,21 @@ void runqueue_ring_buffer_init(runqueue_ring_buffer *buffer) {
9
9
  buffer->tail = 0;
10
10
  }
11
11
 
12
- void runqueue_ring_buffer_free(runqueue_ring_buffer *buffer) {
12
+ inline void runqueue_ring_buffer_free(runqueue_ring_buffer *buffer) {
13
13
  free(buffer->entries);
14
14
  }
15
15
 
16
- int runqueue_ring_buffer_empty_p(runqueue_ring_buffer *buffer) {
16
+ inline int runqueue_ring_buffer_empty_p(runqueue_ring_buffer *buffer) {
17
17
  return buffer->count == 0;
18
18
  }
19
19
 
20
+ inline void runqueue_ring_buffer_clear(runqueue_ring_buffer *buffer) {
21
+ buffer->count = buffer->head = buffer->tail = 0;
22
+ }
23
+
20
24
  static runqueue_entry nil_runqueue_entry = {(Qnil), (Qnil)};
21
25
 
22
- runqueue_entry runqueue_ring_buffer_shift(runqueue_ring_buffer *buffer) {
26
+ inline runqueue_entry runqueue_ring_buffer_shift(runqueue_ring_buffer *buffer) {
23
27
  if (buffer->count == 0) return nil_runqueue_entry;
24
28
 
25
29
  runqueue_entry value = buffer->entries[buffer->head];
@@ -28,7 +32,7 @@ runqueue_entry runqueue_ring_buffer_shift(runqueue_ring_buffer *buffer) {
28
32
  return value;
29
33
  }
30
34
 
31
- void runqueue_ring_buffer_resize(runqueue_ring_buffer *buffer) {
35
+ inline void runqueue_ring_buffer_resize(runqueue_ring_buffer *buffer) {
32
36
  unsigned int old_size = buffer->size;
33
37
  buffer->size = old_size == 1 ? 4 : old_size * 2;
34
38
  buffer->entries = realloc(buffer->entries, buffer->size * sizeof(runqueue_entry));
@@ -37,7 +41,7 @@ void runqueue_ring_buffer_resize(runqueue_ring_buffer *buffer) {
37
41
  buffer->tail = buffer->head + buffer->count;
38
42
  }
39
43
 
40
- void runqueue_ring_buffer_unshift(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value) {
44
+ inline void runqueue_ring_buffer_unshift(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value) {
41
45
  if (buffer->count == buffer->size) runqueue_ring_buffer_resize(buffer);
42
46
 
43
47
  buffer->head = (buffer->head - 1) % buffer->size;
@@ -46,7 +50,7 @@ void runqueue_ring_buffer_unshift(runqueue_ring_buffer *buffer, VALUE fiber, VAL
46
50
  buffer->count++;
47
51
  }
48
52
 
49
- void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value) {
53
+ inline void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value) {
50
54
  if (buffer->count == buffer->size) runqueue_ring_buffer_resize(buffer);
51
55
 
52
56
  buffer->entries[buffer->tail].fiber = fiber;
@@ -55,14 +59,14 @@ void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE
55
59
  buffer->count++;
56
60
  }
57
61
 
58
- void runqueue_ring_buffer_mark(runqueue_ring_buffer *buffer) {
62
+ inline void runqueue_ring_buffer_mark(runqueue_ring_buffer *buffer) {
59
63
  for (unsigned int i = 0; i < buffer->count; i++) {
60
64
  rb_gc_mark(buffer->entries[(buffer->head + i) % buffer->size].fiber);
61
65
  rb_gc_mark(buffer->entries[(buffer->head + i) % buffer->size].value);
62
66
  }
63
67
  }
64
68
 
65
- void runqueue_ring_buffer_delete_at(runqueue_ring_buffer *buffer, unsigned int idx) {
69
+ inline void runqueue_ring_buffer_delete_at(runqueue_ring_buffer *buffer, unsigned int idx) {
66
70
  for (unsigned int idx2 = idx; idx2 != buffer->tail; idx2 = (idx2 + 1) % buffer->size) {
67
71
  buffer->entries[idx2] = buffer->entries[(idx2 + 1) % buffer->size];
68
72
  }
@@ -70,7 +74,7 @@ void runqueue_ring_buffer_delete_at(runqueue_ring_buffer *buffer, unsigned int i
70
74
  buffer->tail = (buffer->tail - 1) % buffer->size;
71
75
  }
72
76
 
73
- void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber) {
77
+ inline void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber) {
74
78
  for (unsigned int i = 0; i < buffer->count; i++) {
75
79
  unsigned int idx = (buffer->head + i) % buffer->size;
76
80
  if (buffer->entries[idx].fiber == fiber) {
@@ -80,7 +84,7 @@ void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber) {
80
84
  }
81
85
  }
82
86
 
83
- int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber) {
87
+ inline int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber) {
84
88
  for (unsigned int i = 0; i < buffer->count; i++) {
85
89
  unsigned int idx = (buffer->head + i) % buffer->size;
86
90
  if (buffer->entries[idx].fiber == fiber)
@@ -89,6 +93,13 @@ int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber) {
89
93
  return -1;
90
94
  }
91
95
 
92
- void runqueue_ring_buffer_clear(runqueue_ring_buffer *buffer) {
93
- buffer->count = buffer->head = buffer->tail = 0;
94
- }
96
+ inline void runqueue_ring_buffer_migrate(runqueue_ring_buffer *src, runqueue_ring_buffer *dest, VALUE fiber) {
97
+ for (unsigned int i = 0; i < src->count; i++) {
98
+ unsigned int idx = (src->head + i) % src->size;
99
+ if (src->entries[idx].fiber == fiber) {
100
+ runqueue_ring_buffer_push(dest, src->entries[idx].fiber, src->entries[idx].value);
101
+ runqueue_ring_buffer_delete_at(src, idx);
102
+ return;
103
+ }
104
+ }
105
+ }
@@ -29,4 +29,6 @@ void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE
29
29
  void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber);
30
30
  int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber);
31
31
 
32
+ void runqueue_ring_buffer_migrate(runqueue_ring_buffer *src, runqueue_ring_buffer *dest, VALUE fiber);
33
+
32
34
  #endif /* RUNQUEUE_RING_BUFFER_H */
@@ -22,14 +22,12 @@ VALUE Thread_fiber_unschedule(VALUE self, VALUE fiber) {
22
22
  return self;
23
23
  }
24
24
 
25
- VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
25
+ inline void Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
26
26
  schedule_fiber(self, fiber, value, 0);
27
- return self;
28
27
  }
29
28
 
30
- VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value) {
29
+ inline void Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value) {
31
30
  schedule_fiber(self, fiber, value, 1);
32
- return self;
33
31
  }
34
32
 
35
33
  VALUE Thread_switch_fiber(VALUE self) {
@@ -61,10 +59,6 @@ VALUE Thread_class_backend(VALUE _self) {
61
59
  void Init_Thread() {
62
60
  rb_define_method(rb_cThread, "setup_fiber_scheduling", Thread_setup_fiber_scheduling, 0);
63
61
  rb_define_method(rb_cThread, "schedule_and_wakeup", Thread_fiber_schedule_and_wakeup, 2);
64
-
65
- rb_define_method(rb_cThread, "schedule_fiber", Thread_schedule_fiber, 2);
66
- rb_define_method(rb_cThread, "schedule_fiber_with_priority",
67
- Thread_schedule_fiber_with_priority, 2);
68
62
  rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
69
63
  rb_define_method(rb_cThread, "fiber_unschedule", Thread_fiber_unschedule, 1);
70
64
 
data/lib/polyphony.rb CHANGED
@@ -121,3 +121,9 @@ end
121
121
 
122
122
  Polyphony.install_terminating_signal_handlers
123
123
  Polyphony.install_at_exit_handler
124
+
125
+ if (debug_socket_path = ENV['POLYPHONY_DEBUG_SOCKET_PATH'])
126
+ puts "Starting debug server on #{debug_socket_path}"
127
+ require 'polyphony/debugger/server'
128
+ Polyphony::DebugServer.start(debug_socket_path)
129
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'polyphony/extensions/debug'
4
+
5
+ module Polyphony
6
+ class DebugServer
7
+ TP_EVENTS = [
8
+ :line,
9
+ :call,
10
+ :return,
11
+ :b_call,
12
+ :b_return
13
+ ]
14
+
15
+
16
+ def self.start(socket_path)
17
+ server = self.new(socket_path)
18
+ server.start
19
+
20
+ trace = TracePoint.new(*TP_EVENTS) { |tp| server.handle_tp(trace, tp) }
21
+ trace.enable
22
+
23
+ at_exit do
24
+ puts "program terminated"
25
+ trace.disable
26
+ server.stop
27
+ end
28
+ end
29
+
30
+ def initialize(socket_path)
31
+ @socket_path = socket_path
32
+ @fiber = Fiber.current
33
+ @controller = spin { control_loop }
34
+ puts "@fiber: #{@fiber.inspect}"
35
+ end
36
+
37
+ def start
38
+ fiber = Fiber.current
39
+ @server = spin(:pdbg_server) do
40
+ puts("Listening on #{@socket_path}")
41
+ FileUtils.rm(@socket_path) if File.exists?(@socket_path)
42
+ socket = UNIXServer.new(@socket_path)
43
+ fiber << :ready
44
+ id = 0
45
+ socket.accept_loop do |client|
46
+ puts "accepted connection"
47
+ handle_client(client)
48
+ end
49
+ end
50
+ receive
51
+ end
52
+
53
+ def stop
54
+ @server.terminate
55
+ @controller.terminate
56
+ end
57
+
58
+ POLYPHONY_LIB_DIR = File.expand_path('../..', __dir__)
59
+ def handle_client(client)
60
+ @client = client
61
+ puts "trace enabled"
62
+ end
63
+
64
+ def control_loop
65
+ @cmd = :step
66
+ loop do
67
+ case @cmd
68
+ when :step
69
+ step
70
+ end
71
+ end
72
+ end
73
+
74
+ def step
75
+ tp = nil
76
+ fiber = nil
77
+ while true
78
+ event = receive
79
+ fiber = event[:fiber]
80
+ if fiber == @fiber && event[:kind] == :line && event[:path] !~ /#{POLYPHONY_LIB_DIR}/
81
+ interact_with_client(event)
82
+ fiber << :ok
83
+ fiber.__unpark__
84
+ return
85
+ end
86
+
87
+ fiber << :ok
88
+ fiber.__unpark__
89
+ end
90
+ rescue => e
91
+ puts "Uncaught error: #{e.inspect}"
92
+ @trace&.disable
93
+ @client = nil
94
+ end
95
+
96
+ def interact_with_client(event)
97
+ @client.puts event.inspect
98
+ result = @client.gets&.chomp
99
+ end
100
+
101
+ def handle_tp(trace, tp)
102
+ return if @in_handle_tp
103
+
104
+ process_tp(trace, tp)
105
+ end
106
+
107
+ def process_tp(trace, tp)
108
+ @in_handle_tp = true
109
+ if !@client
110
+ wait_for_client
111
+ end
112
+
113
+ puts "- #{tp.event} #{tp.path}:#{tp.lineno}"
114
+
115
+ fiber = Fiber.current
116
+ fiber.__park__
117
+
118
+ @controller << {
119
+ fiber: fiber,
120
+ kind: tp.event,
121
+ path: tp.path,
122
+ lineno: tp.lineno
123
+ }
124
+ receive
125
+ ensure
126
+ @in_handle_tp = nil
127
+ end
128
+
129
+ def wait_for_client
130
+ puts "wait_for_client"
131
+ sleep 0.1 until @client
132
+ puts " got client!"
133
+ msg = @client.gets
134
+ @client.puts msg
135
+ end
136
+ end
137
+ end
@@ -10,4 +10,4 @@ module ::Kernel
10
10
  format("%p\n", args.size == 1 ? args.first : args)
11
11
  end
12
12
  end
13
- end
13
+ end
@@ -7,6 +7,10 @@ require_relative '../core/exceptions'
7
7
  module Polyphony
8
8
  # Fiber control API
9
9
  module FiberControl
10
+ def monitor_mailbox
11
+ @monitor_mailbox ||= Polyphony::Queue.new
12
+ end
13
+
10
14
  def interrupt(value = nil)
11
15
  return if @running == false
12
16
 
@@ -124,23 +128,24 @@ module Polyphony
124
128
  def await(*fibers)
125
129
  return [] if fibers.empty?
126
130
 
127
- Fiber.current.message_on_child_termination = true
131
+ current_fiber = self.current
132
+ mailbox = current_fiber.monitor_mailbox
128
133
  results = {}
129
134
  fibers.each do |f|
130
135
  results[f] = nil
131
136
  if f.dead?
132
137
  # fiber already terminated, so queue message
133
- Fiber.current.send [f, f.result]
138
+ mailbox << [f, f.result]
134
139
  else
135
- f.monitor
140
+ f.monitor(current_fiber)
136
141
  end
137
142
  end
138
143
  exception = nil
139
144
  while !fibers.empty?
140
- (fiber, result) = receive
145
+ (fiber, result) = mailbox.shift
141
146
  next unless fibers.include?(fiber)
142
-
143
147
  fibers.delete(fiber)
148
+ current_fiber.remove_child(fiber) if fiber.parent == current_fiber
144
149
  if result.is_a?(Exception)
145
150
  exception ||= result
146
151
  fibers.each { |f| f.terminate }
@@ -148,16 +153,16 @@ module Polyphony
148
153
  results[fiber] = result
149
154
  end
150
155
  end
151
- results.values
152
- ensure
153
- Fiber.current.message_on_child_termination = false
154
156
  raise exception if exception
157
+ results.values
155
158
  end
156
159
  alias_method :join, :await
157
160
 
158
161
  def select(*fibers)
159
162
  return nil if fibers.empty?
160
163
 
164
+ current_fiber = self.current
165
+ mailbox = current_fiber.monitor_mailbox
161
166
  fibers.each do |f|
162
167
  if f.dead?
163
168
  result = f.result
@@ -165,21 +170,18 @@ module Polyphony
165
170
  end
166
171
  end
167
172
 
168
- Fiber.current.message_on_child_termination = true
169
- fibers.each { |f| f.monitor }
173
+ fibers.each { |f| f.monitor(current_fiber) }
170
174
  while true
171
- (fiber, result) = receive
175
+ (fiber, result) = mailbox.shift
172
176
  next unless fibers.include?(fiber)
173
177
 
174
- fibers.each { |f| f.unmonitor }
178
+ fibers.each { |f| f.unmonitor(current_fiber) }
175
179
  if result.is_a?(Exception)
176
180
  raise result
177
181
  else
178
182
  return [fiber, result]
179
183
  end
180
184
  end
181
- ensure
182
- Fiber.current.message_on_child_termination = false
183
185
  end
184
186
 
185
187
  # Creates and schedules with priority an out-of-band fiber that runs the
@@ -219,14 +221,6 @@ module Polyphony
219
221
  f
220
222
  end
221
223
 
222
- def child_done(child_fiber, result)
223
- @children.delete(child_fiber)
224
-
225
- if result.is_a?(Exception) && !@message_on_child_termination
226
- schedule_with_priority(result)
227
- end
228
- end
229
-
230
224
  def terminate_all_children(graceful = false)
231
225
  return unless @children
232
226
 
@@ -240,16 +234,25 @@ module Polyphony
240
234
  def await_all_children
241
235
  return unless @children && !@children.empty?
242
236
 
243
- Fiber.await(*@children.keys)
237
+ Fiber.await(*@children.keys.reject { |c| c.dead? })
244
238
  end
245
239
 
246
240
  def shutdown_all_children(graceful = false)
247
241
  return unless @children
248
242
 
249
243
  @children.keys.each do |c|
244
+ next if c.dead?
245
+
250
246
  c.terminate(graceful)
251
247
  c.await
252
248
  end
249
+ reap_dead_children
250
+ end
251
+
252
+ def reap_dead_children
253
+ return unless @children
254
+
255
+ @children.reject! { |f| f.dead? }
253
256
  end
254
257
 
255
258
  def detach
@@ -323,7 +326,7 @@ module Polyphony
323
326
  Thread.backend.trace(:fiber_terminate, self, result)
324
327
  @result = result
325
328
 
326
- inform_dependants(result, uncaught_exception)
329
+ inform_monitors(result, uncaught_exception)
327
330
  @running = false
328
331
  ensure
329
332
  # Prevent fiber from being resumed after terminating
@@ -341,24 +344,28 @@ module Polyphony
341
344
  [e, true]
342
345
  end
343
346
 
344
- def inform_dependants(result, uncaught_exception)
347
+ def inform_monitors(result, uncaught_exception)
345
348
  if @monitors
346
349
  msg = [self, result]
347
- @monitors.each { |f| f << msg }
350
+ @monitors.each_key { |f| f.monitor_mailbox << msg }
348
351
  end
349
352
 
350
- @parent&.child_done(self, result)
353
+ if uncaught_exception && @parent
354
+ parent_is_monitor = @monitors&.has_key?(@parent)
355
+ @parent.schedule_with_priority(result) unless parent_is_monitor
356
+ end
351
357
  end
352
358
 
353
- attr_accessor :message_on_child_termination
359
+ def monitor(fiber)
360
+ (@monitors ||= {})[fiber] = true
361
+ end
354
362
 
355
- def monitor
356
- @monitors ||= []
357
- @monitors << Fiber.current
363
+ def unmonitor(fiber)
364
+ (@monitors ||= []).delete(fiber)
358
365
  end
359
366
 
360
- def unmonitor
361
- @monitors.delete(Fiber.current) if @monitors
367
+ def monitors
368
+ @monitors&.keys || []
362
369
  end
363
370
 
364
371
  def dead?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.66'
4
+ VERSION = '0.67'
5
5
  end
data/test/helper.rb CHANGED
@@ -46,10 +46,6 @@ end
46
46
  class MiniTest::Test
47
47
  def setup
48
48
  # trace "* setup #{self.name}"
49
- if Fiber.current.children.size > 0
50
- puts "Children left: #{Fiber.current.children.inspect}"
51
- exit!
52
- end
53
49
  Fiber.current.setup_main_fiber
54
50
  Fiber.current.instance_variable_set(:@auto_watcher, nil)
55
51
  Thread.current.backend.finalize
@@ -60,6 +56,10 @@ class MiniTest::Test
60
56
  def teardown
61
57
  # trace "* teardown #{self.name}"
62
58
  Fiber.current.shutdown_all_children
59
+ if Fiber.current.children.size > 0
60
+ puts "Children left after #{self.name}: #{Fiber.current.children.inspect}"
61
+ exit!
62
+ end
63
63
  Fiber.current.instance_variable_set(:@auto_watcher, nil)
64
64
  rescue => e
65
65
  puts e
data/test/stress.rb CHANGED
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  count = ARGV[0] ? ARGV[0].to_i : 100
4
+ test_name = ARGV[1]
4
5
 
5
- TEST_CMD = 'ruby test/run.rb'
6
+ $test_cmd = +'ruby test/run.rb'
7
+ if test_name
8
+ $test_cmd << " --name #{test_name}"
9
+ end
6
10
 
7
11
  def run_test(count)
8
12
  puts "#{count}: running tests..."
9
13
  # sleep 1
10
- system(TEST_CMD)
14
+ system($test_cmd)
11
15
  puts
12
16
 
13
17
  return if $?.exitstatus == 0
data/test/test_fiber.rb CHANGED
@@ -46,7 +46,7 @@ class FiberTest < MiniTest::Test
46
46
  def test_await_dead_children
47
47
  f1 = spin { :foo }
48
48
  f2 = spin { :bar }
49
- 2.times { snooze }
49
+ 4.times { snooze }
50
50
 
51
51
  assert_equal [:foo, :bar], Fiber.await(f1, f2)
52
52
  end
@@ -67,10 +67,12 @@ class FiberTest < MiniTest::Test
67
67
  }
68
68
  Fiber.await(f2, f3)
69
69
  assert_equal [:foo, :bar, :baz], buffer
70
- assert_equal 0, Fiber.current.children.size
70
+ assert_equal [f1], Fiber.current.children
71
+ Fiber.current.reap_dead_children
72
+ assert_equal [], Fiber.current.children
71
73
  end
72
74
 
73
- def test_await_from_multiple_fibers_with_interruption=
75
+ def test_await_from_multiple_fibers_with_interruption
74
76
  buffer = []
75
77
  f1 = spin {
76
78
  sleep 0.02
@@ -91,6 +93,8 @@ class FiberTest < MiniTest::Test
91
93
  f1.stop
92
94
 
93
95
  snooze
96
+ assert_equal [f1, f2, f3], Fiber.current.children
97
+ Fiber.current.reap_dead_children
94
98
  assert_equal [], Fiber.current.children
95
99
  end
96
100
 
@@ -563,10 +567,10 @@ class FiberTest < MiniTest::Test
563
567
  end
564
568
 
565
569
  snooze
566
- child.monitor
570
+ child.monitor(Fiber.current)
567
571
  spin { child << :foo }
568
572
 
569
- msg = receive
573
+ msg = Fiber.current.monitor_mailbox.shift
570
574
  assert_equal [child, :foo], msg
571
575
  end
572
576
 
@@ -578,14 +582,14 @@ class FiberTest < MiniTest::Test
578
582
  end
579
583
 
580
584
  snooze
581
- child.monitor
585
+ child.monitor(Fiber.current)
582
586
  spin { child << :foo }
583
587
  snooze
584
588
 
585
- child.unmonitor
589
+ child.unmonitor(Fiber.current)
586
590
 
587
- Fiber.current << :bar
588
- msg = receive
591
+ Fiber.current.monitor_mailbox << :bar
592
+ msg = Fiber.current.monitor_mailbox.shift
589
593
  assert_equal :bar, msg
590
594
  end
591
595
 
@@ -598,6 +602,7 @@ class FiberTest < MiniTest::Test
598
602
 
599
603
  f.stop
600
604
  snooze
605
+ Fiber.current.reap_dead_children
601
606
  assert_equal [], Fiber.current.children
602
607
  end
603
608
 
@@ -1218,4 +1223,23 @@ class GracefulTerminationTest < MiniTest::Test
1218
1223
 
1219
1224
  assert_equal [1, 2], buffer
1220
1225
  end
1226
+ end
1227
+
1228
+ class DebugTest < MiniTest::Test
1229
+ def test_parking
1230
+ buf = []
1231
+ f = spin do
1232
+ 3.times { |i| snooze; buf << i }
1233
+ end
1234
+ assert_nil f.__parked__?
1235
+ f.__park__
1236
+ assert_equal true, f.__parked__?
1237
+ 10.times { snooze }
1238
+ assert_equal [], buf
1239
+
1240
+ f.__unpark__
1241
+ assert_nil f.__parked__?
1242
+ 10.times { snooze }
1243
+ assert_equal [0, 1, 2], buf
1244
+ end
1221
1245
  end
@@ -375,6 +375,7 @@ class SpinScopeTest < MiniTest::Test
375
375
  buffer << e.message
376
376
  end
377
377
  10.times { snooze }
378
+ Fiber.current.reap_dead_children
378
379
  assert_equal 0, Fiber.current.children.size
379
380
  assert_equal ['foobar'], buffer
380
381
  end
@@ -70,7 +70,7 @@ class ThreadPoolTest < MiniTest::Test
70
70
 
71
71
  sleep 0.15 # allow time for threads to spawn
72
72
  assert_equal @pool.size, threads.uniq.size
73
- assert_equal (0..9).to_a, buffer.sort
73
+ assert_equal (0..9).to_a, buffer.sort if IS_LINUX
74
74
  end
75
75
 
76
76
  def test_busy?
@@ -10,7 +10,7 @@ class ThrottlerTest < MiniTest::Test
10
10
  f = spin { loop { t.process { buffer << 1 } } }
11
11
  sleep 0.2
12
12
  f.stop
13
- assert_in_range 1..3, buffer.size
13
+ assert_in_range 1..4, buffer.size
14
14
  ensure
15
15
  t.stop
16
16
  end
@@ -23,7 +23,7 @@ class ThrottlerTest < MiniTest::Test
23
23
  end
24
24
  sleep 0.25
25
25
  f.stop
26
- assert_in_range 2..6, buffer.size
26
+ assert_in_range 2..7, buffer.size
27
27
  ensure
28
28
  t.stop
29
29
  end
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.66'
4
+ version: '0.67'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-01 00:00:00.000000000 Z
11
+ date: 2021-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -156,6 +156,7 @@ files:
156
156
  - README.md
157
157
  - Rakefile
158
158
  - TODO.md
159
+ - bin/pdbg
159
160
  - bin/polyphony-debug
160
161
  - bin/stress.rb
161
162
  - bin/test
@@ -356,6 +357,7 @@ files:
356
357
  - lib/polyphony/core/thread_pool.rb
357
358
  - lib/polyphony/core/throttler.rb
358
359
  - lib/polyphony/core/timer.rb
360
+ - lib/polyphony/debugger/server.rb
359
361
  - lib/polyphony/extensions/core.rb
360
362
  - lib/polyphony/extensions/debug.rb
361
363
  - lib/polyphony/extensions/fiber.rb