polyphony 0.25 → 0.26

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: c47fca95a744d36163d87e80cd0e56a23ae9a613d1df4970475db7a7b8840ba3
4
- data.tar.gz: eebe2e1f3dd6fd3efe80cd60444c881978d7d0ff34d8c381fdd4324e77ef14a6
3
+ metadata.gz: d97b4ef9db1f6463947a3d5e0e300ee0155bd576ee878a4866557a2cc97cf785
4
+ data.tar.gz: 8fa0f7e075ce3c7bd9cf28270d06b0fbfe399657571aeaa23773400cb6535f58
5
5
  SHA512:
6
- metadata.gz: a266b7d3ef74d98a3a43bf4f821e3894295bae084a4ca7f067c3e63b1600ea6c6191e9fe39caa72166c0dba759bca5dc0cb47312de76e45b7028ce3c231f5166
7
- data.tar.gz: d3cb27c65c01fa8984d88ba047fbfb30d08660a5afcfe3636af6302e47d3b45b5ba3908419eb8a867ed7ed96aa83d3ec03eadf7dd03542de04095fe7cf64c5f5
6
+ metadata.gz: ac3eb507f068f54f70f5e1a79d964c39943f7ac7cfa945da02847f59ec53fc23739b2633428258d0caaf91bfdacc25cfcb6fa73175f8d43c7ed38fdd81f0a360
7
+ data.tar.gz: 75402a0ca525da7aa02fbe933c4f76328d9272ecb9dd7e404003b85a92e327882ca50d63eed49140593628cf0989d1a8cf7aac6abab5ad766124b1e5e7d00348
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ 0.26 2020-01-12
2
+ ---------------
3
+
4
+ * Optimize `IO#read_watcher`, `IO#write_watcher`
5
+ * Implement `Fiber#raise`
6
+ * Fix `Kernel#gets` with `ARGV`
7
+ * Return `[pid, exit_status]` from `Gyro::Child#await`
8
+
1
9
  0.25 2020-01-10
2
10
  ---------------
3
11
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.25)
4
+ polyphony (0.26)
5
5
  modulation (~> 1.0)
6
6
 
7
7
  GEM
@@ -57,4 +57,4 @@ DEPENDENCIES
57
57
  simplecov (= 0.17.1)
58
58
 
59
59
  BUNDLED WITH
60
- 2.1.3
60
+ 2.1.4
data/TODO.md CHANGED
@@ -1,27 +1,26 @@
1
- ## 0.25 Merge Coprocess functionality into Fiber
1
+ ## 0.26 Real IO#gets and IO#read
2
2
 
3
- - Merge `Coprocess` functionality into `Fiber`
4
- - Get rid of the duality Coprocess - Fiber
5
- - exception handling
6
- - interrupting (`MoveOn`/`Cancel`)
7
- - message passing (`receive`/`send`)
8
- - Clear separation between scheduling code and event handling code
9
- - Check performance difference using `http_server.rb`. We should expect a
10
- modest increase in throughput, as well as significantly less memory usage.
11
- - Handle calls to `#sleep` without duration (should just `#suspend`)
12
-
13
- ## 0.26 Move Other interface code into separate gem
14
-
15
- - Pull out redis/postgres code, put into new `polyphony-contrib` gem
3
+ - More tests
4
+ - Implement some basic stuff missing:
5
+ - override `IO#eof?` since it too reads into buffer
6
+ - real `IO#gets` (with buffering)
7
+ - `IO#read` (read to EOF)
8
+ - `IO.foreach`
9
+ - `Process.waitpid`
16
10
 
17
11
  ## 0.27 Working Sinatra application
18
12
 
13
+ - Pull out redis/postgres code, put into new `polyphony-xxx` gems
19
14
  - app with database access (postgresql)
20
15
  - benchmarks!
21
16
 
22
- ## 0.28 Support for multi-threading
17
+ ## 0.28 Sidekick
18
+
19
+ Plan of action:
23
20
 
24
- - Separate event loop for each thread
21
+ - fork sidekiq, make adjustments to Polyphony code
22
+ - test performance
23
+ - proceed from there
25
24
 
26
25
  ## 0.29 Testing && Docs
27
26
 
data/ext/gyro/child.c CHANGED
@@ -89,7 +89,11 @@ void Gyro_Child_callback(struct ev_loop *ev_loop, struct ev_child *ev_child, int
89
89
 
90
90
  if (child->fiber != Qnil) {
91
91
  VALUE fiber = child->fiber;
92
- VALUE resume_value = INT2NUM(child->pid);
92
+ int exit_status = ev_child->rstatus >> 8; // weird, why should we do this?
93
+
94
+ VALUE resume_value = rb_ary_new_from_args(
95
+ 2, INT2NUM(ev_child->rpid), INT2NUM(exit_status)
96
+ );
93
97
  child->fiber = Qnil;
94
98
  Gyro_schedule_fiber(fiber, resume_value);
95
99
  }
data/ext/gyro/gyro.c CHANGED
@@ -31,13 +31,11 @@ ID ID_clear;
31
31
  ID ID_each;
32
32
  ID ID_inspect;
33
33
  ID ID_raise;
34
- ID ID_read_watcher;
35
34
  ID ID_running;
36
35
  ID ID_scheduled;
37
36
  ID ID_scheduled_next;
38
37
  ID ID_scheduled_value;
39
38
  ID ID_transfer;
40
- ID ID_write_watcher;
41
39
  ID ID_R;
42
40
  ID ID_W;
43
41
  ID ID_RW;
@@ -75,13 +73,11 @@ void Init_Gyro() {
75
73
  ID_each = rb_intern("each");
76
74
  ID_inspect = rb_intern("inspect");
77
75
  ID_raise = rb_intern("raise");
78
- ID_read_watcher = rb_intern("read_watcher");
79
76
  ID_running = rb_intern("@running");
80
77
  ID_scheduled = rb_intern("scheduled");
81
78
  ID_scheduled_next = rb_intern("scheduled_next");
82
79
  ID_scheduled_value = rb_intern("scheduled_value");
83
80
  ID_transfer = rb_intern("transfer");
84
- ID_write_watcher = rb_intern("write_watcher");
85
81
  ID_R = rb_intern("r");
86
82
  ID_W = rb_intern("w");
87
83
  ID_RW = rb_intern("rw");
data/ext/gyro/gyro.h CHANGED
@@ -44,10 +44,8 @@ extern ID ID_clear;
44
44
  extern ID ID_each;
45
45
  extern ID ID_inspect;
46
46
  extern ID ID_raise;
47
- extern ID ID_read_watcher;
48
47
  extern ID ID_scheduled_value;
49
48
  extern ID ID_transfer;
50
- extern ID ID_write_watcher;
51
49
  extern ID ID_R;
52
50
  extern ID ID_W;
53
51
  extern ID ID_RW;
data/ext/gyro/gyro_ext.c CHANGED
@@ -1,5 +1,4 @@
1
1
  #include "gyro.h"
2
- #include "../libev/ev.c"
3
2
 
4
3
  void Init_Gyro();
5
4
  void Init_Gyro_Async();
data/ext/gyro/io.c CHANGED
@@ -32,6 +32,11 @@ static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io);
32
32
  static VALUE IO_write(int argc, VALUE *argv, VALUE io);
33
33
  static VALUE IO_write_chevron(VALUE io, VALUE str);
34
34
 
35
+ ID ID_read_watcher;
36
+ ID ID_write_watcher;
37
+ VALUE SYM_r;
38
+ VALUE SYM_w;
39
+
35
40
  void Init_Gyro_IO() {
36
41
  cGyro_IO = rb_define_class_under(mGyro, "IO", rb_cData);
37
42
  rb_define_alloc_func(cGyro_IO, Gyro_IO_allocate);
@@ -48,6 +53,11 @@ void Init_Gyro_IO() {
48
53
  rb_define_method(cIO, "<<", IO_write_chevron, 1);
49
54
  rb_define_method(cIO, "read_watcher", IO_read_watcher, 0);
50
55
  rb_define_method(cIO, "write_watcher", IO_write_watcher, 0);
56
+
57
+ ID_read_watcher = rb_intern("@read_watcher");
58
+ ID_write_watcher = rb_intern("@write_watcher");
59
+ SYM_r = ID2SYM(rb_intern("r"));
60
+ SYM_w = ID2SYM(rb_intern("w"));
51
61
  }
52
62
 
53
63
  static const rb_data_type_t Gyro_IO_type = {
@@ -286,11 +296,28 @@ static VALUE IO_read(int argc, VALUE *argv, VALUE io) {
286
296
  return str;
287
297
  }
288
298
 
299
+ #define READ_DATA_PENDING_COUNT(fptr) ((fptr)->rbuf.len)
300
+ #define MEMMOVE(p1,p2,type,n) memmove((p1), (p2), sizeof(type)*(size_t)(n))
301
+
302
+ static long
303
+ read_buffered_data(char *ptr, long len, rb_io_t *fptr)
304
+ {
305
+ int n;
306
+
307
+ n = READ_DATA_PENDING_COUNT(fptr);
308
+ if (n <= 0) return 0;
309
+ if (n > len) n = (int)len;
310
+ MEMMOVE(ptr, fptr->rbuf.ptr+fptr->rbuf.off, char, n);
311
+ fptr->rbuf.off += n;
312
+ fptr->rbuf.len -= n;
313
+ return n;
314
+ }
315
+
289
316
  static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io) {
290
317
  VALUE underlying_io = rb_iv_get(io, "@io");
291
318
  if (!NIL_P(underlying_io)) io = underlying_io;
292
319
 
293
- long len = argc == 1 ? NUM2LONG(argv[0]) : 8192;
320
+ long len = argc >= 1 ? NUM2LONG(argv[0]) : 8192;
294
321
 
295
322
  rb_io_t *fptr;
296
323
  long n;
@@ -312,21 +339,24 @@ static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io) {
312
339
  if (len == 0)
313
340
  return str;
314
341
 
315
- while (1) {
316
- n = read(fptr->fd, RSTRING_PTR(str), len);
317
- if (n < 0) {
318
- int e = errno;
319
- if (e == EWOULDBLOCK || e == EAGAIN) {
320
- if (read_watcher == Qnil)
321
- read_watcher = IO_read_watcher(io);
322
- Gyro_IO_await(read_watcher);
342
+ n = read_buffered_data(RSTRING_PTR(str), len, fptr);
343
+ if (n <= 0) {
344
+ while (1) {
345
+ n = read(fptr->fd, RSTRING_PTR(str), len);
346
+ if (n < 0) {
347
+ int e = errno;
348
+ if (e == EWOULDBLOCK || e == EAGAIN) {
349
+ if (read_watcher == Qnil)
350
+ read_watcher = IO_read_watcher(io);
351
+ Gyro_IO_await(read_watcher);
352
+ }
353
+ else
354
+ rb_syserr_fail(e, strerror(e));
355
+ // rb_syserr_fail_path(e, fptr->pathv);
323
356
  }
324
357
  else
325
- rb_syserr_fail(e, strerror(e));
326
- // rb_syserr_fail_path(e, fptr->pathv);
358
+ break;
327
359
  }
328
- else
329
- break;
330
360
  }
331
361
 
332
362
  io_set_read_length(str, n, shrinkable);
@@ -406,19 +436,21 @@ static VALUE IO_write_chevron(VALUE io, VALUE str) {
406
436
  }
407
437
 
408
438
  VALUE IO_read_watcher(VALUE self) {
409
- VALUE watcher = rb_iv_get(self, "@read_watcher");
439
+ VALUE watcher = rb_ivar_get(self, ID_read_watcher);
410
440
  if (watcher == Qnil) {
411
- watcher = rb_funcall(cGyro_IO, rb_intern("new"), 2, self, ID2SYM(rb_intern("r")));
412
- rb_iv_set(self, "@read_watcher", watcher);
441
+ VALUE args[] = {self, SYM_r};
442
+ watcher = rb_class_new_instance(2, args, cGyro_IO);
443
+ rb_ivar_set(self, ID_read_watcher, watcher);
413
444
  }
414
445
  return watcher;
415
446
  }
416
447
 
417
448
  VALUE IO_write_watcher(VALUE self) {
418
- VALUE watcher = rb_iv_get(self, "@write_watcher");
449
+ VALUE watcher = rb_ivar_get(self, ID_write_watcher);
419
450
  if (watcher == Qnil) {
420
- watcher = rb_funcall(cGyro_IO, rb_intern("new"), 2, self, ID2SYM(rb_intern("w")));
421
- rb_iv_set(self, "@write_watcher", watcher);
451
+ VALUE args[] = {self, SYM_w};
452
+ watcher = rb_class_new_instance(2, args, cGyro_IO);
453
+ rb_ivar_set(self, ID_write_watcher, watcher);
422
454
  }
423
455
  return watcher;
424
456
  }
data/ext/gyro/libev.c ADDED
@@ -0,0 +1,2 @@
1
+ #include "libev.h"
2
+ #include "../libev/ev.c"
data/lib/polyphony.rb CHANGED
@@ -65,7 +65,6 @@ module Polyphony
65
65
  end
66
66
 
67
67
  def reset!
68
- # Fiber.root.scheduled_value = nil
69
68
  Gyro.reset!
70
69
  Fiber.reset!
71
70
  end
@@ -58,8 +58,6 @@ module ::Kernel
58
58
 
59
59
  alias_method :orig_backtick, :`
60
60
  def `(cmd)
61
- # $stdout.orig_puts '*' * 60
62
- # $stdout.orig_puts caller.join("\n")
63
61
  Open3.popen3(cmd) do |i, o, e, _t|
64
62
  i.close
65
63
  while (l = e.readpartial(8192))
@@ -70,25 +68,29 @@ module ::Kernel
70
68
  end
71
69
 
72
70
  ARGV_GETS_LOOP = proc do |calling_fiber|
73
- ARGV.each do |fn|
71
+ while (fn = ARGV.shift)
74
72
  File.open(fn, 'r') do |f|
75
73
  while (line = f.gets)
76
74
  calling_fiber = calling_fiber.transfer(line)
77
75
  end
78
76
  end
79
77
  end
78
+ nil
80
79
  rescue Exception => e
81
80
  calling_fiber.transfer(e)
82
81
  end
83
82
 
84
83
  alias_method :orig_gets, :gets
85
84
  def gets(*_args)
86
- return $stdin.gets if ARGV.empty?
85
+ if !ARGV.empty? || @gets_fiber
86
+ @gets_fiber ||= Fiber.new(&ARGV_GETS_LOOP)
87
+ result = @gets_fiber.alive? && @gets_fiber.safe_transfer(Fiber.current)
88
+ return result if result
87
89
 
88
- @gets_fiber ||= Fiber.new(&ARGV_GETS_LOOP)
89
- return @gets_fiber.safe_transfer(Fiber.current) if @gets_fiber.alive?
90
+ @gets_fiber = nil
91
+ end
90
92
 
91
- nil
93
+ $stdin.gets
92
94
  end
93
95
 
94
96
  alias_method :orig_system, :system
@@ -100,8 +102,6 @@ module ::Kernel
100
102
  end
101
103
  end
102
104
  true
103
- rescue SystemCallError
104
- nil
105
105
  end
106
106
  end
107
107
 
@@ -8,7 +8,7 @@ Exceptions = import '../core/exceptions'
8
8
  module FiberControl
9
9
  def await
10
10
  if @running == false
11
- return @result.is_a?(Exception) ? (raise @result) : @result
11
+ return @result.is_a?(Exception) ? (Kernel.raise @result) : @result
12
12
  end
13
13
 
14
14
  @waiting_fiber = Fiber.current
@@ -32,6 +32,21 @@ module FiberControl
32
32
  schedule Exceptions::Cancel.new
33
33
  snooze
34
34
  end
35
+
36
+ def raise(*args)
37
+ error = error_from_raise_args(args)
38
+ schedule error
39
+ snooze
40
+ end
41
+
42
+ def error_from_raise_args(args)
43
+ case (arg = args.shift)
44
+ when String then RuntimeError.new(arg)
45
+ when Class then arg.new(args.shift)
46
+ when Exception then arg
47
+ else RuntimeError.new
48
+ end
49
+ end
35
50
  end
36
51
 
37
52
  # Messaging functionality
@@ -109,7 +124,7 @@ class ::Fiber
109
124
  end
110
125
 
111
126
  def run(first_value)
112
- raise first_value if first_value.is_a?(Exception)
127
+ Kernel.raise first_value if first_value.is_a?(Exception)
113
128
 
114
129
  @running = true
115
130
  self.class.map[self] = true
@@ -156,11 +171,11 @@ class ::Fiber
156
171
  end
157
172
 
158
173
  def caller
159
- @caller ||= []
174
+ spin_caller = @caller || []
160
175
  if @calling_fiber
161
- @caller + @calling_fiber.caller
176
+ spin_caller + @calling_fiber.caller
162
177
  else
163
- @caller
178
+ spin_caller
164
179
  end
165
180
  end
166
181
  end
@@ -163,13 +163,26 @@ class ::IO
163
163
 
164
164
  alias_method :orig_write_nonblock, :write_nonblock
165
165
  def write_nonblock(string, _options = {})
166
- # STDOUT << '>'
167
166
  write(string, 0)
168
167
  end
169
168
 
170
169
  alias_method :orig_read_nonblock, :read_nonblock
171
170
  def read_nonblock(maxlen, buf = nil, _options = nil)
172
- # STDOUT << '<'
173
171
  buf ? readpartial(maxlen, buf) : readpartial(maxlen)
174
172
  end
173
+
174
+ alias_method :orig_read, :read
175
+ def read(length = nil, outbuf = nil)
176
+ if length
177
+ return outbuf ? readpartial(length) : readpartial(length, outbuf)
178
+ end
179
+
180
+ until eof?
181
+ result = outbuf ? readpartial(8192, outbuf) : readpartial(8192)
182
+ break unless result
183
+
184
+ outbuf = result
185
+ end
186
+ outbuf
187
+ end
175
188
  end
@@ -23,8 +23,8 @@ class ::OpenSSL::SSL::SSLSocket
23
23
  write_watcher = nil
24
24
  loop do
25
25
  case (result = read_nonblock(maxlen, buf, exception: false))
26
- when :wait_readable then (read_watcher ||= Gyro::IO.new(io, :r)).await
27
- when :wait_writable then (write_watcher ||= Gyro::IO.new(io, :w)).await
26
+ when :wait_readable then (read_watcher ||= io.read_watcher).await
27
+ when :wait_writable then (write_watcher ||= io.write_watcher).await
28
28
  else result
29
29
  end
30
30
  end
@@ -61,8 +61,8 @@ class ::OpenSSL::SSL::SSLSocket
61
61
  write_watcher = nil
62
62
  loop do
63
63
  case (result = write_nonblock(buf, exception: false))
64
- when :wait_readable then (read_watcher ||= Gyro::IO.new(io, :r)).await
65
- when :wait_writable then (write_watcher ||= Gyro::IO.new(io, :w)).await
64
+ when :wait_readable then (read_watcher ||= io.read_watcher).await
65
+ when :wait_writable then (write_watcher ||= io.write_watcher).await
66
66
  else result
67
67
  end
68
68
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.25'
4
+ VERSION = '0.26'
5
5
  end
data/polyphony.gemspec CHANGED
@@ -8,9 +8,10 @@ Gem::Specification.new do |s|
8
8
  s.author = 'Sharon Rosner'
9
9
  s.email = 'ciconia@gmail.com'
10
10
  s.files = `git ls-files`.split
11
- s.homepage = 'http://github.com/digital-fabric/polyphony'
11
+ s.homepage = 'https://dfab.gitbook.io/polyphony/'
12
12
  s.metadata = {
13
- "source_code_uri" => "https://github.com/digital-fabric/polyphony"
13
+ "source_code_uri" => "https://github.com/digital-fabric/polyphony",
14
+ "documentation_uri" => "https://dfab.gitbook.io/polyphony/"
14
15
  }
15
16
  s.rdoc_options = ["--title", "polyphony", "--main", "README.md"]
16
17
  s.extra_rdoc_files = ["README.md"]
data/test/coverage.rb CHANGED
@@ -3,13 +3,11 @@
3
3
  require 'coverage'
4
4
  require 'simplecov'
5
5
 
6
- class << SimpleCov::LinesClassifier
7
- alias_method :orig_whitespace_line?, :whitespace_line?
8
- def whitespace_line?(line)
9
- line.strip =~ /^(begin|end|ensure|else|\})|(\s*rescue\s.+)$/ || orig_whitespace_line?(line)
10
- end
11
- end
12
-
6
+ # Since we load code using Modulation, and the stock coverage gem does not
7
+ # calculate coverage for code loaded using `Kernel#eval` et al, we need to use
8
+ # the TracePoint API in order to trace execution. Here we monkey-patch the two
9
+ # main Coverage class methods, start and result to use TracePoint. Otherwise we
10
+ # let SimpleCov do its business.
13
11
  module Coverage
14
12
  EXCLUDE = %w{coverage eg helper run
15
13
  }.map { |n| File.expand_path("test/#{n}.rb") }
@@ -42,4 +40,15 @@ module Coverage
42
40
  end
43
41
  end
44
42
 
43
+ class << SimpleCov::LinesClassifier
44
+ alias_method :orig_whitespace_line?, :whitespace_line?
45
+ def whitespace_line?(line)
46
+ # apparently TracePoint tracing does not cover lines including only keywords
47
+ # such as begin end etc, so here we mark those lines as whitespace, so they
48
+ # won't count towards the coverage score.
49
+ line.strip =~ /^(begin|end|ensure|else|\})|(\s*rescue\s.+)$/ ||
50
+ orig_whitespace_line?(line)
51
+ end
52
+ end
53
+
45
54
  SimpleCov.start
data/test/test_ext.rb ADDED
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ class ExceptionTest < MiniTest::Test
6
+ def test_sanitize
7
+ prev_disable = Exception.__disable_sanitized_backtrace__
8
+ Exception.__disable_sanitized_backtrace__ = false
9
+
10
+ begin
11
+ lineno = __LINE__ + 1
12
+ spin { raise 'foo' }
13
+ suspend
14
+ rescue => e
15
+ end
16
+
17
+ assert_kind_of Exception, e
18
+ backtrace = e.backtrace
19
+ location = "#{__FILE__}:#{lineno}"
20
+ assert_match /#{location}/, backtrace[0]
21
+ polyphony_re = /^#{Exception::POLYPHONY_DIR}/
22
+ assert_equal [], backtrace.select { |l| l =~ polyphony_re }
23
+
24
+ Exception.__disable_sanitized_backtrace__ = true
25
+ begin
26
+ lineno = __LINE__ + 1
27
+ spin { raise 'foo' }
28
+ suspend
29
+ rescue => e
30
+ end
31
+
32
+ assert_kind_of Exception, e
33
+ backtrace = e.backtrace
34
+ location = "#{__FILE__}:#{lineno}"
35
+ assert_match /#{location}/, backtrace[0]
36
+ assert_match /lib\/polyphony\/extensions\/fiber.rb/, backtrace[1]
37
+ assert_match /lib\/polyphony\/extensions\/fiber.rb/, backtrace[2]
38
+ ensure
39
+ Exception.__disable_sanitized_backtrace__ = prev_disable
40
+ end
41
+
42
+ end
43
+
44
+ class ProcessTest < MiniTest::Test
45
+ def test_detach
46
+ pid = Polyphony.fork { sleep 0.05; exit! 42 }
47
+ buffer = []
48
+ spin { 3.times { |i| buffer << i; snooze } }
49
+ w = Process.detach(pid)
50
+
51
+ assert_kind_of Fiber, w
52
+ result = w.await
53
+
54
+ assert_equal [0, 1, 2], buffer
55
+ assert_equal [pid, 42], result
56
+ end
57
+ end
58
+
59
+ class KernelTest < MiniTest::Test
60
+ def test_backticks
61
+ buffer = []
62
+ spin { 3.times { |i| buffer << i; snooze } }
63
+ data = `sleep 0.01; echo hello`
64
+
65
+ assert_equal [0, 1, 2], buffer
66
+ assert_equal "hello\n", data
67
+ end
68
+
69
+ def test_backticks_stderr
70
+ prev_stderr = $stderr
71
+ $stderr = err_io = StringIO.new
72
+
73
+ data = `>&2 echo "error"`
74
+ $stderr.rewind
75
+ $stderr = prev_stderr
76
+
77
+ assert_nil data
78
+ assert_equal "error\n", err_io.read
79
+ ensure
80
+ $stderr = prev_stderr
81
+ end
82
+
83
+ def test_gets
84
+ prev_stdin = $stdin
85
+ i, o = IO.pipe
86
+ $stdin = i
87
+
88
+ spin { o << "hello\n" }
89
+ s = gets
90
+
91
+ assert_equal "hello\n", s
92
+ ensure
93
+ $stdin = prev_stdin
94
+ end
95
+
96
+ def test_gets_from_argv
97
+ prev_stdin = $stdin
98
+
99
+ ARGV << __FILE__
100
+ ARGV << __FILE__
101
+
102
+ contents = IO.read(__FILE__).lines
103
+ count = contents.size
104
+
105
+ buffer = []
106
+
107
+ (count * 2).times { buffer << gets }
108
+ assert_equal contents * 2, buffer
109
+
110
+ i, o = IO.pipe
111
+ $stdin = i
112
+
113
+ spin { o << "hello\n" }
114
+ s = gets
115
+
116
+ assert_equal "hello\n", s
117
+ ensure
118
+ $stdin = prev_stdin
119
+ end
120
+
121
+ def test_gets_from_bad_argv
122
+ prev_stdin = $stdin
123
+
124
+ ARGV << 'foobar'
125
+
126
+ begin
127
+ gets
128
+ rescue => e
129
+ end
130
+
131
+ assert_kind_of Errno::ENOENT, e
132
+ ensure
133
+ $stdin = prev_stdin
134
+ end
135
+
136
+ def test_system
137
+ prev_stdout = $stdout
138
+ $stdout = out_io = StringIO.new
139
+
140
+ buffer = []
141
+ spin { 3.times { |i| buffer << i; snooze } }
142
+ system('sleep 0.01; echo hello')
143
+ out_io.rewind
144
+ $stdout = prev_stdout
145
+
146
+ assert_equal [0, 1, 2], buffer
147
+ assert_equal "hello\n", out_io.read
148
+ ensure
149
+ $stdout = prev_stdout
150
+ end
151
+ end
152
+
153
+ class TimeoutTest < MiniTest::Test
154
+ def test_that_timeout_yields_to_other_fibers
155
+ buffer = []
156
+ spin { 3.times { |i| buffer << i; snooze } }
157
+ assert_raises(Timeout::Error) { Timeout.timeout(0.05) { sleep 1 } }
158
+ assert_equal [0, 1, 2], buffer
159
+ end
160
+
161
+ class MyTimeout < Exception
162
+ end
163
+
164
+ def test_that_timeout_method_accepts_custom_error_class_and_message
165
+ buffer = []
166
+ spin { 3.times { |i| buffer << i; snooze } }
167
+ begin
168
+ Timeout.timeout(0.05, MyTimeout, 'foo') { sleep 1 }
169
+ rescue Exception => e
170
+ end
171
+
172
+ assert_kind_of MyTimeout, e
173
+ assert_equal 'foo', e.message
174
+ end
175
+ end
data/test/test_fiber.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require_relative 'helper'
4
4
 
5
5
  class FiberTest < MiniTest::Test
6
- def test_that_new_spun_fiber_starts_in_suspended_state
6
+ def test_spin_initial_state
7
7
  result = nil
8
8
  f = Fiber.spin { result = 42 }
9
9
  assert_nil result
@@ -13,7 +13,7 @@ class FiberTest < MiniTest::Test
13
13
  f&.stop
14
14
  end
15
15
 
16
- def test_that_await_blocks_until_fiber_is_done
16
+ def test_await
17
17
  result = nil
18
18
  f = Fiber.spin do
19
19
  snooze
@@ -25,14 +25,14 @@ class FiberTest < MiniTest::Test
25
25
  f&.stop
26
26
  end
27
27
 
28
- def test_that_await_returns_the_fibers_return_value
28
+ def test_await_return_value
29
29
  f = Fiber.spin { %i[foo bar] }
30
30
  assert_equal %i[foo bar], f.await
31
31
  ensure
32
32
  f&.stop
33
33
  end
34
34
 
35
- def test_that_await_raises_error_raised_by_fiber
35
+ def test_await_with_error
36
36
  result = nil
37
37
  f = Fiber.spin { raise 'foo' }
38
38
  begin
@@ -46,7 +46,123 @@ class FiberTest < MiniTest::Test
46
46
  f&.stop
47
47
  end
48
48
 
49
- def test_that_running_fiber_can_be_cancelled
49
+ def test_raise
50
+ result = []
51
+ error = nil
52
+ f = Fiber.spin do
53
+ result << 1
54
+ 2.times { snooze }
55
+ result << 2
56
+ end
57
+ defer { f.raise }
58
+ assert_equal 0, result.size
59
+ begin
60
+ f.await
61
+ rescue Exception => e
62
+ error = e
63
+ end
64
+ assert_equal 1, result.size
65
+ assert_equal 1, result[0]
66
+ assert_kind_of RuntimeError, error
67
+ ensure
68
+ f&.stop
69
+ end
70
+
71
+ class MyError < RuntimeError
72
+ end
73
+
74
+ def test_raise_with_error_class
75
+ result = []
76
+ error = nil
77
+ f = Fiber.spin do
78
+ result << 1
79
+ 2.times { snooze }
80
+ result << 2
81
+ end
82
+ defer { f.raise MyError }
83
+ assert_equal 0, result.size
84
+ begin
85
+ f.await
86
+ rescue Exception => e
87
+ error = e
88
+ end
89
+ assert_equal 1, result.size
90
+ assert_equal 1, result[0]
91
+ assert_kind_of MyError, error
92
+ ensure
93
+ f&.stop
94
+ end
95
+
96
+ def test_raise_with_error_class_and_message
97
+ result = []
98
+ error = nil
99
+ f = Fiber.spin do
100
+ result << 1
101
+ 2.times { snooze }
102
+ result << 2
103
+ end
104
+ defer { f.raise(MyError, 'foo') }
105
+ assert_equal 0, result.size
106
+ begin
107
+ f.await
108
+ rescue Exception => e
109
+ error = e
110
+ end
111
+ assert_equal 1, result.size
112
+ assert_equal 1, result[0]
113
+ assert_kind_of MyError, error
114
+ assert_equal 'foo', error.message
115
+ ensure
116
+ f&.stop
117
+ end
118
+
119
+ def test_raise_with_message
120
+ result = []
121
+ error = nil
122
+ f = Fiber.spin do
123
+ result << 1
124
+ 2.times { snooze }
125
+ result << 2
126
+ end
127
+ defer { f.raise 'foo' }
128
+ assert_equal 0, result.size
129
+ begin
130
+ f.await
131
+ rescue Exception => e
132
+ error = e
133
+ end
134
+ assert_equal 1, result.size
135
+ assert_equal 1, result[0]
136
+ assert_kind_of RuntimeError, error
137
+ assert_equal 'foo', error.message
138
+ ensure
139
+ f&.stop
140
+ end
141
+
142
+ def test_raise_with_exception
143
+ result = []
144
+ error = nil
145
+ f = Fiber.spin do
146
+ result << 1
147
+ 2.times { snooze }
148
+ result << 2
149
+ end
150
+ defer { f.raise MyError.new('bar') }
151
+ assert_equal 0, result.size
152
+ begin
153
+ f.await
154
+ rescue Exception => e
155
+ error = e
156
+ end
157
+ assert_equal 1, result.size
158
+ assert_equal 1, result[0]
159
+ assert_kind_of MyError, error
160
+ assert_equal 'bar', error.message
161
+ ensure
162
+ f&.stop
163
+ end
164
+
165
+ def test_cancel
50
166
  result = []
51
167
  error = nil
52
168
  f = Fiber.spin do
@@ -68,7 +184,7 @@ class FiberTest < MiniTest::Test
68
184
  f&.stop
69
185
  end
70
186
 
71
- def test_that_running_fiber_can_be_interrupted
187
+ def test_interrupt
72
188
  # that is, stopped without exception
73
189
  result = []
74
190
  f = Fiber.spin do
@@ -77,7 +193,7 @@ class FiberTest < MiniTest::Test
77
193
  result << 2
78
194
  3
79
195
  end
80
- defer { f.stop(42) }
196
+ defer { f.interrupt(42) }
81
197
 
82
198
  await_result = f.await
83
199
  assert_equal 1, result.size
@@ -86,56 +202,38 @@ class FiberTest < MiniTest::Test
86
202
  f&.stop
87
203
  end
88
204
 
89
- def test_that_fiber_can_be_awaited
90
- result = nil
91
- f2 = nil
92
- f1 = spin do
93
- f2 = Fiber.spin do
94
- snooze
95
- 42
96
- end
97
- result = f2.await
205
+ def test_stop
206
+ # that is, stopped without exception
207
+ result = []
208
+ f = Fiber.spin do
209
+ result << 1
210
+ 2.times { snooze }
211
+ result << 2
212
+ 3
98
213
  end
99
- suspend
100
- assert_equal 42, result
101
- ensure
102
- f1&.stop
103
- f2&.stop
104
- end
214
+ defer { f.stop(42) }
105
215
 
106
- def test_that_fiber_can_be_stopped
107
- result = nil
108
- f = spin do
109
- snooze
110
- result = 42
111
- end
112
- defer { f.interrupt }
113
- suspend
114
- assert_nil result
216
+ await_result = f.await
217
+ assert_equal 1, result.size
218
+ assert_equal 42, await_result
115
219
  ensure
116
220
  f&.stop
117
221
  end
118
222
 
119
- def test_that_fiber_can_be_cancelled
120
- result = nil
121
- f = spin do
122
- snooze
123
- result = 42
124
- rescue Polyphony::Cancel => e
125
- result = e
223
+ def test_interrupt_before_start
224
+ result = []
225
+ f = Fiber.spin do
226
+ result << 1
126
227
  end
127
- defer { f.cancel! }
128
-
129
- suspend
228
+ f.interrupt(42)
229
+ snooze
130
230
 
131
- assert_kind_of Polyphony::Cancel, result
132
- assert_kind_of Polyphony::Cancel, f.result
133
231
  assert_equal :dead, f.state
134
- ensure
135
- f&.stop
232
+ assert_equal [], result
233
+ assert_equal 42, f.result
136
234
  end
137
235
 
138
- def test_that_inner_fiber_can_be_interrupted
236
+ def test_interrupt_nested_fiber
139
237
  result = nil
140
238
  f2 = nil
141
239
  f1 = spin do
@@ -175,7 +273,7 @@ class FiberTest < MiniTest::Test
175
273
  f&.stop
176
274
  end
177
275
 
178
- def test_fiber_exception_propagation
276
+ def test_exception_bubbling
179
277
  # error is propagated to calling fiber
180
278
  raised_error = nil
181
279
  spin do
@@ -192,17 +290,7 @@ class FiberTest < MiniTest::Test
192
290
  assert_equal 'foo', raised_error.message
193
291
  end
194
292
 
195
- def test_that_fiber_can_be_interrupted_before_first_scheduling
196
- buffer = []
197
- f = spin { buffer << 1 }
198
- f.stop
199
-
200
- snooze
201
- assert !f.running?
202
- assert_equal [], buffer
203
- end
204
-
205
- def test_exception_propagation_for_orphan_fiber
293
+ def test_exception_bubling_for_orphan_fiber
206
294
  raised_error = nil
207
295
  spin do
208
296
  spin do
@@ -288,38 +376,6 @@ class FiberTest < MiniTest::Test
288
376
  assert_equal [42], values
289
377
  assert !f.running?
290
378
  end
291
-
292
- def test_interrupt
293
- f = spin do
294
- sleep 1
295
- :foo
296
- end
297
-
298
- snooze
299
- assert f.alive?
300
-
301
- f.interrupt :bar
302
- assert !f.running?
303
-
304
- assert_equal :bar, f.result
305
- end
306
-
307
- def test_cancel
308
- error = nil
309
- f = spin do
310
- sleep 1
311
- :foo
312
- end
313
-
314
- snooze
315
- f.cancel!
316
- rescue Polyphony::Cancel => e
317
- # cancel error should bubble up
318
- error = e
319
- ensure
320
- assert error
321
- assert_equal :dead, f.state
322
- end
323
379
  end
324
380
 
325
381
  class MailboxTest < MiniTest::Test
@@ -379,4 +435,29 @@ class MailboxTest < MiniTest::Test
379
435
  snooze
380
436
  assert_equal 1, Fiber.count
381
437
  end
438
+
439
+ def test_inspect
440
+ expected = format('#<Fiber:%s (root) (running)>', Fiber.current.object_id)
441
+ assert_equal expected, Fiber.current.inspect
442
+
443
+ spin_line_no = __LINE__ + 1
444
+ f = spin { :foo }
445
+
446
+ expected = format(
447
+ '#<Fiber:%s %s:%d:in `test_inspect\' (scheduled)>',
448
+ f.object_id,
449
+ __FILE__,
450
+ spin_line_no
451
+ )
452
+ assert_equal expected, f.inspect
453
+
454
+ f.await
455
+ expected = format(
456
+ '#<Fiber:%s %s:%d:in `test_inspect\' (dead)>',
457
+ f.object_id,
458
+ __FILE__,
459
+ spin_line_no
460
+ )
461
+ assert_equal expected, f.inspect
462
+ end
382
463
  end
@@ -294,4 +294,20 @@ class MoveOnAfterTest < MiniTest::Test
294
294
  f.stop
295
295
  assert (4..5).include?(buffer.size)
296
296
  end
297
+
298
+ def test_sleep
299
+ t0 = Time.now
300
+ sleep 0.05
301
+ elapsed = Time.now - t0
302
+ puts "* elapsed: #{elapsed.inspect}"
303
+ assert (0.045..0.8).include? elapsed
304
+
305
+ f = spin { sleep }
306
+ snooze
307
+ assert f.running?
308
+ snooze
309
+ assert f.running?
310
+ f.stop
311
+ assert !f.running?
312
+ end
297
313
  end
@@ -56,14 +56,14 @@ class SupervisorTest < MiniTest::Test
56
56
  foo_f = bar_f = baz_f = nil
57
57
  result, f = Polyphony::Supervisor.new.select { |s|
58
58
  foo_f = s.spin { sleep 0.01; buffer << :foo; :foo }
59
- bar_f = s.spin { sleep 0.02; buffer << :bar; :bar }
60
- baz_f = s.spin { sleep 0.03; buffer << :baz; :baz }
59
+ bar_f = s.spin { sleep 0.03; buffer << :bar; :bar }
60
+ baz_f = s.spin { sleep 0.05; buffer << :baz; :baz }
61
61
  }
62
62
 
63
63
  assert_equal :foo, result
64
64
  assert_equal foo_f, f
65
65
 
66
- sleep 0.03
66
+ sleep 0.05
67
67
  assert !bar_f.running?
68
68
  assert !baz_f.running?
69
69
  assert_equal [:foo], buffer
data/test/test_timer.rb CHANGED
@@ -45,6 +45,6 @@ class TimerTest < MiniTest::Test
45
45
  }
46
46
  suspend
47
47
  deltas = times.each_with_object([]) { |t, a| a << t - last; last = t }
48
- assert_equal 0, deltas.filter { |d| (d - 0.01).abs >= 0.005 }.size
48
+ assert_equal 0, deltas.filter { |d| (d - 0.01).abs >= 0.006 }.size
49
49
  end
50
50
  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.25'
4
+ version: '0.26'
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-01-10 00:00:00.000000000 Z
11
+ date: 2020-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: modulation
@@ -254,6 +254,7 @@ files:
254
254
  - ext/gyro/gyro.h
255
255
  - ext/gyro/gyro_ext.c
256
256
  - ext/gyro/io.c
257
+ - ext/gyro/libev.c
257
258
  - ext/gyro/libev.h
258
259
  - ext/gyro/signal.c
259
260
  - ext/gyro/socket.c
@@ -303,6 +304,7 @@ files:
303
304
  - test/run.rb
304
305
  - test/test_async.rb
305
306
  - test/test_cancel_scope.rb
307
+ - test/test_ext.rb
306
308
  - test/test_fiber.rb
307
309
  - test/test_global_api.rb
308
310
  - test/test_gyro.rb
@@ -312,11 +314,12 @@ files:
312
314
  - test/test_signal.rb
313
315
  - test/test_supervisor.rb
314
316
  - test/test_timer.rb
315
- homepage: http://github.com/digital-fabric/polyphony
317
+ homepage: https://dfab.gitbook.io/polyphony/
316
318
  licenses:
317
319
  - MIT
318
320
  metadata:
319
321
  source_code_uri: https://github.com/digital-fabric/polyphony
322
+ documentation_uri: https://dfab.gitbook.io/polyphony/
320
323
  post_install_message:
321
324
  rdoc_options:
322
325
  - "--title"
@@ -336,7 +339,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
336
339
  - !ruby/object:Gem::Version
337
340
  version: '0'
338
341
  requirements: []
339
- rubygems_version: 3.0.6
342
+ rubygems_version: 3.1.2
340
343
  signing_key:
341
344
  specification_version: 4
342
345
  summary: 'Polyphony: Fiber-based Concurrency for Ruby'