polyphony 0.66 → 0.67

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: 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