polyphony 0.69 → 0.73

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +2 -2
  4. data/.gitignore +3 -1
  5. data/CHANGELOG.md +33 -4
  6. data/Gemfile.lock +2 -2
  7. data/TODO.md +1 -24
  8. data/bin/pdbg +1 -1
  9. data/bin/polyphony-debug +0 -0
  10. data/bin/stress.rb +0 -0
  11. data/bin/test +0 -0
  12. data/docs/_user-guide/all-about-timers.md +1 -1
  13. data/docs/api-reference/exception.md +5 -1
  14. data/docs/api-reference/fiber.md +2 -2
  15. data/docs/faq.md +1 -1
  16. data/docs/getting-started/overview.md +8 -8
  17. data/docs/getting-started/tutorial.md +3 -3
  18. data/docs/main-concepts/concurrency.md +1 -1
  19. data/docs/main-concepts/extending.md +3 -3
  20. data/docs/main-concepts/fiber-scheduling.md +1 -1
  21. data/examples/core/calc.rb +37 -0
  22. data/examples/core/calc_with_restart.rb +40 -0
  23. data/examples/core/calc_with_supervise.rb +37 -0
  24. data/examples/core/message_based_supervision.rb +1 -1
  25. data/examples/core/ring.rb +29 -0
  26. data/examples/io/rack_server.rb +1 -1
  27. data/examples/io/tunnel.rb +1 -1
  28. data/examples/performance/fiber_transfer.rb +1 -1
  29. data/examples/performance/line_splitting.rb +1 -1
  30. data/examples/performance/thread-vs-fiber/compare.rb +1 -1
  31. data/ext/polyphony/backend_common.c +31 -7
  32. data/ext/polyphony/backend_common.h +2 -1
  33. data/ext/polyphony/backend_io_uring.c +57 -67
  34. data/ext/polyphony/backend_io_uring_context.c +1 -1
  35. data/ext/polyphony/backend_io_uring_context.h +1 -1
  36. data/ext/polyphony/backend_libev.c +38 -30
  37. data/ext/polyphony/extconf.rb +25 -13
  38. data/ext/polyphony/polyphony.h +5 -1
  39. data/ext/polyphony/queue.c +2 -2
  40. data/ext/polyphony/runqueue_ring_buffer.c +3 -2
  41. data/ext/polyphony/thread.c +1 -1
  42. data/lib/polyphony/adapters/irb.rb +11 -1
  43. data/lib/polyphony/{extensions → core}/debug.rb +0 -0
  44. data/lib/polyphony/core/global_api.rb +3 -6
  45. data/lib/polyphony/core/timer.rb +2 -2
  46. data/lib/polyphony/debugger.rb +3 -3
  47. data/lib/polyphony/extensions/exception.rb +45 -0
  48. data/lib/polyphony/extensions/fiber.rb +30 -16
  49. data/lib/polyphony/extensions/io.rb +2 -2
  50. data/lib/polyphony/extensions/{core.rb → kernel.rb} +0 -73
  51. data/lib/polyphony/extensions/openssl.rb +20 -5
  52. data/lib/polyphony/extensions/process.rb +19 -0
  53. data/lib/polyphony/extensions/socket.rb +3 -4
  54. data/lib/polyphony/extensions/timeout.rb +10 -0
  55. data/lib/polyphony/extensions.rb +9 -0
  56. data/lib/polyphony/net.rb +0 -1
  57. data/lib/polyphony/version.rb +1 -1
  58. data/lib/polyphony.rb +2 -5
  59. data/polyphony.gemspec +1 -1
  60. data/test/coverage.rb +2 -2
  61. data/test/stress.rb +1 -1
  62. data/test/test_backend.rb +12 -12
  63. data/test/test_event.rb +1 -1
  64. data/test/test_ext.rb +1 -1
  65. data/test/test_fiber.rb +52 -12
  66. data/test/test_global_api.rb +16 -4
  67. data/test/test_io.rb +3 -3
  68. data/test/test_process_supervision.rb +39 -10
  69. data/test/test_queue.rb +6 -6
  70. data/test/test_signal.rb +20 -1
  71. data/test/test_socket.rb +12 -10
  72. data/test/test_supervise.rb +249 -81
  73. data/test/test_sync.rb +2 -2
  74. data/test/test_thread.rb +22 -2
  75. data/test/test_thread_pool.rb +1 -1
  76. data/test/test_throttler.rb +1 -1
  77. data/test/test_timer.rb +2 -2
  78. data/test/test_trace.rb +1 -1
  79. metadata +18 -9
data/polyphony.gemspec CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |s|
27
27
  s.add_development_dependency 'simplecov', '0.17.1'
28
28
  s.add_development_dependency 'rubocop', '0.85.1'
29
29
  s.add_development_dependency 'pry', '0.13.1'
30
-
30
+
31
31
  s.add_development_dependency 'msgpack', '1.4.2'
32
32
  s.add_development_dependency 'httparty', '0.17.1'
33
33
  s.add_development_dependency 'localhost', '~>1.1.4'
data/test/coverage.rb CHANGED
@@ -24,10 +24,10 @@ module Coverage
24
24
  @result = {}
25
25
  trace = TracePoint.new(:line) do |tp|
26
26
  next if tp.path =~ /\(/
27
-
27
+
28
28
  absolute = File.expand_path(tp.path)
29
29
  next unless LIB_FILES.include?(absolute)# =~ /^#{LIB_DIR}/
30
-
30
+
31
31
  @result[absolute] ||= relevant_lines_for_filename(absolute)
32
32
  @result[absolute][tp.lineno - 1] = 1
33
33
  end
data/test/stress.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  count = ARGV[0] ? ARGV[0].to_i : 100
4
4
  test_name = ARGV[1]
5
5
 
6
- $test_cmd = +'ruby test/run.rb'
6
+ $test_cmd = +'ruby test/run.rb --name test_receive_cross_thread_exception'
7
7
  if test_name
8
8
  $test_cmd << " --name #{test_name}"
9
9
  end
data/test/test_backend.rb CHANGED
@@ -36,7 +36,7 @@ class BackendTest < MiniTest::Test
36
36
  f = spin { @backend.read(i, buf, 5, false, 0) }
37
37
  @backend.write(o, 'Hello world')
38
38
  return_value = f.await
39
-
39
+
40
40
  assert_equal 'Hello', buf
41
41
  assert_equal return_value, buf
42
42
  end
@@ -51,7 +51,7 @@ class BackendTest < MiniTest::Test
51
51
  snooze
52
52
  o.close
53
53
  return_value = f.await
54
-
54
+
55
55
  assert_equal 'Hello', buf
56
56
  assert_equal return_value, buf
57
57
  end
@@ -66,7 +66,7 @@ class BackendTest < MiniTest::Test
66
66
  snooze
67
67
  o.close
68
68
  return_value = f.await
69
-
69
+
70
70
  assert_equal 'Hello world', buf
71
71
  assert_equal return_value, buf
72
72
  end
@@ -77,7 +77,7 @@ class BackendTest < MiniTest::Test
77
77
  f = spin { @backend.read(i, buf, 20, false, 0) }
78
78
  @backend.write(o, 'blah')
79
79
  return_value = f.await
80
-
80
+
81
81
  assert_equal 'blah', buf
82
82
  assert_equal return_value, buf
83
83
 
@@ -85,7 +85,7 @@ class BackendTest < MiniTest::Test
85
85
  f = spin { @backend.read(i, buf, 20, false, -1) }
86
86
  @backend.write(o, 'klmn')
87
87
  return_value = f.await
88
-
88
+
89
89
  assert_equal 'abcdefghijklmn', buf
90
90
  assert_equal return_value, buf
91
91
 
@@ -93,7 +93,7 @@ class BackendTest < MiniTest::Test
93
93
  f = spin { @backend.read(i, buf, 20, false, 3) }
94
94
  @backend.write(o, 'DEF')
95
95
  return_value = f.await
96
-
96
+
97
97
  assert_equal 'abcDEF', buf
98
98
  assert_equal return_value, buf
99
99
  end
@@ -109,12 +109,12 @@ class BackendTest < MiniTest::Test
109
109
 
110
110
  o << data
111
111
  o.close
112
-
112
+
113
113
  buf = +''
114
114
 
115
115
  @backend.read(i, buf, 4096, false, -1)
116
116
  assert_equal 4096, buf.bytesize
117
-
117
+
118
118
  @backend.read(i, buf, 1, false, -1)
119
119
  assert_equal 4097, buf.bytesize
120
120
 
@@ -129,7 +129,7 @@ class BackendTest < MiniTest::Test
129
129
  @backend.post_fork
130
130
  exit(42)
131
131
  end
132
-
132
+
133
133
  result = @backend.waitpid(pid)
134
134
  assert_equal [pid, 42], result
135
135
  end
@@ -427,7 +427,7 @@ class BackendTest < MiniTest::Test
427
427
  counter = 0
428
428
 
429
429
  @backend.idle_proc = proc { counter += 1 }
430
-
430
+
431
431
  3.times { snooze }
432
432
  assert_equal 0, counter
433
433
 
@@ -488,7 +488,7 @@ class BackendChainTest < MiniTest::Test
488
488
 
489
489
  snooze
490
490
  client = TCPSocket.new('127.0.0.1', port)
491
-
491
+
492
492
  result = Thread.backend.chain(
493
493
  [:send, client, 'hello', 0],
494
494
  [:send, client, " world\n", 0]
@@ -512,7 +512,7 @@ class BackendChainTest < MiniTest::Test
512
512
  while true
513
513
  len = o.splice(from, 8192)
514
514
  break if len == 0
515
-
515
+
516
516
  backend.chain(
517
517
  [:write, to, chunk_header(len)],
518
518
  [:splice, i, to, len]
data/test/test_event.rb CHANGED
@@ -25,7 +25,7 @@ class EventTest < MiniTest::Test
25
25
  def test_that_event_coalesces_signals
26
26
  count = 0
27
27
  a = Polyphony::Event.new
28
-
28
+
29
29
  coproc = spin {
30
30
  loop {
31
31
  a.await
data/test/test_ext.rb CHANGED
@@ -73,7 +73,7 @@ class KernelTest < MiniTest::Test
73
73
  data = `>&2 echo "error"`
74
74
  $stderr.rewind
75
75
  $stderr = prev_stderr
76
-
76
+
77
77
  assert_equal '', data
78
78
  assert_equal "error\n", err_io.read
79
79
  ensure
data/test/test_fiber.rb CHANGED
@@ -47,7 +47,7 @@ class FiberTest < MiniTest::Test
47
47
  f1 = spin { :foo }
48
48
  f2 = spin { :bar }
49
49
  4.times { snooze }
50
-
50
+
51
51
  assert_equal [:foo, :bar], Fiber.await(f1, f2)
52
52
  end
53
53
 
@@ -67,8 +67,6 @@ class FiberTest < MiniTest::Test
67
67
  }
68
68
  Fiber.await(f2, f3)
69
69
  assert_equal [:foo, :bar, :baz], buffer
70
- assert_equal [f1], Fiber.current.children
71
- Fiber.current.reap_dead_children
72
70
  assert_equal [], Fiber.current.children
73
71
  end
74
72
 
@@ -93,8 +91,6 @@ class FiberTest < MiniTest::Test
93
91
  f1.stop
94
92
 
95
93
  snooze
96
- assert_equal [f1, f2, f3], Fiber.current.children
97
- Fiber.current.reap_dead_children
98
94
  assert_equal [], Fiber.current.children
99
95
  end
100
96
 
@@ -602,7 +598,6 @@ class FiberTest < MiniTest::Test
602
598
 
603
599
  f.stop
604
600
  snooze
605
- Fiber.current.reap_dead_children
606
601
  assert_equal [], Fiber.current.children
607
602
  end
608
603
 
@@ -744,7 +739,7 @@ class FiberTest < MiniTest::Test
744
739
  def test_setup_raw
745
740
  buffer = []
746
741
  f = Fiber.new { buffer << receive }
747
-
742
+
748
743
  assert_nil f.thread
749
744
  snooze
750
745
  f.setup_raw
@@ -776,10 +771,11 @@ class FiberTest < MiniTest::Test
776
771
 
777
772
  snooze
778
773
  assert_equal parent, child.parent
779
- child.detach
774
+ result = child.detach
775
+ assert_equal result, child
780
776
  assert_equal Fiber.current, child.parent
781
777
  parent.await
782
-
778
+
783
779
  child << :bye
784
780
  child.await
785
781
 
@@ -812,7 +808,7 @@ class FiberTest < MiniTest::Test
812
808
  child.attach_to(new_parent)
813
809
  assert_equal new_parent, child.parent
814
810
  parent.await
815
-
811
+
816
812
  child << :bye
817
813
  new_parent << :bye_new_parent
818
814
  snooze
@@ -825,6 +821,29 @@ class FiberTest < MiniTest::Test
825
821
  :bye_new_parent
826
822
  ], buf
827
823
  end
824
+
825
+ def test_attach_all_children_to
826
+ children = []
827
+ f1 = spin do
828
+ 3.times {
829
+ children << spin { receive }
830
+ }
831
+ Fiber.current.parent << :ok
832
+ receive
833
+ end
834
+
835
+ result = receive
836
+ assert_equal :ok, result
837
+ assert_equal 3, children.size
838
+
839
+ f2 = spin { supervise }
840
+ f1.attach_all_children_to(f2)
841
+
842
+ snooze
843
+
844
+ assert_equal [], f1.children
845
+ assert_equal children, f2.children
846
+ end
828
847
  end
829
848
 
830
849
  class MailboxTest < MiniTest::Test
@@ -954,6 +973,27 @@ class MailboxTest < MiniTest::Test
954
973
  assert_equal ['foo'] * 100, messages
955
974
  end
956
975
 
976
+ def test_receive_exception
977
+ e = RuntimeError.new 'foo'
978
+ spin { Fiber.current.parent << e }
979
+ r = receive
980
+ assert_equal e, r
981
+
982
+ spin { Fiber.current.parent.schedule e }
983
+ assert_raises(RuntimeError) { receive }
984
+ end
985
+
986
+ def test_receive_cross_thread_exception
987
+ e = RuntimeError.new 'foo'
988
+ f = Fiber.current
989
+ Thread.new { f << e }
990
+ r = receive
991
+ assert_equal e, r
992
+
993
+ Thread.new { f.schedule e }
994
+ assert_raises(RuntimeError) { receive }
995
+ end
996
+
957
997
  def test_receive_all_pending
958
998
  assert_equal [], receive_all_pending
959
999
 
@@ -1110,7 +1150,7 @@ class RestartTest < MiniTest::Test
1110
1150
  assert_equal [1], buffer
1111
1151
  snooze
1112
1152
  assert_equal [1, 1], buffer
1113
-
1153
+
1114
1154
  f << 'foo'
1115
1155
  sleep 0.1
1116
1156
  assert_equal [1, 1, 2], buffer
@@ -1236,7 +1276,7 @@ class DebugTest < MiniTest::Test
1236
1276
  assert_equal true, f.__parked__?
1237
1277
  10.times { snooze }
1238
1278
  assert_equal [], buf
1239
-
1279
+
1240
1280
  f.__unpark__
1241
1281
  assert_nil f.__parked__?
1242
1282
  10.times { snooze }
@@ -280,7 +280,7 @@ class SpinLoopTest < MiniTest::Test
280
280
  def test_spin_loop_location
281
281
  location = /^#{__FILE__}:#{__LINE__ + 1}/
282
282
  f = spin_loop { snooze }
283
-
283
+
284
284
  assert_match location, f.location
285
285
  end
286
286
 
@@ -375,7 +375,6 @@ class SpinScopeTest < MiniTest::Test
375
375
  buffer << e.message
376
376
  end
377
377
  10.times { snooze }
378
- Fiber.current.reap_dead_children
379
378
  assert_equal 0, Fiber.current.children.size
380
379
  assert_equal ['foobar'], buffer
381
380
  end
@@ -403,7 +402,7 @@ class ThrottledLoopTest < MiniTest::Test
403
402
  f.await
404
403
  t1 = Time.now
405
404
  assert_in_range 0.075..0.15, t1 - t0 if IS_LINUX
406
- assert_equal [1, 2, 3, 4, 5], buffer
405
+ assert_equal [1, 2, 3, 4, 5], buffer
407
406
  end
408
407
  end
409
408
 
@@ -413,7 +412,7 @@ class GlobalAPIEtcTest < MiniTest::Test
413
412
  f = after(0.001) { buffer << 2 }
414
413
  snooze
415
414
  assert_equal [], buffer
416
- sleep 0.001
415
+ sleep 0.0015
417
416
  assert_equal [2], buffer
418
417
  end
419
418
 
@@ -430,6 +429,19 @@ class GlobalAPIEtcTest < MiniTest::Test
430
429
  assert_in_range 4..6, buffer.size
431
430
  end
432
431
 
432
+ def test_every_with_slow_op
433
+ skip unless IS_LINUX
434
+
435
+ buffer = []
436
+ t0 = Time.now
437
+ f = spin do
438
+ every(0.01) { sleep 0.05; buffer << 1 }
439
+ end
440
+ sleep 0.15
441
+ f.stop
442
+ assert_in_range 2..3, buffer.size
443
+ end
444
+
433
445
  def test_sleep
434
446
  t0 = Time.now
435
447
  sleep 0.1
data/test/test_io.rb CHANGED
@@ -107,10 +107,10 @@ class IOTest < MiniTest::Test
107
107
  sleep 0.01
108
108
  o << 'hi'
109
109
  }
110
- assert_equal 'hi', i.readpartial(2)
110
+ assert_equal 'hi', i.readpartial(2)
111
111
  o.close
112
112
 
113
- assert_raises(EOFError) { i.readpartial(1) }
113
+ assert_raises(EOFError) { i.readpartial(1) }
114
114
  end
115
115
 
116
116
  def test_gets
@@ -132,7 +132,7 @@ class IOTest < MiniTest::Test
132
132
  f << Fiber.current
133
133
  sleep 0.05
134
134
  assert_equal [], buf
135
-
135
+
136
136
  o << "ulous\n"
137
137
  receive
138
138
  assert_equal ["fabulous\n"], buf
@@ -6,7 +6,7 @@ class ProcessSupervisionTest < MiniTest::Test
6
6
  def test_process_supervisor_with_block
7
7
  i, o = IO.pipe
8
8
 
9
- f = spin do
9
+ watcher = spin do
10
10
  Polyphony.watch_process do
11
11
  i.close
12
12
  sleep 5
@@ -14,31 +14,60 @@ class ProcessSupervisionTest < MiniTest::Test
14
14
  o << 'foo'
15
15
  o.close
16
16
  end
17
- supervise(on_error: :restart)
18
17
  end
19
18
 
19
+ supervisor = spin { supervise(watcher, restart: :always) }
20
+
20
21
  sleep 0.05
21
- f.terminate
22
- f.await
22
+ supervisor.terminate
23
+ supervisor.await
23
24
 
24
25
  o.close
25
26
  msg = i.read
26
- i.close
27
27
  assert_equal 'foo', msg
28
28
  end
29
29
 
30
+ def test_process_supervisor_restart_with_block
31
+ i1, o1 = IO.pipe
32
+ i2, o2 = IO.pipe
33
+
34
+ count = 0
35
+ watcher = spin do
36
+ count += 1
37
+ Polyphony.watch_process do
38
+ i1.gets
39
+ o2.puts count
40
+ end
41
+ end
42
+
43
+ supervisor = spin { supervise(watcher, restart: :always) }
44
+
45
+ o1.puts
46
+ l = i2.gets
47
+ assert_equal "1\n", l
48
+
49
+ o1.puts
50
+ l = i2.gets
51
+ assert_equal "2\n", l
52
+
53
+ o1.puts
54
+ l = i2.gets
55
+ assert_equal "3\n", l
56
+ end
57
+
30
58
  def test_process_supervisor_with_cmd
31
59
  fn = '/tmp/test_process_supervisor_with_cmd'
32
60
  FileUtils.rm(fn) rescue nil
33
61
 
34
- f = spin do
62
+ watcher = spin do
35
63
  Polyphony.watch_process("echo foo >> #{fn}")
36
- supervise(on_error: :restart)
37
64
  end
38
65
 
39
- sleep 0.05
40
- f.terminate
41
- f.await
66
+ supervisor = spin { supervise(watcher) }
67
+
68
+ sleep 0.1
69
+ supervisor.terminate
70
+ supervisor.await
42
71
 
43
72
  assert_equal "foo\n", IO.read(fn)
44
73
 
data/test/test_queue.rb CHANGED
@@ -70,7 +70,7 @@ class QueueTest < MiniTest::Test
70
70
 
71
71
  def test_empty?
72
72
  assert @queue.empty?
73
-
73
+
74
74
  @queue << :foo
75
75
  assert !@queue.empty?
76
76
 
@@ -82,7 +82,7 @@ class QueueTest < MiniTest::Test
82
82
  f1 = spin { @queue.shift }
83
83
  f2 = spin { @queue.shift }
84
84
  f3 = spin { @queue.shift }
85
-
85
+
86
86
  # let fibers run
87
87
  snooze
88
88
 
@@ -99,7 +99,7 @@ class QueueTest < MiniTest::Test
99
99
 
100
100
  def test_fiber_removal_from_queue_simple
101
101
  f1 = spin { @queue.shift }
102
-
102
+
103
103
  # let fibers run
104
104
  snooze
105
105
 
@@ -114,11 +114,11 @@ class QueueTest < MiniTest::Test
114
114
  assert_equal 0, @queue.size
115
115
 
116
116
  @queue.push 1
117
-
117
+
118
118
  assert_equal 1, @queue.size
119
119
 
120
120
  @queue.push 2
121
-
121
+
122
122
  assert_equal 2, @queue.size
123
123
 
124
124
  @queue.shift
@@ -231,7 +231,7 @@ class CappedQueueTest < MiniTest::Test
231
231
  i = 0
232
232
  spin_loop do
233
233
  i += 1
234
- snooze
234
+ snooze
235
235
  end
236
236
 
237
237
  5.times { snooze }
data/test/test_signal.rb CHANGED
@@ -93,4 +93,23 @@ class SignalTrapTest < Minitest::Test
93
93
  buffer = i.read
94
94
  assert_equal "INT\n", buffer
95
95
  end
96
- end
96
+
97
+ def test_busy_signal_handling
98
+ i, o = IO.pipe
99
+ pid = Polyphony.fork do
100
+ main = Fiber.current
101
+ trap('INT') { o.puts 'INT'; o.close; main.stop }
102
+ i.close
103
+ f1 = spin_loop { snooze }
104
+ f2 = spin_loop { snooze }
105
+ f1.await
106
+ end
107
+
108
+ o.close
109
+ sleep 0.1
110
+ Process.kill('INT', pid)
111
+ Thread.current.backend.waitpid(pid)
112
+ buffer = i.read
113
+ assert_equal "INT\n", buffer
114
+ end
115
+ end
data/test/test_socket.rb CHANGED
@@ -9,9 +9,16 @@ class SocketTest < MiniTest::Test
9
9
  super
10
10
  end
11
11
 
12
- def test_tcp
13
- port = rand(1234..5678)
12
+ def start_tcp_server_on_random_port
13
+ port = rand(1100..60000)
14
14
  server = TCPServer.new('127.0.0.1', port)
15
+ [port, server]
16
+ rescue Errno::EADDRINUSE
17
+ retry
18
+ end
19
+
20
+ def test_tcp
21
+ port, server = start_tcp_server_on_random_port
15
22
  server_fiber = spin do
16
23
  while (socket = server.accept)
17
24
  spin do
@@ -34,8 +41,7 @@ class SocketTest < MiniTest::Test
34
41
  end
35
42
 
36
43
  def test_read
37
- port = rand(1234..5678)
38
- server = TCPServer.new('127.0.0.1', port)
44
+ port, server = start_tcp_server_on_random_port
39
45
  server_fiber = spin do
40
46
  while (socket = server.accept)
41
47
  spin do
@@ -74,9 +80,7 @@ class SocketTest < MiniTest::Test
74
80
 
75
81
  # sending multiple strings at once
76
82
  def test_sendv
77
- port = rand(1234..5678)
78
- server = TCPServer.new('127.0.0.1', port)
79
-
83
+ port, server = start_tcp_server_on_random_port
80
84
  server_fiber = spin do
81
85
  while (socket = server.accept)
82
86
  spin do
@@ -100,9 +104,7 @@ class SocketTest < MiniTest::Test
100
104
 
101
105
 
102
106
  def test_feed_loop
103
- port = rand(1234..5678)
104
- server = TCPServer.new('127.0.0.1', port)
105
-
107
+ port, server = start_tcp_server_on_random_port
106
108
  server_fiber = spin do
107
109
  reader = MessagePack::Unpacker.new
108
110
  while (socket = server.accept)