polyphony 0.25 → 0.26

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