polyphony 0.43.4 → 0.43.5

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: 9481b252526ebc3f8e0f5a0121eebb68492bc0c1502cfed447da7b0648e6095b
4
- data.tar.gz: dfd2d5d0415833f14f9ebe2cc6336e87d0951ce86f07ebcdec0ad892f71fb74c
3
+ metadata.gz: 55e44a81e7358528fabd37e1d48b6387957bc6001e4d947bf6c881d2425953d9
4
+ data.tar.gz: 45af7e485891927a1e9f88e8a6342692c25f306aa60e40fa8a74c0ca8494d3d4
5
5
  SHA512:
6
- metadata.gz: 652633b1fc27623edb2e87b5657322ba4826ec176e8eb5131a43249e08340f793ba426743be343bfe0fce02fc60b73cb790afef816a4153585d68bdfeaa9c55d
7
- data.tar.gz: 946abb7526324cae5f1d6b62e25c8410129b07ca75c82a6b7285d6269ed1911e161a6030a394ef29ba238a80da6f96787dd94fdbd5c05bd2a643379252ce80d0
6
+ metadata.gz: 444675baf3131ffc843b61649b92ce5ca7d77773277db750d8c8ac80a88ccd133e6f60d61282fa38ffb91de59eaf2304ea9b0fca196a7eb61c7a53becaa1fe46
7
+ data.tar.gz: f21e26a41c8dfeea0d803b5b9bc82b1e648bf0ca588225f721560c432f363020b37561c59daeefbe2d434b012f1f5f767460557939a22d04e78e40471813a8ca
@@ -1,6 +1,6 @@
1
1
  name: Tests
2
2
 
3
- on: [push]
3
+ on: [push, pull_request]
4
4
 
5
5
  jobs:
6
6
  build:
@@ -1,3 +1,11 @@
1
+ ## 0.43.5 2020-07-13
2
+
3
+ * Fix `#read_nonblock`, `#write_nonblock` for `IO` and `Socket` (#27)
4
+ * Patch `Kernel#p`, `IO#puts` to issue single write call
5
+ * Add support for multiple arguments in `IO#write` and `LibevAgent#write`
6
+ * Use LibevQueue for fiber run queue
7
+ * Reimplement LibevQueue as ring buffer
8
+
1
9
  ## 0.43.4 2020-07-09
2
10
 
3
11
  * Reimplement Kernel#trap
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.43.4)
4
+ polyphony (0.43.5)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -18,9 +18,8 @@ implements a comprehensive
18
18
  using [libev](https://github.com/enki/libev) as a high-performance event reactor
19
19
  for I/O, timers, and other asynchronous events.
20
20
 
21
+ [Overview](getting-started/overview){: .btn .btn-green .text-gamma }
21
22
  [Take the tutorial](getting-started/tutorial){: .btn .btn-blue .text-gamma }
22
- [Main Concepts](main-concepts/concurrency/){: .btn .btn-green .text-gamma }
23
- [FAQ](faq){: .btn .btn-green .text-gamma }
24
23
  [Source code](https://github.com/digital-fabric/polyphony){: .btn .btn-purple .text-gamma target="_blank" }
25
24
  {: .mt-6 .h-align-center }
26
25
 
@@ -6,7 +6,7 @@ parent: Main Concepts
6
6
  permalink: /main-concepts/design-principles/
7
7
  prev_title: Extending Polyphony
8
8
  ---
9
- # The Design of Polyphony
9
+ # The Design of Polyphony
10
10
 
11
11
  Polyphony is a new gem that aims to enable developing high-performance
12
12
  concurrent applications in Ruby using a fluent, compact syntax and API.
@@ -47,7 +47,7 @@ Nevertheless, while work is being done to harness fibers for providing a better
47
47
  way to do concurrency in Ruby, fibers remain a mistery for most Ruby
48
48
  programmers, a perplexing unfamiliar corner right at the heart of Ruby.
49
49
 
50
- ## Design Principles
50
+ ## The History of Polyphony
51
51
 
52
52
  Polyphony started as an experiment, but over about two years of slow, jerky
53
53
  evolution turned into something I'm really excited to share with the Ruby
@@ -58,31 +58,24 @@ Polyphony today as nothing like the way it began. A careful examination of the
58
58
  [CHANGELOG](https://github.com/digital-fabric/polyphony/blob/master/CHANGELOG.md)
59
59
  would show how Polyphony explored not only different event reactor designs, but
60
60
  also different API designs incorporating various concurrent paradigms such as
61
- promises, async/await, fibers, and finally structured concurrency.
62
-
63
- While Polyphony, like nio4r or EventMachine, uses an event reactor to turn
64
- blocking operations into non-blocking ones, it completely embraces fibers and in
65
- fact does not provide any callback-based APIs. Furthermore, Polyphony provides
66
- fullblown fiber-aware implementations of blocking operations, such as
67
- `read/write`, `sleep` or `waitpid`, instead of just event watching primitives.
61
+ promises, async/await, fibers, and finally structured concurrency.
68
62
 
69
63
  Throughout the development process, it was my intention to create a programming
70
- interface that would make highly-concurrent
71
-
72
-
73
-
74
-
64
+ interface that would make it easy to author highly-concurrent Ruby programs.
75
65
 
66
+ ## Design Principles
76
67
 
68
+ While Polyphony, like nio4r or EventMachine, uses an event reactor to turn
69
+ blocking operations into non-blocking ones, it completely embraces fibers and in
70
+ fact does not provide any callback-based APIs.
77
71
 
78
- a single Ruby process may spin up millions of
79
- concurrent fibers.
72
+ Furthermore, Polyphony provides fullblown fiber-aware implementations of
73
+ blocking operations, such as `read/write`, `sleep` or `waitpid`, instead of just
74
+ event watching primitives.
80
75
 
81
- , by utilizing Ruby fibers together with the
82
- [libev](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod) event reactor
83
- library. Polyphony's design is based on the following principles:
76
+ Polyphony's design is based on the following principles:
84
77
 
85
- - Polyphony's concurrency model should feel "baked-in". The API should allow
78
+ - The concurrency model should feel "baked-in". The API should allow
86
79
  concurrency with minimal effort. Polyphony should facilitate writing both
87
80
  large apps and small scripts with as little boilerplate code as possible.
88
81
  There should be no calls to initialize the event reactor, or other ceremonial
@@ -91,8 +84,8 @@ library. Polyphony's design is based on the following principles:
91
84
  ```ruby
92
85
  require 'polyphony'
93
86
 
94
- # start 10 fibers, each sleeping for 1 second
95
- 10.times { spin { sleep 1 } }
87
+ # start 10 fibers, each sleeping for 3 seconds
88
+ 10.times { spin { sleep 3 } }
96
89
 
97
90
  puts 'going to sleep now'
98
91
  # wait for other fibers to terminate
@@ -106,14 +99,14 @@ library. Polyphony's design is based on the following principles:
106
99
  ```ruby
107
100
  # in Polyphony, I/O ops might block the current fiber, but implicitly yield to
108
101
  # other concurrent fibers:
109
- clients.each { |client|
102
+ clients.each do |client|
110
103
  spin { client.puts 'Elvis has left the chatroom' }
111
- }
104
+ end
112
105
  ```
113
106
 
114
107
  - Concurrency primitives should be accessible using idiomatic Ruby techniques
115
108
  (blocks, method chaining...) and should feel as much as possible "part of the
116
- language". The resulting API is based more on methods and less on classes,
109
+ language". The resulting API is fundamentally based on methods rather than classes,
117
110
  for example `spin` or `move_on_after`, leading to a coding style that is both
118
111
  more compact and more legible:
119
112
 
@@ -125,8 +118,6 @@ library. Polyphony's design is based on the following principles:
125
118
  }
126
119
  ```
127
120
 
128
- - Breaking up operations into
129
-
130
121
  - Polyphony should embrace Ruby's standard `raise/rescue/ensure` exception
131
122
  handling mechanism. Exception handling in a highly concurrent environment
132
123
  should be robust and foolproof:
@@ -147,7 +138,8 @@ library. Polyphony's design is based on the following principles:
147
138
  constructs through composition.
148
139
 
149
140
  - The entire design should embrace fibers. There should be no callback-based
150
- asynchronous APIs.
141
+ asynchronous APIs. The library and its ecosystem will foster the development
142
+ of techniques and tools for converting callback-based APIs to fiber-based ones.
151
143
 
152
144
  - Use of extensive monkey patching of Ruby core modules and classes such as
153
145
  `Kernel`, `Fiber`, `IO` and `Timeout`. This allows porting over non-Polyphony
@@ -160,13 +152,10 @@ library. Polyphony's design is based on the following principles:
160
152
  # use TCPServer from Ruby's stdlib
161
153
  server = TCPServer.open('127.0.0.1', 1234)
162
154
  while (client = server.accept)
163
- spin do
155
+ spin {
164
156
  while (data = client.gets)
165
- client.write('you said: ', data.chomp, "!\n")
157
+ client.write("you said: #{ data.chomp }\n")
166
158
  end
167
- end
159
+ }
168
160
  end
169
161
  ```
170
-
171
- - Development of techniques and tools for converting callback-based APIs to
172
- fiber-based ones.
@@ -44,7 +44,7 @@ pong = Fiber.new { loop { puts "pong"; ping.transfer } }
44
44
  ping.transfer
45
45
  ```
46
46
 
47
- `Fiber#transform` also allows using the main fiber as a general purpose
47
+ `Fiber#transfer` also allows using the main fiber as a general purpose
48
48
  resumable execution context. For that reason, Polyphony uses `Fiber#transfer`
49
49
  exclusively for scheduling fibers. Normally, however, applications based on
50
50
  Polyphony will not use this API directly.
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ X = 1_000_000
7
+
8
+ GC.disable
9
+
10
+ count = 0
11
+
12
+ pong = spin_loop do
13
+ msg, ping = receive
14
+ count += 1
15
+ ping << 'pong'
16
+ end
17
+
18
+ ping = spin do
19
+ X.times do
20
+ pong << ['ping', Fiber.current]
21
+ msg = receive
22
+ count += 1
23
+ end
24
+ end
25
+
26
+ t0 = Time.now
27
+ ping.await
28
+ dt = Time.now - t0
29
+ puts format('message rate: %d/s', (X / dt))
@@ -5,19 +5,20 @@ require 'polyphony'
5
5
 
6
6
  def bm(fibers, iterations)
7
7
  count = 0
8
- t0 = Time.now
9
- supervise do |s|
10
- fibers.times do
11
- s.spin do
12
- iterations.times do
13
- snooze
14
- count += 1
15
- end
8
+ t_pre = Time.now
9
+ fibers.times do
10
+ spin do
11
+ iterations.times do
12
+ snooze
13
+ count += 1
16
14
  end
17
15
  end
18
16
  end
17
+ t0 = Time.now
18
+ Fiber.current.await_all_children
19
19
  dt = Time.now - t0
20
- puts "#{[fibers, iterations].inspect} count: #{count} #{count / dt.to_f}/s"
20
+ puts "#{[fibers, iterations].inspect} setup: #{t0 - t_pre}s count: #{count} #{count / dt.to_f}/s"
21
+ Thread.current.run_queue_trace
21
22
  end
22
23
 
23
24
  GC.disable
@@ -27,5 +28,6 @@ bm(10, 100_000)
27
28
  bm(100, 10_000)
28
29
  bm(1_000, 1_000)
29
30
  bm(10_000, 100)
31
+
30
32
  # bm(100_000, 10)
31
33
  # bm(1_000_000, 1)
@@ -130,7 +130,7 @@ VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue
130
130
  GetLibevAgent(self, agent);
131
131
 
132
132
  if (is_nowait) {
133
- long runnable_count = RARRAY_LEN(queue);
133
+ long runnable_count = LibevQueue_len(queue);
134
134
  agent->run_no_wait_count++;
135
135
  if (agent->run_no_wait_count < runnable_count || agent->run_no_wait_count < 10)
136
136
  return self;
@@ -413,45 +413,60 @@ error:
413
413
  return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
414
414
  }
415
415
 
416
- VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
416
+ VALUE LibevAgent_write(int argc, VALUE *argv, VALUE self) {
417
417
  struct LibevAgent_t *agent;
418
418
  struct libev_io watcher;
419
419
  rb_io_t *fptr;
420
420
  VALUE switchpoint_result = Qnil;
421
+ VALUE io;
422
+ VALUE underlying_io;
423
+ long total_written = 0;
424
+ int arg_idx = 1;
421
425
 
422
- char *buf = StringValuePtr(str);
423
- long len = RSTRING_LEN(str);
424
- long left = len;
425
-
426
- VALUE underlying_io = rb_iv_get(io, "@io");
426
+ if (argc < 2)
427
+ rb_raise(rb_eRuntimeError, "(wrong number of arguments (expected 2 or more))");
428
+
429
+ io = argv[0];
430
+ underlying_io = rb_iv_get(io, "@io");
427
431
  if (underlying_io != Qnil) io = underlying_io;
428
432
  GetLibevAgent(self, agent);
429
433
  io = rb_io_get_write_io(io);
430
434
  GetOpenFile(io, fptr);
431
435
  watcher.fiber = Qnil;
432
436
 
433
- while (left > 0) {
434
- ssize_t n = write(fptr->fd, buf, left);
435
- if (n < 0) {
436
- int e = errno;
437
- if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
437
+ while (arg_idx < argc) {
438
+ VALUE str = argv[arg_idx];
439
+ char *buf = StringValuePtr(str);
440
+ long len = RSTRING_LEN(str);
441
+ long left = len;
438
442
 
439
- switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
440
- if (TEST_EXCEPTION(switchpoint_result)) goto error;
441
- }
442
- else {
443
- switchpoint_result = libev_snooze();
444
- if (TEST_EXCEPTION(switchpoint_result)) goto error;
443
+ while (left > 0) {
444
+ ssize_t n = write(fptr->fd, buf, left);
445
+ if (n < 0) {
446
+ int e = errno;
447
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
445
448
 
446
- buf += n;
447
- left -= n;
449
+ switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
450
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
451
+ }
452
+ else {
453
+ buf += n;
454
+ left -= n;
455
+ }
448
456
  }
457
+ total_written += len;
458
+ arg_idx++;
459
+ }
460
+
461
+ if (watcher.fiber == Qnil) {
462
+ switchpoint_result = libev_snooze();
463
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
449
464
  }
450
465
 
451
466
  RB_GC_GUARD(watcher.fiber);
452
467
  RB_GC_GUARD(switchpoint_result);
453
468
 
454
- return INT2NUM(len);
469
+ return INT2NUM(total_written);
455
470
  error:
456
471
  return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
457
472
  }
@@ -723,7 +738,7 @@ void Init_LibevAgent() {
723
738
 
724
739
  rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
725
740
  rb_define_method(cLibevAgent, "read_loop", LibevAgent_read_loop, 1);
726
- rb_define_method(cLibevAgent, "write", LibevAgent_write, 2);
741
+ rb_define_method(cLibevAgent, "write", LibevAgent_write, -1);
727
742
  rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
728
743
  rb_define_method(cLibevAgent, "accept_loop", LibevAgent_accept_loop, 1);
729
744
  // rb_define_method(cLibevAgent, "connect", LibevAgent_accept, 3);
@@ -1,4 +1,5 @@
1
1
  #include "polyphony.h"
2
+ #include "ring_buffer.h"
2
3
 
3
4
  struct async_watcher {
4
5
  ev_async async;
@@ -6,48 +7,65 @@ struct async_watcher {
6
7
  VALUE fiber;
7
8
  };
8
9
 
9
- struct async_queue {
10
+ struct async_watcher_queue {
10
11
  struct async_watcher **queue;
11
- unsigned int len;
12
+ unsigned int length;
12
13
  unsigned int count;
13
14
  unsigned int push_idx;
14
- unsigned int pop_idx;
15
+ unsigned int shift_idx;
15
16
  };
16
17
 
17
- void async_queue_init(struct async_queue *queue) {
18
- queue->len = 4;
18
+ void async_watcher_queue_init(struct async_watcher_queue *queue) {
19
+ queue->length = 1;
19
20
  queue->count = 0;
20
- queue->queue = malloc(sizeof(struct async_watcher *) * queue->len);
21
+ queue->queue = malloc(sizeof(struct async_watcher *) * queue->length);
21
22
  queue->push_idx = 0;
22
- queue->pop_idx = 0;
23
+ queue->shift_idx = 0;
23
24
  }
24
25
 
25
- void async_queue_free(struct async_queue *queue) {
26
+ void async_watcher_queue_free(struct async_watcher_queue *queue) {
26
27
  free(queue->queue);
27
28
  }
28
29
 
29
- void async_queue_push(struct async_queue *queue, struct async_watcher *watcher) {
30
- if (queue->push_idx == queue->len) {
31
- queue->len = queue->len * 2;
32
- queue->queue = realloc(queue->queue, sizeof(struct async_watcher *) * queue->len);
33
- }
30
+ void async_watcher_queue_realign(struct async_watcher_queue *queue) {
31
+ memmove(
32
+ queue->queue,
33
+ queue->queue + queue->shift_idx,
34
+ queue->count * sizeof(struct async_watcher *)
35
+ );
36
+ queue->push_idx = queue->push_idx - queue->shift_idx;
37
+ queue->shift_idx = 0;
38
+ }
39
+
40
+ #define QUEUE_REALIGN_THRESHOLD 32
41
+
42
+ void async_watcher_queue_push(struct async_watcher_queue *queue, struct async_watcher *watcher) {
34
43
  if (queue->count == 0) {
35
44
  queue->push_idx = 0;
36
- queue->pop_idx = 0;
45
+ queue->shift_idx = 0;
46
+ }
47
+ if (queue->push_idx == queue->length) {
48
+ // prevent shift idx moving too much away from zero
49
+ if (queue->length >= QUEUE_REALIGN_THRESHOLD && queue->shift_idx >= (queue->length / 2))
50
+ async_watcher_queue_realign(queue);
51
+ else {
52
+ queue->length = (queue->length == 1) ? 4 : queue->length * 2;
53
+ queue->queue = realloc(queue->queue, sizeof(struct async_watcher *) * queue->length);
54
+ }
37
55
  }
38
56
  queue->count++;
39
57
  queue->queue[queue->push_idx++] = watcher;
40
58
  }
41
59
 
42
- struct async_watcher *async_queue_pop(struct async_queue *queue) {
60
+ struct async_watcher *async_watcher_queue_shift(struct async_watcher_queue *queue) {
43
61
  if (queue->count == 0) return 0;
44
62
 
45
63
  queue->count--;
46
64
 
47
- return queue->queue[queue->pop_idx++];
65
+ return queue->queue[queue->shift_idx++];
48
66
  }
49
67
 
50
- void async_queue_remove_at_idx(struct async_queue *queue, unsigned int remove_idx) {
68
+ void async_watcher_queue_remove_at_idx(struct async_watcher_queue *queue, unsigned int remove_idx) {
51
69
  queue->count--;
52
70
  queue->push_idx--;
53
71
  if (remove_idx < queue->push_idx)
@@ -58,33 +76,33 @@ void async_queue_remove_at_idx(struct async_queue *queue, unsigned int remove_id
58
76
  );
59
77
  }
60
78
 
61
- void async_queue_remove_by_fiber(struct async_queue *queue, VALUE fiber) {
79
+ void async_watcher_queue_remove_by_fiber(struct async_watcher_queue *queue, VALUE fiber) {
62
80
  if (queue->count == 0) return;
63
81
 
64
- for (unsigned idx = queue->pop_idx; idx < queue->push_idx; idx++) {
82
+ for (unsigned idx = queue->shift_idx; idx < queue->push_idx; idx++) {
65
83
  if (queue->queue[idx]->fiber == fiber) {
66
- async_queue_remove_at_idx(queue, idx);
84
+ async_watcher_queue_remove_at_idx(queue, idx);
67
85
  return;
68
86
  }
69
87
  }
70
88
  }
71
89
 
72
90
  typedef struct queue {
73
- VALUE items;
74
- struct async_queue shift_queue;
91
+ ring_buffer values;
92
+ struct async_watcher_queue shift_queue;
75
93
  } LibevQueue_t;
76
94
 
77
-
78
95
  VALUE cLibevQueue = Qnil;
79
96
 
80
97
  static void LibevQueue_mark(void *ptr) {
81
98
  LibevQueue_t *queue = ptr;
82
- rb_gc_mark(queue->items);
99
+ ring_buffer_mark(&queue->values);
83
100
  }
84
101
 
85
102
  static void LibevQueue_free(void *ptr) {
86
103
  LibevQueue_t *queue = ptr;
87
- async_queue_free(&queue->shift_queue);
104
+ ring_buffer_free(&queue->values);
105
+ async_watcher_queue_free(&queue->shift_queue);
88
106
  xfree(ptr);
89
107
  }
90
108
 
@@ -112,8 +130,8 @@ static VALUE LibevQueue_initialize(VALUE self) {
112
130
  LibevQueue_t *queue;
113
131
  GetQueue(self, queue);
114
132
 
115
- queue->items = rb_ary_new();
116
- async_queue_init(&queue->shift_queue);
133
+ ring_buffer_init(&queue->values);
134
+ async_watcher_queue_init(&queue->shift_queue);
117
135
 
118
136
  return self;
119
137
  }
@@ -122,18 +140,31 @@ VALUE LibevQueue_push(VALUE self, VALUE value) {
122
140
  LibevQueue_t *queue;
123
141
  GetQueue(self, queue);
124
142
  if (queue->shift_queue.count > 0) {
125
- struct async_watcher *watcher = async_queue_pop(&queue->shift_queue);
143
+ struct async_watcher *watcher = async_watcher_queue_shift(&queue->shift_queue);
144
+ if (watcher) {
145
+ ev_async_send(watcher->ev_loop, &watcher->async);
146
+ }
147
+ }
148
+ ring_buffer_push(&queue->values, value);
149
+ return self;
150
+ }
151
+
152
+ VALUE LibevQueue_unshift(VALUE self, VALUE value) {
153
+ LibevQueue_t *queue;
154
+ GetQueue(self, queue);
155
+ if (queue->shift_queue.count > 0) {
156
+ struct async_watcher *watcher = async_watcher_queue_shift(&queue->shift_queue);
126
157
  if (watcher) {
127
158
  ev_async_send(watcher->ev_loop, &watcher->async);
128
159
  }
129
160
  }
130
- rb_ary_push(queue->items, value);
161
+ ring_buffer_unshift(&queue->values, value);
131
162
  return self;
132
163
  }
133
164
 
134
165
  struct ev_loop *LibevAgent_ev_loop(VALUE self);
135
166
 
136
- void async_queue_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
167
+ void async_watcher_queue_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
137
168
  struct async_watcher *watcher = (struct async_watcher *)ev_async;
138
169
  Fiber_make_runnable(watcher->fiber, Qnil);
139
170
  }
@@ -144,22 +175,22 @@ VALUE LibevQueue_shift(VALUE self) {
144
175
  LibevQueue_t *queue;
145
176
  GetQueue(self, queue);
146
177
 
147
- if (RARRAY_LEN(queue->items) == 0) {
178
+ if (queue->values.count == 0) {
148
179
  struct async_watcher watcher;
149
180
  VALUE agent = rb_ivar_get(rb_thread_current(), ID_ivar_agent);
150
181
  VALUE switchpoint_result = Qnil;
151
182
 
152
183
  watcher.ev_loop = LibevAgent_ev_loop(agent);
153
184
  watcher.fiber = rb_fiber_current();
154
- async_queue_push(&queue->shift_queue, &watcher);
155
- ev_async_init(&watcher.async, async_queue_callback);
185
+ async_watcher_queue_push(&queue->shift_queue, &watcher);
186
+ ev_async_init(&watcher.async, async_watcher_queue_callback);
156
187
  ev_async_start(watcher.ev_loop, &watcher.async);
157
188
 
158
189
  switchpoint_result = libev_agent_await(agent);
159
190
  ev_async_stop(watcher.ev_loop, &watcher.async);
160
191
 
161
192
  if (RTEST(rb_obj_is_kind_of(switchpoint_result, rb_eException))) {
162
- async_queue_remove_by_fiber(&queue->shift_queue, watcher.fiber);
193
+ async_watcher_queue_remove_by_fiber(&queue->shift_queue, watcher.fiber);
163
194
  return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
164
195
  }
165
196
  RB_GC_GUARD(watcher.fiber);
@@ -167,36 +198,72 @@ VALUE LibevQueue_shift(VALUE self) {
167
198
  RB_GC_GUARD(switchpoint_result);
168
199
  }
169
200
 
170
- return rb_ary_shift(queue->items);
201
+ return ring_buffer_shift(&queue->values);
202
+ }
203
+
204
+ VALUE LibevQueue_shift_no_wait(VALUE self) {
205
+ LibevQueue_t *queue;
206
+ GetQueue(self, queue);
207
+
208
+ return ring_buffer_shift(&queue->values);
209
+ }
210
+
211
+ VALUE LibevQueue_delete(VALUE self, VALUE value) {
212
+ LibevQueue_t *queue;
213
+ GetQueue(self, queue);
214
+
215
+ ring_buffer_delete(&queue->values, value);
216
+ return self;
217
+ }
218
+
219
+ VALUE LibevQueue_clear(VALUE self) {
220
+ LibevQueue_t *queue;
221
+ GetQueue(self, queue);
222
+
223
+ ring_buffer_clear(&queue->values);
224
+ return self;
225
+ }
226
+
227
+ long LibevQueue_len(VALUE self) {
228
+ LibevQueue_t *queue;
229
+ GetQueue(self, queue);
230
+
231
+ return queue->values.count;
171
232
  }
172
233
 
173
234
  VALUE LibevQueue_shift_each(VALUE self) {
174
235
  LibevQueue_t *queue;
175
- VALUE old_queue;
176
236
  GetQueue(self, queue);
177
- old_queue = queue->items;
178
- queue->items = rb_ary_new();
179
-
180
- if (rb_block_given_p()) {
181
- long len = RARRAY_LEN(old_queue);
182
- long i;
183
- for (i = 0; i < len; i++) {
184
- rb_yield(RARRAY_AREF(old_queue, i));
185
- }
186
- RB_GC_GUARD(old_queue);
187
- return self;
188
- }
189
- else {
190
- RB_GC_GUARD(old_queue);
191
- return old_queue;
192
- }
237
+
238
+ ring_buffer_shift_each(&queue->values);
239
+ return self;
240
+ }
241
+
242
+ VALUE LibevQueue_shift_all(VALUE self) {
243
+ LibevQueue_t *queue;
244
+ GetQueue(self, queue);
245
+
246
+ return ring_buffer_shift_all(&queue->values);
193
247
  }
194
248
 
195
249
  VALUE LibevQueue_empty_p(VALUE self) {
196
250
  LibevQueue_t *queue;
197
251
  GetQueue(self, queue);
198
252
 
199
- return (RARRAY_LEN(queue->items) == 0) ? Qtrue : Qfalse;
253
+ return (queue->values.count == 0) ? Qtrue : Qfalse;
254
+ }
255
+
256
+ void LibevQueue_trace(VALUE self) {
257
+ LibevQueue_t *queue;
258
+ GetQueue(self, queue);
259
+
260
+ printf(
261
+ "queue size: %d count: %d head: %d tail: %d\n",
262
+ queue->values.size,
263
+ queue->values.count,
264
+ queue->values.head,
265
+ queue->values.tail
266
+ );
200
267
  }
201
268
 
202
269
  void Init_LibevQueue() {
@@ -206,11 +273,15 @@ void Init_LibevQueue() {
206
273
  rb_define_method(cLibevQueue, "initialize", LibevQueue_initialize, 0);
207
274
  rb_define_method(cLibevQueue, "push", LibevQueue_push, 1);
208
275
  rb_define_method(cLibevQueue, "<<", LibevQueue_push, 1);
276
+ rb_define_method(cLibevQueue, "unshift", LibevQueue_unshift, 1);
209
277
 
210
- rb_define_method(cLibevQueue, "pop", LibevQueue_shift, 0);
211
278
  rb_define_method(cLibevQueue, "shift", LibevQueue_shift, 0);
279
+ rb_define_method(cLibevQueue, "pop", LibevQueue_shift, 0);
280
+ rb_define_method(cLibevQueue, "shift_no_wait", LibevQueue_shift_no_wait, 0);
281
+ rb_define_method(cLibevQueue, "delete", LibevQueue_delete, 1);
212
282
 
213
283
  rb_define_method(cLibevQueue, "shift_each", LibevQueue_shift_each, 0);
284
+ rb_define_method(cLibevQueue, "shift_all", LibevQueue_shift_all, 0);
214
285
  rb_define_method(cLibevQueue, "empty?", LibevQueue_empty_p, 0);
215
286
  }
216
287
 
@@ -1,5 +1,5 @@
1
- #ifndef RUBY_EV_H
2
- #define RUBY_EV_H
1
+ #ifndef POLYPHONY_H
2
+ #define POLYPHONY_H
3
3
 
4
4
  #include "ruby.h"
5
5
  #include "ruby/io.h"
@@ -76,9 +76,16 @@ VALUE LibevAgent_unref(VALUE self);
76
76
  int LibevAgent_ref_count(VALUE self);
77
77
  void LibevAgent_reset_ref_count(VALUE self);
78
78
 
79
- VALUE Polyphony_snooze(VALUE self);
79
+ VALUE LibevQueue_push(VALUE self, VALUE value);
80
+ VALUE LibevQueue_unshift(VALUE self, VALUE value);
81
+ VALUE LibevQueue_shift(VALUE self);
82
+ VALUE LibevQueue_shift_no_wait(VALUE self);
83
+ VALUE LibevQueue_clear(VALUE self);
84
+ VALUE LibevQueue_delete(VALUE self, VALUE value);
85
+ long LibevQueue_len(VALUE self);
86
+ void LibevQueue_trace(VALUE self);
80
87
 
81
- VALUE Polyphony_Queue_push(VALUE self, VALUE value);
88
+ VALUE Polyphony_snooze(VALUE self);
82
89
 
83
90
  VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
84
91
  VALUE Thread_switch_fiber(VALUE thread);
@@ -87,4 +94,4 @@ int io_setstrbuf(VALUE *str, long len);
87
94
  void io_set_read_length(VALUE str, long n, int shrinkable);
88
95
  VALUE io_enc_str(VALUE str, rb_io_t *fptr);
89
96
 
90
- #endif /* RUBY_EV_H */
97
+ #endif /* POLYPHONY_H */
@@ -0,0 +1,120 @@
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
+ #define TRACE_RING_BUFFER(func, buffer) printf( \
21
+ "%s size: %d count: %d head: %d tail: %d\n", \
22
+ func, \
23
+ buffer->size, \
24
+ buffer->count, \
25
+ buffer->head, \
26
+ buffer->tail \
27
+ )
28
+
29
+ VALUE ring_buffer_shift(ring_buffer *buffer) {
30
+ // TRACE_RING_BUFFER("ring_buffer_shift", buffer);
31
+
32
+ VALUE value;
33
+ if (buffer->count == 0) return Qnil;
34
+
35
+ value = buffer->entries[buffer->head];
36
+ buffer->head = (buffer->head + 1) % buffer->size;
37
+ buffer->count--;
38
+ // INSPECT(value);
39
+ return value;
40
+ }
41
+
42
+ void ring_buffer_resize(ring_buffer *buffer) {
43
+ // TRACE_RING_BUFFER("ring_buffer_resize", buffer);
44
+
45
+ unsigned int old_size = buffer->size;
46
+ buffer->size = old_size == 1 ? 4 : old_size * 2;
47
+ // printf("new size: %d\n", buffer->size);
48
+ buffer->entries = realloc(buffer->entries, buffer->size * sizeof(VALUE));
49
+ for (unsigned int idx = 0; idx < buffer->head && idx < buffer->tail; idx++)
50
+ buffer->entries[old_size + idx] = buffer->entries[idx];
51
+ buffer->tail = buffer->head + buffer->count;
52
+ }
53
+
54
+ void ring_buffer_unshift(ring_buffer *buffer, VALUE value) {
55
+ // TRACE_RING_BUFFER("ring_buffer_unshift", buffer);
56
+ // INSPECT(value);
57
+
58
+ if (buffer->count == buffer->size) ring_buffer_resize(buffer);
59
+
60
+ buffer->head = (buffer->head - 1) % buffer->size;
61
+ buffer->entries[buffer->head] = value;
62
+ buffer->count++;
63
+ }
64
+
65
+ void ring_buffer_push(ring_buffer *buffer, VALUE value) {
66
+ // TRACE_RING_BUFFER("ring_buffer_push", buffer);
67
+ // INSPECT(value);
68
+ if (buffer->count == buffer->size) ring_buffer_resize(buffer);
69
+
70
+ buffer->entries[buffer->tail] = value;
71
+ buffer->tail = (buffer->tail + 1) % buffer->size;
72
+ buffer->count++;
73
+ }
74
+
75
+ void ring_buffer_mark(ring_buffer *buffer) {
76
+ for (unsigned int i = 0; i < buffer->count; i++)
77
+ rb_gc_mark(buffer->entries[(buffer->head + i) % buffer->size]);
78
+ }
79
+
80
+ void ring_buffer_shift_each(ring_buffer *buffer) {
81
+ // TRACE_RING_BUFFER("ring_buffer_shift_each", buffer);
82
+
83
+ for (unsigned int i = 0; i < buffer->count; i++)
84
+ rb_yield(buffer->entries[(buffer->head + i) % buffer->size]);
85
+
86
+ buffer->count = buffer->head = buffer->tail = 0;
87
+ }
88
+
89
+ VALUE ring_buffer_shift_all(ring_buffer *buffer) {
90
+ // TRACE_RING_BUFFER("ring_buffer_all", buffer);
91
+ VALUE array = rb_ary_new_capa(buffer->count);
92
+ for (unsigned int i = 0; i < buffer->count; i++)
93
+ rb_ary_push(array, buffer->entries[(buffer->head + i) % buffer->size]);
94
+ buffer->count = buffer->head = buffer->tail = 0;
95
+ return array;
96
+ }
97
+
98
+ void ring_buffer_delete_at(ring_buffer *buffer, unsigned int idx) {
99
+ for (unsigned int idx2 = idx; idx2 != buffer->tail; idx2 = (idx2 + 1) % buffer->size) {
100
+ buffer->entries[idx2] = buffer->entries[(idx2 + 1) % buffer->size];
101
+ }
102
+ buffer->count--;
103
+ buffer->tail = (buffer->tail - 1) % buffer->size;
104
+ }
105
+
106
+ void ring_buffer_delete(ring_buffer *buffer, VALUE value) {
107
+ // TRACE_RING_BUFFER("ring_buffer_delete", buffer);
108
+ for (unsigned int i = 0; i < buffer->count; i++) {
109
+ unsigned int idx = (buffer->head + i) % buffer->size;
110
+ if (buffer->entries[idx] == value) {
111
+ ring_buffer_delete_at(buffer, idx);
112
+ return;
113
+ }
114
+ }
115
+ }
116
+
117
+ void ring_buffer_clear(ring_buffer *buffer) {
118
+ // TRACE_RING_BUFFER("ring_buffer_clear", buffer);
119
+ buffer->count = buffer->head = buffer->tail = 0;
120
+ }
@@ -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(cLibevQueue, 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;
@@ -61,7 +60,7 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
61
60
  }
62
61
 
63
62
  queue = rb_ivar_get(self, ID_run_queue);
64
- rb_ary_push(queue, fiber);
63
+ LibevQueue_push(queue, fiber);
65
64
  rb_ivar_set(fiber, ID_runnable, Qtrue);
66
65
 
67
66
  if (rb_thread_current() != self) {
@@ -88,13 +87,13 @@ VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value)
88
87
 
89
88
  // if fiber is already scheduled, remove it from the run queue
90
89
  if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
91
- rb_ary_delete(queue, fiber);
90
+ LibevQueue_delete(queue, fiber);
92
91
  } else {
93
92
  rb_ivar_set(fiber, ID_runnable, Qtrue);
94
93
  }
95
94
 
96
95
  // the fiber is given priority by putting it at the front of the run queue
97
- rb_ary_unshift(queue, fiber);
96
+ LibevQueue_unshift(queue, fiber);
98
97
 
99
98
  if (rb_thread_current() != self) {
100
99
  // if the fiber scheduling is done across threads, we need to make sure the
@@ -124,7 +123,7 @@ VALUE Thread_switch_fiber(VALUE self) {
124
123
 
125
124
  ref_count = LibevAgent_ref_count(agent);
126
125
  while (1) {
127
- next_fiber = rb_ary_shift(queue);
126
+ next_fiber = LibevQueue_shift_no_wait(queue);
128
127
  if (next_fiber != Qnil) {
129
128
  if (ref_count > 0) {
130
129
  // this mechanism prevents event starvation in case the run queue never
@@ -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
+ LibevQueue_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
+ LibevQueue_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");
@@ -49,7 +49,7 @@ module Polyphony
49
49
  end
50
50
 
51
51
  def run_queued_task
52
- (block, watcher) = @task_queue.pop
52
+ (block, watcher) = @task_queue.shift
53
53
  result = block.()
54
54
  watcher&.signal(result)
55
55
  rescue Exception => e
@@ -107,6 +107,15 @@ module ::Kernel
107
107
  $stdin.gets
108
108
  end
109
109
 
110
+ alias_method :orig_p, :p
111
+ def p(*args)
112
+ strs = args.inject([]) do |m, a|
113
+ m << a.inspect << "\n"
114
+ end
115
+ STDOUT.write *strs
116
+ args.size == 1 ? args.first : args
117
+ end
118
+
110
119
  alias_method :orig_system, :system
111
120
  def system(*args)
112
121
  Open3.popen2(*args) do |i, o, _t|
@@ -189,7 +189,7 @@ module Polyphony
189
189
  end
190
190
 
191
191
  def receive_pending
192
- @mailbox.shift_each
192
+ @mailbox.shift_all
193
193
  end
194
194
  end
195
195
 
@@ -221,6 +221,13 @@ module Polyphony
221
221
  def await_all_children
222
222
  return unless @children && !@children.empty?
223
223
 
224
+ # @results = @children.dup
225
+ # @on_child_done = proc do |c, r|
226
+ # @results[c] = r
227
+ # self.schedule if @children.empty?
228
+ # end
229
+ # suspend
230
+ # @results.values
224
231
  Fiber.await(*@children.keys)
225
232
  end
226
233
 
@@ -108,19 +108,23 @@ class ::IO
108
108
  end
109
109
 
110
110
  alias_method :orig_readpartial, :read
111
- def readpartial(len)
111
+ def readpartial(len, str = nil)
112
112
  @read_buffer ||= +''
113
113
  result = Thread.current.agent.read(self, @read_buffer, len, false)
114
114
  raise EOFError unless result
115
115
 
116
- already_read = @read_buffer
116
+ if str
117
+ str << @read_buffer
118
+ else
119
+ str = @read_buffer
120
+ end
117
121
  @read_buffer = +''
118
- already_read
122
+ str
119
123
  end
120
124
 
121
125
  alias_method :orig_write, :write
122
- def write(str)
123
- Thread.current.agent.write(self, str)
126
+ def write(str, *args)
127
+ Thread.current.agent.write(self, str, *args)
124
128
  end
125
129
 
126
130
  alias_method :orig_write_chevron, :<<
@@ -166,16 +170,13 @@ class ::IO
166
170
  return
167
171
  end
168
172
 
169
- s = args.each_with_object(+'') do |a, str|
170
- if a.is_a?(Array)
171
- a.each { |a2| str << a2.to_s << "\n" }
172
- else
173
- a = a.to_s
174
- str << a
175
- str << "\n" unless a =~ /\n$/
176
- end
173
+ strs = args.inject([]) do |m, a|
174
+ a = a.to_s
175
+ m << a
176
+ m << "\n" unless a =~ /\n$/
177
+ m
177
178
  end
178
- write s
179
+ write *strs
179
180
  nil
180
181
  end
181
182
 
@@ -193,7 +194,7 @@ class ::IO
193
194
 
194
195
  alias_method :orig_write_nonblock, :write_nonblock
195
196
  def write_nonblock(string, _options = {})
196
- write(string, 0)
197
+ write(string)
197
198
  end
198
199
 
199
200
  alias_method :orig_read_nonblock, :read_nonblock
@@ -5,6 +5,16 @@ require 'socket'
5
5
  require_relative './io'
6
6
  require_relative '../core/thread_pool'
7
7
 
8
+ class ::BasicSocket
9
+ def write_nonblock(string, _options = {})
10
+ write(string)
11
+ end
12
+
13
+ def read_nonblock(maxlen, str = nil, _options = {})
14
+ readpartial(maxlen, str)
15
+ end
16
+ end
17
+
8
18
  # Socket overrides (eventually rewritten in C)
9
19
  class ::Socket
10
20
  def accept
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.43.4'
4
+ VERSION = '0.43.5'
5
5
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'fiber'
5
+ require_relative '../lib/polyphony_ext'
6
+
7
+ queue = Polyphony::LibevQueue.new
8
+
9
+ queue.push :a
10
+ queue.push :b
11
+ queue.push :c
12
+ p [queue.shift_no_wait]
13
+ queue.push :d
14
+ p [queue.shift_no_wait]
15
+ p [queue.shift_no_wait]
16
+ p [queue.shift_no_wait]
17
+ p [queue.shift_no_wait]
18
+
19
+ queue.unshift :e
20
+ p [queue.shift_no_wait]
21
+
22
+ queue.push :f
23
+ queue.push :g
24
+ p [queue.shift_no_wait]
@@ -211,13 +211,13 @@ class SpinLoopTest < MiniTest::Test
211
211
 
212
212
  def test_spin_loop_location
213
213
  location = /^#{__FILE__}:#{__LINE__ + 1}/
214
- f = spin_loop {}
214
+ f = spin_loop { snooze }
215
215
 
216
216
  assert_match location, f.location
217
217
  end
218
218
 
219
219
  def test_spin_loop_tag
220
- f = spin_loop(:my_loop) {}
220
+ f = spin_loop(:my_loop) { snooze }
221
221
 
222
222
  assert_equal :my_loop, f.tag
223
223
  end
@@ -30,6 +30,14 @@ class IOTest < MiniTest::Test
30
30
  assert_equal 'hello', msg
31
31
  end
32
32
 
33
+ def test_write_multiple_arguments
34
+ i, o = IO.pipe
35
+ count = o.write('a', 'b', "\n", 'c')
36
+ assert_equal 4, count
37
+ o.close
38
+ assert_equal "ab\nc", i.read
39
+ end
40
+
33
41
  def test_that_double_chevron_method_returns_io
34
42
  assert_equal @o, @o << 'foo'
35
43
 
@@ -121,7 +129,7 @@ class IOClassMethodsTest < MiniTest::Test
121
129
  assert_equal "end\n", lines[-1]
122
130
  end
123
131
 
124
- def test_read
132
+ def test_read_class_method
125
133
  s = IO.read(__FILE__)
126
134
  assert_kind_of String, s
127
135
  assert(!s.empty?)
@@ -144,7 +152,7 @@ class IOClassMethodsTest < MiniTest::Test
144
152
 
145
153
  WRITE_DATA = "foo\nbar קוקו"
146
154
 
147
- def test_write
155
+ def test_write_class_method
148
156
  fn = '/tmp/test_write'
149
157
  FileUtils.rm(fn) rescue nil
150
158
 
@@ -8,7 +8,7 @@ class QueueTest < MiniTest::Test
8
8
  @queue = Polyphony::Queue.new
9
9
  end
10
10
 
11
- def test_pop
11
+ def test_push_shift
12
12
  spin {
13
13
  @queue << 42
14
14
  }
@@ -21,6 +21,18 @@ class QueueTest < MiniTest::Test
21
21
  assert_equal [1, 2, 3, 4], buf
22
22
  end
23
23
 
24
+ def test_unshift
25
+ @queue.push 1
26
+ @queue.push 2
27
+ @queue.push 3
28
+ @queue.unshift 4
29
+
30
+ buf = []
31
+ buf << @queue.shift while !@queue.empty?
32
+
33
+ assert_equal [4, 1, 2, 3], buf
34
+ end
35
+
24
36
  def test_multiple_waiters
25
37
  a = spin { @queue.shift }
26
38
  b = spin { @queue.shift }
@@ -41,6 +53,19 @@ class QueueTest < MiniTest::Test
41
53
  buf = []
42
54
  @queue.shift_each { |i| buf << i }
43
55
  assert_equal [1, 2, 3, 4], buf
56
+
57
+ buf = []
58
+ @queue.shift_each { |i| buf << i }
59
+ assert_equal [], buf
60
+ end
61
+
62
+ def test_shift_all
63
+ (1..4).each { |i| @queue << i }
64
+ buf = @queue.shift_all
65
+ assert_equal [1, 2, 3, 4], buf
66
+
67
+ buf = @queue.shift_all
68
+ assert_equal [], buf
44
69
  end
45
70
 
46
71
  def test_empty?
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.4
4
+ version: 0.43.5
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-09 00:00:00.000000000 Z
11
+ date: 2020-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -353,6 +353,7 @@ files:
353
353
  - examples/io/xx-tcpsocket.rb
354
354
  - examples/performance/fs_read.rb
355
355
  - examples/performance/mem-usage.rb
356
+ - examples/performance/messaging.rb
356
357
  - examples/performance/multi_snooze.rb
357
358
  - examples/performance/snooze.rb
358
359
  - examples/performance/snooze_raw.rb
@@ -391,6 +392,8 @@ files:
391
392
  - ext/polyphony/polyphony.c
392
393
  - ext/polyphony/polyphony.h
393
394
  - ext/polyphony/polyphony_ext.c
395
+ - ext/polyphony/ring_buffer.c
396
+ - ext/polyphony/ring_buffer.h
394
397
  - ext/polyphony/thread.c
395
398
  - ext/polyphony/tracing.c
396
399
  - lib/polyphony.rb
@@ -420,6 +423,7 @@ files:
420
423
  - test/coverage.rb
421
424
  - test/eg.rb
422
425
  - test/helper.rb
426
+ - test/q.rb
423
427
  - test/run.rb
424
428
  - test/stress.rb
425
429
  - test/test_agent.rb