polyphony 0.99 → 0.99.1

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -1
  3. data/.rubocop.yml +3 -3
  4. data/.yardopts +30 -0
  5. data/CHANGELOG.md +4 -0
  6. data/LICENSE +1 -1
  7. data/README.md +63 -29
  8. data/Rakefile +1 -5
  9. data/TODO.md +0 -4
  10. data/docs/{main-concepts/concurrency.md → concurrency.md} +2 -9
  11. data/docs/{main-concepts/design-principles.md → design-principles.md} +3 -9
  12. data/docs/{main-concepts/exception-handling.md → exception-handling.md} +2 -9
  13. data/docs/{main-concepts/extending.md → extending.md} +2 -9
  14. data/docs/faq.md +3 -16
  15. data/docs/{main-concepts/fiber-scheduling.md → fiber-scheduling.md} +1 -9
  16. data/docs/link_rewriter.rb +16 -0
  17. data/docs/{getting-started/overview.md → overview.md} +1 -30
  18. data/docs/{getting-started/tutorial.md → tutorial.md} +3 -28
  19. data/docs/{_posts/2020-07-26-polyphony-0.44.md → whats-new.md} +3 -1
  20. data/examples/adapters/redis_client.rb +3 -2
  21. data/examples/io/echo_server.rb +1 -1
  22. data/examples/io/echo_server_plain_ruby.rb +26 -0
  23. data/ext/polyphony/backend_io_uring.c +154 -9
  24. data/ext/polyphony/backend_io_uring_context.c +21 -12
  25. data/ext/polyphony/backend_io_uring_context.h +12 -7
  26. data/ext/polyphony/backend_libev.c +1 -1
  27. data/ext/polyphony/extconf.rb +24 -8
  28. data/ext/polyphony/fiber.c +79 -2
  29. data/ext/polyphony/io_extensions.c +53 -0
  30. data/ext/polyphony/pipe.c +42 -2
  31. data/ext/polyphony/polyphony.c +345 -31
  32. data/ext/polyphony/polyphony.h +9 -2
  33. data/ext/polyphony/queue.c +181 -0
  34. data/ext/polyphony/ring_buffer.c +0 -1
  35. data/ext/polyphony/runqueue.c +8 -1
  36. data/ext/polyphony/runqueue_ring_buffer.c +13 -0
  37. data/ext/polyphony/runqueue_ring_buffer.h +2 -1
  38. data/ext/polyphony/socket_extensions.c +6 -0
  39. data/ext/polyphony/thread.c +34 -2
  40. data/lib/polyphony/adapters/process.rb +11 -1
  41. data/lib/polyphony/adapters/sequel.rb +1 -1
  42. data/lib/polyphony/core/channel.rb +2 -0
  43. data/lib/polyphony/core/debug.rb +1 -1
  44. data/lib/polyphony/core/global_api.rb +25 -24
  45. data/lib/polyphony/core/resource_pool.rb +7 -6
  46. data/lib/polyphony/core/sync.rb +2 -2
  47. data/lib/polyphony/core/thread_pool.rb +3 -3
  48. data/lib/polyphony/core/timer.rb +8 -8
  49. data/lib/polyphony/extensions/exception.rb +2 -0
  50. data/lib/polyphony/extensions/fiber.rb +15 -13
  51. data/lib/polyphony/extensions/io.rb +127 -5
  52. data/lib/polyphony/extensions/kernel.rb +20 -2
  53. data/lib/polyphony/extensions/openssl.rb +100 -11
  54. data/lib/polyphony/extensions/pipe.rb +103 -7
  55. data/lib/polyphony/extensions/process.rb +13 -1
  56. data/lib/polyphony/extensions/socket.rb +93 -27
  57. data/lib/polyphony/extensions/thread.rb +9 -1
  58. data/lib/polyphony/extensions/timeout.rb +1 -1
  59. data/lib/polyphony/version.rb +2 -1
  60. data/lib/polyphony.rb +27 -7
  61. data/polyphony.gemspec +1 -8
  62. data/test/stress.rb +1 -1
  63. data/test/test_global_api.rb +45 -7
  64. data/test/test_socket.rb +96 -0
  65. data/test/test_timer.rb +5 -5
  66. metadata +17 -40
  67. data/docs/_config.yml +0 -64
  68. data/docs/_includes/head.html +0 -40
  69. data/docs/_includes/title.html +0 -1
  70. data/docs/_sass/custom/custom.scss +0 -10
  71. data/docs/_sass/overrides.scss +0 -0
  72. data/docs/api-reference/exception.md +0 -31
  73. data/docs/api-reference/fiber.md +0 -425
  74. data/docs/api-reference/index.md +0 -9
  75. data/docs/api-reference/io.md +0 -36
  76. data/docs/api-reference/object.md +0 -99
  77. data/docs/api-reference/polyphony-baseexception.md +0 -33
  78. data/docs/api-reference/polyphony-cancel.md +0 -26
  79. data/docs/api-reference/polyphony-moveon.md +0 -24
  80. data/docs/api-reference/polyphony-net.md +0 -20
  81. data/docs/api-reference/polyphony-process.md +0 -28
  82. data/docs/api-reference/polyphony-resourcepool.md +0 -59
  83. data/docs/api-reference/polyphony-restart.md +0 -18
  84. data/docs/api-reference/polyphony-terminate.md +0 -18
  85. data/docs/api-reference/polyphony-threadpool.md +0 -67
  86. data/docs/api-reference/polyphony-throttler.md +0 -77
  87. data/docs/api-reference/polyphony.md +0 -36
  88. data/docs/api-reference/thread.md +0 -88
  89. data/docs/favicon.ico +0 -0
  90. data/docs/getting-started/index.md +0 -10
  91. data/docs/getting-started/installing.md +0 -34
  92. /data/{docs/assets/img → assets}/echo-fibers.svg +0 -0
  93. /data/{docs → assets}/polyphony-logo.png +0 -0
  94. /data/{docs/assets/img → assets}/sleeping-fiber.svg +0 -0
@@ -6,7 +6,7 @@ require_relative './io'
6
6
  require_relative '../core/thread_pool'
7
7
 
8
8
  # BasicSocket extensions
9
- class BasicSocket
9
+ class BasicSocket < ::IO
10
10
  # Returns `:backend_recv`. This method is used to tell parsers which read
11
11
  # method to use for this object.
12
12
  #
@@ -15,13 +15,17 @@ class BasicSocket
15
15
  :backend_recv
16
16
  end
17
17
 
18
+ # Returns `:backend_send`. This method is used to tell various libraries which
19
+ # write method to use for this object.
20
+ #
21
+ # @return [:backend_send] use Polyphony.backend_send to send DATA
18
22
  def __write_method__
19
23
  :backend_send
20
24
  end
21
25
  end
22
26
 
23
27
  # Socket extensions # TODO: rewrite in C
24
- class ::Socket
28
+ class ::Socket < ::BasicSocket
25
29
 
26
30
  # Accepts an incoming connection.
27
31
 
@@ -35,12 +39,13 @@ class ::Socket
35
39
  #
36
40
  # Accepts incoming connections in an infinite loop.
37
41
  #
38
- # @param &block [Proc] handler block
42
+ # @yield [Socket] block receiving accepted sockets
39
43
  # @return [void]
40
44
  def accept_loop(&block)
41
45
  Polyphony.backend_accept_loop(self, TCPSocket, &block)
42
46
  end
43
47
 
48
+ # @!visibility private
44
49
  NO_EXCEPTION = { exception: false }.freeze
45
50
 
46
51
  # Connects to the given address
@@ -53,6 +58,7 @@ class ::Socket
53
58
  self
54
59
  end
55
60
 
61
+ # @!visibility private
56
62
  alias_method :orig_read, :read
57
63
 
58
64
  # call-seq:
@@ -122,7 +128,7 @@ class ::Socket
122
128
  # will be passed to the given block.
123
129
  #
124
130
  # @param maxlen [Integer] maximum bytes to receive
125
- # @param &block [Proc] handler block
131
+ # @yield [String] handler block
126
132
  # @return [void]
127
133
  def recv_loop(maxlen = 8192, &block)
128
134
  Polyphony.backend_recv_loop(self, maxlen, &block)
@@ -149,13 +155,17 @@ class ::Socket
149
155
  #
150
156
  # @param receiver [any] receiver object
151
157
  # @param method [Symbol] method to call
152
- # @param &block [Proc] block to handle result of method call to receiver
158
+ # @yield [any] block to handle result of method call to receiver
153
159
  # @return [void]
154
160
  def feed_loop(receiver, method = :call, &block)
155
161
  Polyphony.backend_recv_feed_loop(self, receiver, method, &block)
156
162
  end
157
163
 
158
- # :no-doc:
164
+ # Reimplements #recvfrom.
165
+ #
166
+ # @param maxlen [Integer] maximum bytes to receive
167
+ # @param flags [Integer] optional flags
168
+ # @return [String] received data
159
169
  def recvfrom(maxlen, flags = 0)
160
170
  buf = +''
161
171
  while true
@@ -198,6 +208,7 @@ class ::Socket
198
208
  result
199
209
  end
200
210
 
211
+ # @!visibility private
201
212
  ZERO_LINGER = [0, 0].pack('ii').freeze
202
213
 
203
214
  # Sets the linger option to 0.
@@ -233,7 +244,8 @@ class ::Socket
233
244
  end
234
245
 
235
246
  class << self
236
- alias_method :orig_getaddrinfo, :getaddrinfo
247
+ # @!visibility private
248
+ alias_method :orig_getaddrinfo, :getaddrinfo
237
249
 
238
250
  # Resolves the given addr using a worker thread from the default thread
239
251
  # pool.
@@ -246,9 +258,11 @@ class ::Socket
246
258
  end
247
259
 
248
260
  # Overide stock TCPSocket code by encapsulating a Socket instance
249
- class ::TCPSocket
261
+ class ::TCPSocket < ::IPSocket
262
+ # @!visibility private
250
263
  NO_EXCEPTION = { exception: false }.freeze
251
264
 
265
+ # @!visibility private
252
266
  attr_reader :io
253
267
 
254
268
  class << self
@@ -275,6 +289,7 @@ class ::TCPSocket
275
289
  @io.connect(addr)
276
290
  end
277
291
 
292
+ # @!visibility private
278
293
  alias_method :orig_close, :close
279
294
 
280
295
  # Closes the socket.
@@ -285,6 +300,7 @@ class ::TCPSocket
285
300
  self
286
301
  end
287
302
 
303
+ # @!visibility private
288
304
  alias_method :orig_setsockopt, :setsockopt
289
305
 
290
306
  # Calls `setsockopt` with the given arguments.
@@ -295,6 +311,7 @@ class ::TCPSocket
295
311
  self
296
312
  end
297
313
 
314
+ # @!visibility private
298
315
  alias_method :orig_closed?, :closed?
299
316
 
300
317
  # Returns true if the socket is closed.
@@ -336,6 +353,7 @@ class ::TCPSocket
336
353
  self
337
354
  end
338
355
 
356
+ # @!visibility private
339
357
  alias_method :orig_read, :read
340
358
 
341
359
  # call-seq:
@@ -405,7 +423,7 @@ class ::TCPSocket
405
423
  # will be passed to the given block.
406
424
  #
407
425
  # @param maxlen [Integer] maximum bytes to receive
408
- # @param &block [Proc] handler block
426
+ # @yield [String] handler block
409
427
  # @return [void]
410
428
  def recv_loop(maxlen = 8192, &block)
411
429
  Polyphony.backend_recv_loop(self, maxlen, &block)
@@ -432,7 +450,7 @@ class ::TCPSocket
432
450
  #
433
451
  # @param receiver [any] receiver object
434
452
  # @param method [Symbol] method to call
435
- # @param &block [Proc] block to handle result of method call to receiver
453
+ # @yield [any] block to handle result of method call to receiver
436
454
  # @return [void]
437
455
  def feed_loop(receiver, method = :call, &block)
438
456
  Polyphony.backend_recv_feed_loop(self, receiver, method, &block)
@@ -460,8 +478,8 @@ class ::TCPSocket
460
478
  # @param buf_pos [Number] buffer position to read into
461
479
  # @param raise_on_eof [bool] whether to raise an exception on `EOF`
462
480
  # @return [String, nil] buffer used for reading or nil on `EOF`
463
- def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof = true)
464
- result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
481
+ def readpartial(maxlen, buf = +'', buf_pos = 0, raise_on_eof = true)
482
+ result = Polyphony.backend_recv(self, buf, maxlen, buf_pos)
465
483
  raise EOFError if !result && raise_on_eof
466
484
  result
467
485
  end
@@ -476,8 +494,8 @@ class ::TCPSocket
476
494
  # @param buf [String, nil] read buffer
477
495
  # @param exception [bool] whether to raise an exception if not ready for reading
478
496
  # @return [String, :wait_readable] read buffer
479
- def read_nonblock(len, buf = nil, exception: true)
480
- @io.read_nonblock(len, buf, exception: exception)
497
+ def read_nonblock(maxlen, buf = nil, exception: true)
498
+ @io.read_nonblock(maxlen, buf, exception: exception)
481
499
  end
482
500
 
483
501
  # Performs a non-blocking to the socket. If the socket is not ready for
@@ -494,7 +512,7 @@ class ::TCPSocket
494
512
  end
495
513
 
496
514
  # TCPServer extensions
497
- class ::TCPServer
515
+ class ::TCPServer < ::TCPSocket
498
516
 
499
517
  # Initializes the TCP server socket.
500
518
  #
@@ -503,10 +521,12 @@ class ::TCPServer
503
521
  def initialize(hostname = nil, port = 0)
504
522
  addr = Addrinfo.tcp(hostname, port)
505
523
  @io = Socket.new addr.afamily, Socket::SOCK_STREAM
524
+ @io.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
506
525
  @io.bind(addr)
507
526
  @io.listen(0)
508
527
  end
509
528
 
529
+ # @!visibility private
510
530
  alias_method :orig_accept, :accept
511
531
 
512
532
  # Accepts an incoming connection.
@@ -516,17 +536,33 @@ class ::TCPServer
516
536
  Polyphony.backend_accept(@io, TCPSocket)
517
537
  end
518
538
 
539
+ if Polyphony.instance_methods(false).include?(:backend_multishot_accept)
540
+ # Starts a multishot accept operation (only available with io_uring
541
+ # backend). Example usage:
542
+ #
543
+ # server.multishot_accept do
544
+ # server.accept_loop { |c| handle_connection(c) }
545
+ # end
546
+ #
547
+ # @yield [TCPSocket] code block
548
+ # @return [any] return value of code block
549
+ def multishot_accept(&block)
550
+ Polyphony.backend_multishot_accept(@io, &block)
551
+ end
552
+ end
553
+
519
554
  # call-seq:
520
555
  # socket.accept_loop { |conn| ... }
521
556
  #
522
557
  # Accepts incoming connections in an infinite loop.
523
558
  #
524
- # @param &block [Proc] handler block
559
+ # @yield [TCPSocket] handler block
525
560
  # @return [void]
526
561
  def accept_loop(&block)
527
562
  Polyphony.backend_accept_loop(@io, TCPSocket, &block)
528
563
  end
529
564
 
565
+ # @!visibility private
530
566
  alias_method :orig_close, :close
531
567
 
532
568
  # Closes the server socket.
@@ -538,7 +574,9 @@ class ::TCPServer
538
574
  end
539
575
  end
540
576
 
541
- class ::UNIXServer
577
+ # UNIXServer extensions
578
+ class ::UNIXServer < ::UNIXSocket
579
+ # @!visibility private
542
580
  alias_method :orig_accept, :accept
543
581
 
544
582
  # Accepts an incoming connection.
@@ -553,14 +591,16 @@ class ::UNIXServer
553
591
  #
554
592
  # Accepts incoming connections in an infinite loop.
555
593
  #
556
- # @param &block [Proc] handler block
594
+ # @yield [UNIXSocket] handler block
557
595
  # @return [void]
558
596
  def accept_loop(&block)
559
597
  Polyphony.backend_accept_loop(self, UNIXSocket, &block)
560
598
  end
561
599
  end
562
600
 
563
- class ::UNIXSocket
601
+ # UNIXSocket extensions
602
+ class ::UNIXSocket < ::BasicSocket
603
+ # @!visibility private
564
604
  alias_method :orig_read, :read
565
605
 
566
606
  # call-seq:
@@ -630,7 +670,7 @@ class ::UNIXSocket
630
670
  # will be passed to the given block.
631
671
  #
632
672
  # @param maxlen [Integer] maximum bytes to receive
633
- # @param &block [Proc] handler block
673
+ # @yield [String] handler block
634
674
  # @return [void]
635
675
  def recv_loop(maxlen = 8192, &block)
636
676
  Polyphony.backend_recv_loop(self, maxlen, &block)
@@ -657,7 +697,7 @@ class ::UNIXSocket
657
697
  #
658
698
  # @param receiver [any] receiver object
659
699
  # @param method [Symbol] method to call
660
- # @param &block [Proc] block to handle result of method call to receiver
700
+ # @yield [any] block to handle result of method call to receiver
661
701
  # @return [void]
662
702
  def feed_loop(receiver, method = :call, &block)
663
703
  Polyphony.backend_recv_feed_loop(self, receiver, method, &block)
@@ -675,7 +715,7 @@ class ::UNIXSocket
675
715
  # Sends one or more strings on the socket. The strings are guaranteed to be
676
716
  # written as a single blocking operation.
677
717
  #
678
- # @param *args [Array<String>] string buffers to write
718
+ # @param args [Array<String>] string buffers to write
679
719
  # @return [Integer] number of bytes written
680
720
  def write(*args)
681
721
  Polyphony.backend_sendv(self, args, 0)
@@ -711,8 +751,8 @@ class ::UNIXSocket
711
751
  # @param buf_pos [Number] buffer position to read into
712
752
  # @param raise_on_eof [bool] whether to raise an exception on `EOF`
713
753
  # @return [String, nil] buffer used for reading or nil on `EOF`
714
- def readpartial(maxlen, str = +'', buffer_pos = 0, raise_on_eof = true)
715
- result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
754
+ def readpartial(maxlen, buf = +'', buf_pos = 0, raise_on_eof = true)
755
+ result = Polyphony.backend_recv(self, buf, maxlen, buf_pos)
716
756
  raise EOFError if !result && raise_on_eof
717
757
  result
718
758
  end
@@ -727,8 +767,8 @@ class ::UNIXSocket
727
767
  # @param buf [String, nil] read buffer
728
768
  # @param exception [bool] whether to raise an exception if not ready for reading
729
769
  # @return [String, :wait_readable] read buffer
730
- def read_nonblock(len, str = nil, exception: true)
731
- @io.read_nonblock(len, str, exception: exception)
770
+ def read_nonblock(maxlen, buf = nil, exception: true)
771
+ @io.read_nonblock(maxlen, buf, exception: exception)
732
772
  end
733
773
 
734
774
  # Performs a non-blocking to the socket. If the socket is not ready for
@@ -744,21 +784,47 @@ class ::UNIXSocket
744
784
  end
745
785
  end
746
786
 
747
- class ::UDPSocket
787
+ # UDPSocket extensions
788
+ class ::UDPSocket < ::IPSocket
789
+ # Reimplements #recvfrom.
790
+ #
791
+ # @param maxlen [Integer] maximum bytes to receive
792
+ # @param flags [Integer] optional flags
793
+ # @return [String] received data
748
794
  def recvfrom(maxlen, flags = 0)
749
795
  buf = +''
750
796
  Polyphony.backend_recvmsg(self, buf, maxlen, 0, flags, 0, nil)
751
797
  end
752
798
 
799
+ # Reimplements #recvmsg.
800
+ #
801
+ # @param maxlen [Integer] maximum bytes to receive
802
+ # @param flags [Integer] optional flags
803
+ # @param maxcontrollen [Integer] maximum control bytes to receive
804
+ # @param opts [Hash] options
805
+ # @return [String] received data
753
806
  def recvmsg(maxlen = nil, flags = 0, maxcontrollen = nil, opts = {})
754
807
  buf = +''
755
808
  Polyphony.backend_recvmsg(self, buf, maxlen || 4096, 0, flags, maxcontrollen, opts)
756
809
  end
757
810
 
811
+ # Reimplements #sendmsg.
812
+ #
813
+ # @param msg [String] data to send
814
+ # @param flags [Integer] optional flags
815
+ # @param dest_sockaddr [Sockaddr, nil] optional destination address
816
+ # @param controls [Array] optional control data
817
+ # @return [Integer] bytes sent
758
818
  def sendmsg(msg, flags = 0, dest_sockaddr = nil, *controls)
759
819
  Polyphony.backend_sendmsg(self, msg, flags, dest_sockaddr, controls)
760
820
  end
761
821
 
822
+ # Sends data.
823
+ #
824
+ # @param msg [String] data to send
825
+ # @param flags [Integer] flags
826
+ # @param addr [Array] addresses to send to
827
+ # @return [Integer] bytes sent
762
828
  def send(msg, flags, *addr)
763
829
  sockaddr = case addr.size
764
830
  when 2
@@ -7,9 +7,13 @@ class ::Thread
7
7
  attr_reader :main_fiber, :result
8
8
  attr_accessor :backend
9
9
 
10
+ # @!visibility private
10
11
  alias_method :orig_initialize, :initialize
11
12
 
12
13
  # Initializes the thread.
14
+ # @param args [Array] arguments to pass to thread block
15
+ # @yield [any] thread block
16
+ # @return [void]
13
17
  def initialize(*args, &block)
14
18
  @join_wait_queue = []
15
19
  @finalization_mutex = Mutex.new
@@ -27,6 +31,7 @@ class ::Thread
27
31
  setup_fiber_scheduling
28
32
  end
29
33
 
34
+ # @!visibility private
30
35
  alias_method :orig_join, :join
31
36
 
32
37
  # call-seq:
@@ -56,6 +61,7 @@ class ::Thread
56
61
  end
57
62
  alias_method :await, :join
58
63
 
64
+ # @!visibility private
59
65
  alias_method :orig_raise, :raise
60
66
 
61
67
  # call-seq:
@@ -77,6 +83,7 @@ class ::Thread
77
83
  main_fiber&.raise(error)
78
84
  end
79
85
 
86
+ # @!visibility private
80
87
  alias_method :orig_kill, :kill
81
88
 
82
89
  # Terminates the thread.
@@ -89,6 +96,7 @@ class ::Thread
89
96
  self
90
97
  end
91
98
 
99
+ # @!visibility private
92
100
  alias_method :orig_inspect, :inspect
93
101
 
94
102
  # Returns a string representation of the thread for debugging purposes.
@@ -128,7 +136,7 @@ class ::Thread
128
136
 
129
137
  # Sets the idle handler for the thread's backend.
130
138
  #
131
- # @param &block [Proc] idle handler
139
+ # @yield [] idle handler
132
140
  # @return [Proc] idle handler
133
141
  def on_idle(&block)
134
142
  backend.idle_proc = block
@@ -13,7 +13,7 @@ module ::Timeout
13
13
  # @param sec [Number] timeout period in seconds
14
14
  # @param klass [Class] exception class
15
15
  # @param message [String] exception message
16
- # @param &block [Proc] code to run
16
+ # @yield [] code to run
17
17
  # @return [any] block's return value
18
18
  def self.timeout(sec, klass = Timeout::Error, message = 'execution expired', &block)
19
19
  cancel_after(sec, with_exception: [klass, message], &block)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.99'
4
+ # @!visibility private
5
+ VERSION = '0.99.1'
5
6
  end
data/lib/polyphony.rb CHANGED
@@ -18,10 +18,14 @@ require_relative './polyphony/adapters/process'
18
18
  # Polyphony API
19
19
  module Polyphony
20
20
  class << self
21
+ # Creates a new Polyphony::Pipe instance.
22
+ #
23
+ # @return [Polyphony::Pipe] created pipe
21
24
  def pipe
22
25
  Pipe.new
23
26
  end
24
27
 
28
+ # @!visibility private
25
29
  def fork(&block)
26
30
  Kernel.fork do
27
31
  # A race condition can arise if a TERM or INT signal is received before
@@ -39,6 +43,19 @@ module Polyphony
39
43
  end
40
44
  end
41
45
 
46
+ # call-seq:
47
+ # Polyphony.watch_process(cmd)
48
+ # Polyphony.watch_process { sleep 1 }
49
+ #
50
+ # Lubnches a process using either a command or a block for a forked process,
51
+ # waiting for the child process to terminate.
52
+ def watch_process(cmd = nil, &block)
53
+ Polyphony::Process.watch(cmd, &block)
54
+ end
55
+
56
+ private
57
+
58
+ # @!visibility private
42
59
  def spin_forked_block(&block)
43
60
  Fiber.new do
44
61
  run_forked_block(&block)
@@ -54,6 +71,7 @@ module Polyphony
54
71
  end
55
72
  end
56
73
 
74
+ # @!visibility private
57
75
  def run_forked_block(&block)
58
76
  Thread.current.setup
59
77
  Thread.current.backend.post_fork
@@ -63,6 +81,7 @@ module Polyphony
63
81
  block.()
64
82
  end
65
83
 
84
+ # @!visibility private
66
85
  def exit_forked_process
67
86
  terminate_threads
68
87
  Fiber.current.shutdown_all_children
@@ -74,10 +93,7 @@ module Polyphony
74
93
  exit
75
94
  end
76
95
 
77
- def watch_process(cmd = nil, &block)
78
- Polyphony::Process.watch(cmd, &block)
79
- end
80
-
96
+ # @!visibility private
81
97
  def install_terminating_signal_handlers
82
98
  trap('SIGTERM') { raise SystemExit }
83
99
  orig_trap('SIGINT') do
@@ -86,6 +102,7 @@ module Polyphony
86
102
  end
87
103
  end
88
104
 
105
+ # @!visibility private
89
106
  def terminate_threads
90
107
  threads = Thread.list - [Thread.current]
91
108
  return if threads.empty?
@@ -94,8 +111,10 @@ module Polyphony
94
111
  threads.each(&:join)
95
112
  end
96
113
 
114
+ # @!visibility private
97
115
  attr_accessor :original_pid
98
116
 
117
+ # @!visibility private
99
118
  def install_at_exit_handler
100
119
  @original_pid = ::Process.pid
101
120
 
@@ -106,7 +125,7 @@ module Polyphony
106
125
  at_exit do
107
126
  next unless @original_pid == ::Process.pid
108
127
 
109
- Polyphony.terminate_threads
128
+ terminate_threads
110
129
  Fiber.current.shutdown_all_children
111
130
  end
112
131
  end
@@ -123,10 +142,11 @@ module Polyphony
123
142
 
124
143
  Object.const_set(:ConditionVariable, Polyphony::ConditionVariable)
125
144
  $VERBOSE = verbose
145
+
146
+ install_terminating_signal_handlers
147
+ install_at_exit_handler
126
148
  end
127
149
 
128
- Polyphony.install_terminating_signal_handlers
129
- Polyphony.install_at_exit_handler
130
150
 
131
151
  if (debug_socket_path = ENV['POLYPHONY_DEBUG_SOCKET_PATH'])
132
152
  puts "Starting debug server on #{debug_socket_path}"
data/polyphony.gemspec CHANGED
@@ -11,9 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.homepage = 'https://digital-fabric.github.io/polyphony'
12
12
  s.metadata = {
13
13
  "source_code_uri" => "https://github.com/digital-fabric/polyphony",
14
- # "documentation_uri" => "https://www.rubydoc.info/gems/polyphony",
15
- "documentation_uri" => "https://digital-fabric.github.io/polyphony/",
16
- "homepage_uri" => "https://digital-fabric.github.io/polyphony/",
14
+ "documentation_uri" => "https://www.rubydoc.info/gems/polyphony",
17
15
  "changelog_uri" => "https://github.com/digital-fabric/polyphony/blob/master/CHANGELOG.md"
18
16
  }
19
17
  s.rdoc_options = ["--title", "polyphony", "--main", "README.md"]
@@ -31,9 +29,4 @@ Gem::Specification.new do |s|
31
29
  s.add_development_dependency 'msgpack', '1.6.0'
32
30
  s.add_development_dependency 'httparty', '0.21.0'
33
31
  s.add_development_dependency 'localhost', '1.1.10'
34
-
35
- # s.add_development_dependency 'jekyll', '~>3.8.6'
36
- # s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
37
- # s.add_development_dependency 'jekyll-seo-tag', '~>2.6.1'
38
- # s.add_development_dependency 'just-the-docs', '~>0.3.0'
39
32
  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 --name test_signal_handler_trace'
6
+ $test_cmd = +'ruby test/run.rb'
7
7
  if test_name
8
8
  $test_cmd << " --name #{test_name}"
9
9
  end
@@ -145,13 +145,13 @@ class MoveOnAfterTest < MiniTest::Test
145
145
 
146
146
  t0 = monotonic_clock
147
147
  o = move_on_after(0.01, with_value: 1) do
148
- move_on_after(0.02, with_value: 2) do
148
+ move_on_after(0.03, with_value: 2) do
149
149
  sleep 1
150
150
  end
151
151
  end
152
152
  t1 = monotonic_clock
153
153
  assert_equal 1, o
154
- assert_in_range 0.008..0.015, t1 - t0 if IS_LINUX
154
+ assert_in_range 0.008..0.027, t1 - t0 if IS_LINUX
155
155
 
156
156
  t0 = monotonic_clock
157
157
  o = move_on_after(0.05, with_value: 1) do
@@ -161,7 +161,7 @@ class MoveOnAfterTest < MiniTest::Test
161
161
  end
162
162
  t1 = monotonic_clock
163
163
  assert_equal 2, o
164
- assert_in_range 0.008..0.013, t1 - t0 if IS_LINUX
164
+ assert_in_range 0.008..0.025, t1 - t0 if IS_LINUX
165
165
  end
166
166
  end
167
167
 
@@ -181,15 +181,17 @@ class CancelAfterTest < MiniTest::Test
181
181
 
182
182
  def test_cancel_after_with_reset
183
183
  t0 = monotonic_clock
184
- cancel_after(0.01) do |f|
184
+ cancel_after(0.1) do |f|
185
185
  assert_kind_of Fiber, f
186
186
  assert_equal Fiber.current, f.parent
187
- sleep 0.007
187
+ sleep 0.05
188
188
  f.reset
189
- sleep 0.007
189
+ sleep 0.05
190
+ f.reset
191
+ sleep 0.05
190
192
  end
191
193
  t1 = monotonic_clock
192
- assert_in_range 0.014..0.024, t1 - t0 if IS_LINUX
194
+ assert_in_range 0.14..0.24, t1 - t0 if IS_LINUX
193
195
  end
194
196
 
195
197
  class CustomException < Exception
@@ -228,6 +230,34 @@ class CancelAfterTest < MiniTest::Test
228
230
  assert_equal 'foo', e.message
229
231
  end
230
232
  end
233
+
234
+ def test_lots_of_cancel_after
235
+ cancels = 100
236
+
237
+ cancel_count = 0
238
+ cancels.times do
239
+ begin
240
+ cancel_after(0.001) { sleep 1 }
241
+ rescue Polyphony::Cancel
242
+ cancel_count += 1
243
+ end
244
+ end
245
+ assert_equal cancels, cancel_count
246
+ end
247
+
248
+ def test_cancel_after_with_lots_of_resets
249
+ resets = 100
250
+
251
+ t0 = monotonic_clock
252
+ cancel_after(0.1) do |f|
253
+ resets.times do
254
+ sleep 0.0001
255
+ f.reset
256
+ end
257
+ end
258
+ t1 = monotonic_clock
259
+ assert_in_range 0.01..0.2, t1 - t0 if IS_LINUX
260
+ end
231
261
  end
232
262
 
233
263
 
@@ -381,6 +411,14 @@ class ThrottledLoopTest < MiniTest::Test
381
411
  assert_in_range 0.075..0.15, t1 - t0 if IS_LINUX
382
412
  assert_equal [1, 2, 3, 4, 5], buffer
383
413
  end
414
+
415
+ def test_throttled_loop_inside_move_on_after
416
+ count = 0
417
+ move_on_after(0.1) do
418
+ throttled_loop(50) { count += 1 }
419
+ end
420
+ assert_in_range 3..7, count
421
+ end
384
422
  end
385
423
 
386
424
  class GlobalAPIEtcTest < MiniTest::Test