polyphony 0.20 → 0.21

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitbook.yaml +1 -2
  3. data/.rubocop.yml +1 -0
  4. data/CHANGELOG.md +10 -0
  5. data/Gemfile.lock +1 -1
  6. data/README.md +18 -449
  7. data/TODO.md +0 -10
  8. data/docs/README.md +39 -0
  9. data/docs/getting-started/installing.md +28 -0
  10. data/docs/getting-started/tutorial.md +133 -0
  11. data/docs/summary.md +37 -3
  12. data/docs/technical-overview/concurrency.md +47 -0
  13. data/docs/technical-overview/design-principles.md +112 -0
  14. data/docs/technical-overview/exception-handling.md +34 -41
  15. data/docs/technical-overview/extending.md +80 -0
  16. data/docs/technical-overview/faq.md +74 -0
  17. data/docs/technical-overview/fiber-scheduling.md +23 -52
  18. data/docs/user-guide/web-server.md +129 -0
  19. data/examples/core/01-spinning-up-coprocesses.rb +21 -0
  20. data/examples/core/02-awaiting-coprocesses.rb +18 -0
  21. data/examples/core/03-interrupting.rb +34 -0
  22. data/examples/core/04-no-auto-run.rb +18 -0
  23. data/examples/core/mem-usage.rb +34 -0
  24. data/examples/core/spin_error.rb +0 -1
  25. data/examples/core/spin_uncaught_error.rb +0 -1
  26. data/examples/core/wait_for_signal.rb +14 -0
  27. data/examples/http/http_server_graceful.rb +25 -0
  28. data/examples/http/http_server_simple.rb +11 -0
  29. data/examples/interfaces/redis_pubsub_perf.rb +1 -1
  30. data/ext/gyro/async.c +4 -40
  31. data/ext/gyro/child.c +0 -42
  32. data/ext/gyro/io.c +0 -41
  33. data/lib/polyphony/core/coprocess.rb +8 -0
  34. data/lib/polyphony/core/supervisor.rb +29 -10
  35. data/lib/polyphony/extensions/core.rb +1 -1
  36. data/lib/polyphony/http/server/http2.rb +20 -4
  37. data/lib/polyphony/http/server/http2_stream.rb +35 -3
  38. data/lib/polyphony/version.rb +1 -1
  39. data/lib/polyphony.rb +17 -5
  40. data/test/test_async.rb +14 -7
  41. data/test/test_coprocess.rb +42 -12
  42. data/test/test_core.rb +26 -0
  43. data/test/test_io.rb +14 -5
  44. data/test/test_signal.rb +6 -10
  45. metadata +17 -5
  46. data/docs/getting-started/getting-started.md +0 -10
  47. data/examples/core/spin.rb +0 -14
  48. data/examples/core/spin_cancel.rb +0 -17
data/ext/gyro/io.c CHANGED
@@ -10,7 +10,6 @@ struct Gyro_IO {
10
10
  struct ev_io ev_io;
11
11
  int active;
12
12
  int event_mask;
13
- VALUE callback;
14
13
  VALUE fiber;
15
14
  };
16
15
 
@@ -23,9 +22,6 @@ static size_t Gyro_IO_size(const void *ptr);
23
22
 
24
23
  static VALUE Gyro_IO_initialize(VALUE self, VALUE io, VALUE event_mask);
25
24
 
26
- static VALUE Gyro_IO_start(VALUE self);
27
- static VALUE Gyro_IO_stop(VALUE self);
28
-
29
25
  void Gyro_IO_callback(struct ev_loop *ev_loop, struct ev_io *io, int revents);
30
26
 
31
27
  static int Gyro_IO_symbol2event_mask(VALUE sym);
@@ -41,8 +37,6 @@ void Init_Gyro_IO() {
41
37
  rb_define_alloc_func(cGyro_IO, Gyro_IO_allocate);
42
38
 
43
39
  rb_define_method(cGyro_IO, "initialize", Gyro_IO_initialize, 2);
44
- rb_define_method(cGyro_IO, "start", Gyro_IO_start, 0);
45
- rb_define_method(cGyro_IO, "stop", Gyro_IO_stop, 0);
46
40
  rb_define_method(cGyro_IO, "await", Gyro_IO_await, 0);
47
41
 
48
42
  VALUE cIO = rb_const_get(rb_cObject, rb_intern("IO"));
@@ -71,9 +65,6 @@ static VALUE Gyro_IO_allocate(VALUE klass) {
71
65
 
72
66
  static void Gyro_IO_mark(void *ptr) {
73
67
  struct Gyro_IO *io = ptr;
74
- if (io->callback != Qnil) {
75
- rb_gc_mark(io->callback);
76
- }
77
68
  if (io->fiber != Qnil) {
78
69
  rb_gc_mark(io->fiber);
79
70
  }
@@ -101,7 +92,6 @@ static VALUE Gyro_IO_initialize(VALUE self, VALUE io_obj, VALUE event_mask) {
101
92
  GetGyro_IO(self, io);
102
93
 
103
94
  io->event_mask = Gyro_IO_symbol2event_mask(event_mask);
104
- io->callback = Qnil;
105
95
  io->fiber = Qnil;
106
96
  io->active = 0;
107
97
 
@@ -122,42 +112,11 @@ void Gyro_IO_callback(struct ev_loop *ev_loop, struct ev_io *ev_io, int revents)
122
112
  io->fiber = Qnil;
123
113
  SCHEDULE_FIBER(fiber, 0);
124
114
  }
125
- else if (io->callback != Qnil) {
126
- rb_funcall(io->callback, ID_call, 1, INT2NUM(revents));
127
- }
128
115
  else {
129
116
  ev_io_stop(EV_DEFAULT, ev_io);
130
117
  }
131
118
  }
132
119
 
133
- static VALUE Gyro_IO_start(VALUE self) {
134
- struct Gyro_IO *io;
135
- GetGyro_IO(self, io);
136
-
137
- if (rb_block_given_p()) {
138
- io->callback = rb_block_proc();
139
- }
140
-
141
- if (!io->active) {
142
- ev_io_start(EV_DEFAULT, &io->ev_io);
143
- io->active = 1;
144
- }
145
-
146
- return self;
147
- }
148
-
149
- static VALUE Gyro_IO_stop(VALUE self) {
150
- struct Gyro_IO *io;
151
- GetGyro_IO(self, io);
152
-
153
- if (io->active) {
154
- ev_io_stop(EV_DEFAULT, &io->ev_io);
155
- io->active = 0;
156
- }
157
-
158
- return self;
159
- }
160
-
161
120
  VALUE Gyro_IO_await(VALUE self) {
162
121
  struct Gyro_IO *io;
163
122
  VALUE ret;
@@ -100,6 +100,14 @@ class Coprocess
100
100
  @fiber
101
101
  end
102
102
 
103
+ def caller
104
+ @fiber&.__caller__[2..]
105
+ end
106
+
107
+ def location
108
+ caller[0]
109
+ end
110
+
103
111
  # Kernel.await expects the given argument / block to be a callable, so #call
104
112
  # in fact waits for the coprocess to finish
105
113
  def await
@@ -9,12 +9,14 @@ Exceptions = import('./exceptions')
9
9
  class Supervisor
10
10
  def initialize
11
11
  @coprocesses = []
12
+ @pending = []
12
13
  end
13
14
 
14
15
  def await(&block)
15
16
  @supervisor_fiber = Fiber.current
16
17
  block&.(self)
17
18
  suspend
19
+ @coprocesses.map { |cp| cp.result }
18
20
  rescue Exceptions::MoveOn => e
19
21
  e.value
20
22
  ensure
@@ -30,12 +32,21 @@ class Supervisor
30
32
  end
31
33
  end
32
34
 
33
- def spin(proc = nil, &block)
34
- proc = Coprocess.new(&(proc || block)) unless proc.is_a?(Coprocess)
35
- @coprocesses << proc
36
- proc.when_done { task_completed(proc) }
37
- proc.run unless proc.alive?
38
- proc
35
+ def spin(coproc = nil, &block)
36
+ coproc = Coprocess.new(&(coproc || block)) unless coproc.is_a?(Coprocess)
37
+ @coprocesses << coproc
38
+ @pending << coproc
39
+ coproc.when_done { task_completed(coproc) }
40
+ coproc.run unless coproc.alive?
41
+ coproc
42
+ end
43
+
44
+ def add(coproc)
45
+ @coprocesses << coproc
46
+ @pending << coproc
47
+ coproc.when_done { task_completed(coproc) }
48
+ coproc.run unless coproc.alive?
49
+ coproc
39
50
  end
40
51
 
41
52
  def still_running?
@@ -51,15 +62,23 @@ class Supervisor
51
62
 
52
63
  def stop_all_tasks
53
64
  exception = Exceptions::MoveOn.new
54
- @coprocesses.each do |c|
65
+ @pending.each do |c|
55
66
  c.transfer(exception)
56
67
  end
57
68
  end
58
69
 
59
70
  def task_completed(coprocess)
60
- return unless @coprocesses.include?(coprocess)
71
+ return unless @pending.include?(coprocess)
61
72
 
62
- @coprocesses.delete(coprocess)
63
- @supervisor_fiber&.transfer if @coprocesses.empty?
73
+ @pending.delete(coprocess)
74
+ @supervisor_fiber&.transfer if @pending.empty?
64
75
  end
65
76
  end
77
+
78
+ class Coprocess
79
+ def self.await(*coprocs)
80
+ supervise do |s|
81
+ coprocs.each { |cp| s.add cp }
82
+ end
83
+ end
84
+ end
@@ -12,7 +12,7 @@ Throttler = import('../core/throttler')
12
12
  # Fiber extensions
13
13
  class ::Fiber
14
14
  attr_accessor :__calling_fiber__
15
- attr_writer :__caller__
15
+ attr_accessor :__caller__
16
16
  attr_writer :cancelled
17
17
  attr_accessor :coprocess, :scheduled_value
18
18
 
@@ -19,10 +19,16 @@ class Protocol
19
19
  @upgrade_headers = upgrade_headers
20
20
 
21
21
  @interface = ::HTTP2::Server.new
22
- @interface.on(:frame) { |bytes| @conn << bytes }
22
+ @connection_fiber = Fiber.current
23
+ @interface.on(:frame, &method(:send_frame))
24
+ @streams = {}
23
25
  end
24
26
 
25
- # request API
27
+ def send_frame(data)
28
+ @conn << data
29
+ rescue Exception => e
30
+ @connection_fiber.transfer e
31
+ end
26
32
 
27
33
  UPGRADE_MESSAGE = <<~HTTP.gsub("\n", "\r\n")
28
34
  HTTP/1.1 101 Switching Protocols
@@ -34,6 +40,7 @@ class Protocol
34
40
  def upgrade
35
41
  @conn << UPGRADE_MESSAGE
36
42
  settings = @upgrade_headers['HTTP2-Settings']
43
+ Fiber.current.schedule(nil)
37
44
  @interface.upgrade(settings, @upgrade_headers, '')
38
45
  ensure
39
46
  @upgrade_headers = nil
@@ -41,7 +48,7 @@ class Protocol
41
48
 
42
49
  # Iterates over incoming requests
43
50
  def each(&block)
44
- @interface.on(:stream) { |stream| Stream.new(stream, &block) }
51
+ @interface.on(:stream) { |stream| start_stream(stream, &block) }
45
52
  upgrade if @upgrade_headers
46
53
 
47
54
  while (data = @conn.readpartial(8192))
@@ -51,8 +58,17 @@ class Protocol
51
58
  rescue SystemCallError, IOError
52
59
  # ignore
53
60
  ensure
54
- # release references to various objects
61
+ finalize_client_loop
62
+ end
63
+
64
+ def start_stream(stream, &block)
65
+ stream = Stream.new(stream, &block)
66
+ @streams[stream] = true
67
+ end
68
+
69
+ def finalize_client_loop
55
70
  @interface = nil
71
+ @streams.each_key(&:stop)
56
72
  @conn.close
57
73
  end
58
74
 
@@ -5,6 +5,7 @@ export_default :StreamHandler
5
5
  require 'http/2'
6
6
 
7
7
  Request = import './request'
8
+ Exceptions = import '../../core/exceptions'
8
9
 
9
10
  # Manages an HTTP 2 stream
10
11
  class StreamHandler
@@ -12,14 +13,38 @@ class StreamHandler
12
13
 
13
14
  def initialize(stream, &block)
14
15
  @stream = stream
15
- @stream_fiber = Fiber.new(&block)
16
-
17
- # stream callbacks occur on connection fiber
16
+ @calling_fiber = Fiber.current
17
+ @stream_fiber = Fiber.new { |req| handle_request(req, &block) }
18
+
19
+ # Stream callbacks occur on the connection fiber (see HTTP2::Protocol#each).
20
+ # The request handler is run on a separate fiber for each stream, allowing
21
+ # concurrent handling of incoming requests on the same HTTP/2 connection.
22
+ #
23
+ # The different stream adapter APIs suspend the stream fiber, waiting for
24
+ # stream callbacks to be called. The callbacks, in turn, transfer control to
25
+ # the stream fiber, effectively causing the return of the adapter API calls.
26
+ #
27
+ # Note: the request handler is run once headers are received. Reading the
28
+ # request body, if present, is at the discretion of the request handler.
29
+ # This mirrors the behaviour of the HTTP/1 adapter.
18
30
  stream.on(:headers, &method(:on_headers))
19
31
  stream.on(:data, &method(:on_data))
20
32
  stream.on(:half_close, &method(:on_half_close))
21
33
  end
22
34
 
35
+ def handle_request(request, &block)
36
+ error = nil
37
+ block.(request)
38
+ @calling_fiber.transfer
39
+ rescue Exceptions::MoveOn
40
+ # ignore
41
+ rescue Exception => e
42
+ error = e
43
+ ensure
44
+ @done = true
45
+ @calling_fiber.transfer error
46
+ end
47
+
23
48
  def on_headers(headers)
24
49
  @request = Request.new(headers.to_h, self)
25
50
  @stream_fiber.transfer(@request)
@@ -101,4 +126,11 @@ class StreamHandler
101
126
  @stream.headers(headers, end_stream: true)
102
127
  end
103
128
  end
129
+
130
+ def stop
131
+ return if @done
132
+
133
+ @stream.close
134
+ @stream_fiber.schedule(Polyphony::MoveOn.new)
135
+ end
104
136
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.20'
4
+ VERSION = '0.21'
5
5
  end
data/lib/polyphony.rb CHANGED
@@ -32,11 +32,23 @@ module Polyphony
32
32
  )
33
33
 
34
34
  class << self
35
- def trap(sig, ref = false, &callback)
36
- sig = Signal.list[sig.to_s.upcase] if sig.is_a?(Symbol)
37
- watcher = Gyro::Signal.new(sig, &callback)
38
- Gyro.unref unless ref
39
- watcher
35
+ # def trap(sig, ref = false, &callback)
36
+ # sig = Signal.list[sig.to_s.upcase] if sig.is_a?(Symbol)
37
+ # puts "sig = #{sig.inspect}"
38
+ # watcher = Gyro::Signal.new(sig, &callback)
39
+ # # Gyro.unref unless ref
40
+ # watcher
41
+ # end
42
+
43
+ def wait_for_signal(sig)
44
+ fiber = Fiber.current
45
+ Gyro.ref
46
+ trap(sig) do
47
+ trap(sig, :DEFAULT)
48
+ Gyro.unref
49
+ fiber.transfer(sig)
50
+ end
51
+ suspend
40
52
  end
41
53
 
42
54
  def fork(&block)
data/test/test_async.rb CHANGED
@@ -5,10 +5,12 @@ require_relative 'helper'
5
5
  class AsyncTest < MiniTest::Test
6
6
  def test_that_async_watcher_receives_signal_across_threads
7
7
  count = 0
8
- a = Gyro::Async.new do
8
+ a = Gyro::Async.new
9
+ spin {
10
+ a.await
9
11
  count += 1
10
- a.stop
11
- end
12
+ }
13
+ snooze
12
14
  Thread.new do
13
15
  sync_sleep 0.001
14
16
  a.signal!
@@ -19,10 +21,15 @@ class AsyncTest < MiniTest::Test
19
21
 
20
22
  def test_that_async_watcher_coalesces_signals
21
23
  count = 0
22
- a = Gyro::Async.new do
23
- count += 1
24
- Gyro::Timer.new(0.01, 0).start { a.stop }
25
- end
24
+ a = Gyro::Async.new
25
+ coproc = spin {
26
+ loop {
27
+ a.await
28
+ count += 1
29
+ after(0.01) { coproc.stop }
30
+ }
31
+ }
32
+ snooze
26
33
  Thread.new do
27
34
  sync_sleep 0.001
28
35
  3.times { a.signal! }
@@ -229,22 +229,52 @@ class CoprocessTest < MiniTest::Test
229
229
  assert(raised_error)
230
230
  assert_equal('foo', raised_error.message)
231
231
  end
232
- end
233
232
 
234
- def test_exception_propagation_for_orphan_fiber
235
- raised_error = nil
236
- spin do
233
+ def test_exception_propagation_for_orphan_fiber
234
+ raised_error = nil
237
235
  spin do
238
- snooze
239
- raise 'bar'
236
+ spin do
237
+ snooze
238
+ raise 'bar'
239
+ end
240
+ end
241
+ suspend
242
+ rescue Exception => e
243
+ raised_error = e
244
+ ensure
245
+ assert(raised_error)
246
+ assert_equal('bar', raised_error.message)
247
+ end
248
+
249
+ def test_await_multiple_coprocesses
250
+ cp1 = spin { sleep 0.01; :foo }
251
+ cp2 = spin { sleep 0.01; :bar }
252
+ cp3 = spin { sleep 0.01; :baz }
253
+
254
+ result = Polyphony::Coprocess.await(cp1, cp2, cp3)
255
+ assert_equal %i{foo bar baz}, result
256
+ end
257
+
258
+ def test_caller
259
+ location = /^#{__FILE__}:#{__LINE__ + 1}/
260
+ cp = spin do
261
+ sleep 0.01
262
+ end
263
+ snooze
264
+
265
+ caller = cp.caller
266
+ assert caller[0] =~ location
267
+ end
268
+
269
+ def test_location
270
+ location = /^#{__FILE__}:#{__LINE__ + 1}/
271
+ cp = spin do
272
+ sleep 0.01
240
273
  end
274
+ snooze
275
+
276
+ assert cp.location =~ location
241
277
  end
242
- suspend
243
- rescue Exception => e
244
- raised_error = e
245
- ensure
246
- assert(raised_error)
247
- assert_equal('bar', raised_error.message)
248
278
  end
249
279
 
250
280
  class MailboxTest < MiniTest::Test
data/test/test_core.rb CHANGED
@@ -216,3 +216,29 @@ class ExceptionTest < MiniTest::Test
216
216
  assert_kind_of RuntimeError, error
217
217
  end
218
218
  end
219
+
220
+ class MoveOnAfterTest < MiniTest::Test
221
+ def test_move_on_after
222
+ t0 = Time.now
223
+ v = move_on_after(0.01) do
224
+ sleep 1
225
+ :foo
226
+ end
227
+ t1 = Time.now
228
+
229
+ assert t1 - t0 < 0.02
230
+ assert_nil v
231
+ end
232
+
233
+ def test_move_on_after_with_value
234
+ t0 = Time.now
235
+ v = move_on_after(0.01, with_value: :bar) do
236
+ sleep 1
237
+ :foo
238
+ end
239
+ t1 = Time.now
240
+
241
+ assert t1 - t0 < 0.02
242
+ assert_equal :bar, v
243
+ end
244
+ end
data/test/test_io.rb CHANGED
@@ -6,14 +6,23 @@ class GyroIOTest < MiniTest::Test
6
6
  def test_that_reading_works
7
7
  i, o = IO.pipe
8
8
  data = +''
9
- w = Gyro::IO.new(i, :r)
10
- w.start do
9
+ sequence = []
10
+ watcher = Gyro::IO.new(i, :r)
11
+ spin {
12
+ sequence << 1
13
+ watcher.await
14
+ sequence << 2
11
15
  i.read_nonblock(8192, data)
12
- w.stop unless data.empty?
16
+ }
17
+ snooze
18
+ sequence << 3
19
+ defer do
20
+ o << 'hello'
21
+ sequence << 4
13
22
  end
14
- defer { o << 'hello' }
15
23
  suspend
16
- assert_equal('hello', data)
24
+ assert_equal 'hello', data
25
+ assert_equal [1, 3, 4, 2], sequence
17
26
  end
18
27
  end
19
28
 
data/test/test_signal.rb CHANGED
@@ -18,19 +18,15 @@ class SignalTest < MiniTest::Test
18
18
  assert_equal(1, count)
19
19
  end
20
20
 
21
- def test_trap_api
21
+ def test_wait_for_signal_api
22
22
  count = 0
23
- w = Polyphony.trap(:usr1, true) do
23
+ spin do
24
+ Polyphony.wait_for_signal 'SIGHUP'
24
25
  count += 1
25
- w.stop
26
26
  end
27
27
 
28
- assert_kind_of(Gyro::Signal, w)
29
- Thread.new do
30
- sync_sleep 0.001
31
- Process.kill(:USR1, Process.pid)
32
- end
33
- suspend
34
- assert_equal(1, count)
28
+ snooze
29
+ Process.kill(:HUP, Process.pid)
30
+ assert_equal 1, count
35
31
  end
36
32
  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.20'
4
+ version: '0.21'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-27 00:00:00.000000000 Z
11
+ date: 2019-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: modulation
@@ -213,11 +213,21 @@ files:
213
213
  - Rakefile
214
214
  - TODO.md
215
215
  - bin/poly
216
- - docs/getting-started/getting-started.md
216
+ - docs/README.md
217
+ - docs/getting-started/installing.md
217
218
  - docs/getting-started/tutorial.md
218
219
  - docs/summary.md
220
+ - docs/technical-overview/concurrency.md
221
+ - docs/technical-overview/design-principles.md
219
222
  - docs/technical-overview/exception-handling.md
223
+ - docs/technical-overview/extending.md
224
+ - docs/technical-overview/faq.md
220
225
  - docs/technical-overview/fiber-scheduling.md
226
+ - docs/user-guide/web-server.md
227
+ - examples/core/01-spinning-up-coprocesses.rb
228
+ - examples/core/02-awaiting-coprocesses.rb
229
+ - examples/core/03-interrupting.rb
230
+ - examples/core/04-no-auto-run.rb
221
231
  - examples/core/cancel.rb
222
232
  - examples/core/channel_echo.rb
223
233
  - examples/core/defer.rb
@@ -227,6 +237,7 @@ files:
227
237
  - examples/core/fork.rb
228
238
  - examples/core/genserver.rb
229
239
  - examples/core/lock.rb
240
+ - examples/core/mem-usage.rb
230
241
  - examples/core/move_on.rb
231
242
  - examples/core/move_on_twice.rb
232
243
  - examples/core/move_on_with_ensure.rb
@@ -243,8 +254,6 @@ files:
243
254
  - examples/core/sleep.rb
244
255
  - examples/core/sleep_spin.rb
245
256
  - examples/core/snooze.rb
246
- - examples/core/spin.rb
247
- - examples/core/spin_cancel.rb
248
257
  - examples/core/spin_error.rb
249
258
  - examples/core/spin_error_backtrace.rb
250
259
  - examples/core/spin_uncaught_error.rb
@@ -258,6 +267,7 @@ files:
258
267
  - examples/core/thread_pool.rb
259
268
  - examples/core/throttle.rb
260
269
  - examples/core/timeout.rb
270
+ - examples/core/wait_for_signal.rb
261
271
  - examples/fs/read.rb
262
272
  - examples/http/config.ru
263
273
  - examples/http/cuba.ru
@@ -269,6 +279,8 @@ files:
269
279
  - examples/http/http_server.js
270
280
  - examples/http/http_server.rb
271
281
  - examples/http/http_server_forked.rb
282
+ - examples/http/http_server_graceful.rb
283
+ - examples/http/http_server_simple.rb
272
284
  - examples/http/http_server_throttled.rb
273
285
  - examples/http/http_ws_server.rb
274
286
  - examples/http/https_raw_client.rb
@@ -1,10 +0,0 @@
1
- # Installing
2
-
3
- ## Installing
4
-
5
- ```bash
6
- $ gem install polyphony
7
- ```
8
-
9
- ## Tutorial
10
-
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/setup'
4
- require 'polyphony/auto_run'
5
-
6
- def my_sleep(t)
7
- puts 'going to sleep...'
8
- sleep t
9
- puts 'woke up'
10
- end
11
-
12
- spin do
13
- spin { my_sleep(1) }.await
14
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/setup'
4
- require 'polyphony/auto_run'
5
-
6
- spin do
7
- cancel_after(1) do
8
- spin do
9
- puts 'going to sleep...'
10
- sleep(2)
11
- ensure
12
- puts 'woke up'
13
- end.await
14
- end
15
- rescue Polyphony::Cancel => e
16
- puts "got error: #{e}"
17
- end