polyphony 0.43.8

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 (221) hide show
  1. checksums.yaml +7 -0
  2. data/.gitbook.yaml +4 -0
  3. data/.github/workflows/test.yml +29 -0
  4. data/.gitignore +59 -0
  5. data/.rubocop.yml +175 -0
  6. data/CHANGELOG.md +393 -0
  7. data/Gemfile +3 -0
  8. data/Gemfile.lock +141 -0
  9. data/LICENSE +21 -0
  10. data/README.md +51 -0
  11. data/Rakefile +26 -0
  12. data/TODO.md +201 -0
  13. data/bin/polyphony-debug +87 -0
  14. data/docs/_config.yml +64 -0
  15. data/docs/_includes/head.html +40 -0
  16. data/docs/_includes/title.html +1 -0
  17. data/docs/_sass/custom/custom.scss +10 -0
  18. data/docs/_sass/overrides.scss +0 -0
  19. data/docs/_user-guide/all-about-timers.md +126 -0
  20. data/docs/_user-guide/index.md +9 -0
  21. data/docs/_user-guide/web-server.md +136 -0
  22. data/docs/api-reference/exception.md +27 -0
  23. data/docs/api-reference/fiber.md +425 -0
  24. data/docs/api-reference/index.md +9 -0
  25. data/docs/api-reference/io.md +36 -0
  26. data/docs/api-reference/object.md +99 -0
  27. data/docs/api-reference/polyphony-baseexception.md +33 -0
  28. data/docs/api-reference/polyphony-cancel.md +26 -0
  29. data/docs/api-reference/polyphony-moveon.md +24 -0
  30. data/docs/api-reference/polyphony-net.md +20 -0
  31. data/docs/api-reference/polyphony-process.md +28 -0
  32. data/docs/api-reference/polyphony-resourcepool.md +59 -0
  33. data/docs/api-reference/polyphony-restart.md +18 -0
  34. data/docs/api-reference/polyphony-terminate.md +18 -0
  35. data/docs/api-reference/polyphony-threadpool.md +67 -0
  36. data/docs/api-reference/polyphony-throttler.md +77 -0
  37. data/docs/api-reference/polyphony.md +36 -0
  38. data/docs/api-reference/thread.md +88 -0
  39. data/docs/assets/img/echo-fibers.svg +1 -0
  40. data/docs/assets/img/sleeping-fiber.svg +1 -0
  41. data/docs/faq.md +195 -0
  42. data/docs/favicon.ico +0 -0
  43. data/docs/getting-started/index.md +10 -0
  44. data/docs/getting-started/installing.md +34 -0
  45. data/docs/getting-started/overview.md +486 -0
  46. data/docs/getting-started/tutorial.md +359 -0
  47. data/docs/index.md +94 -0
  48. data/docs/main-concepts/concurrency.md +151 -0
  49. data/docs/main-concepts/design-principles.md +161 -0
  50. data/docs/main-concepts/exception-handling.md +291 -0
  51. data/docs/main-concepts/extending.md +89 -0
  52. data/docs/main-concepts/fiber-scheduling.md +197 -0
  53. data/docs/main-concepts/index.md +9 -0
  54. data/docs/polyphony-logo.png +0 -0
  55. data/examples/adapters/concurrent-ruby.rb +9 -0
  56. data/examples/adapters/pg_client.rb +36 -0
  57. data/examples/adapters/pg_notify.rb +35 -0
  58. data/examples/adapters/pg_pool.rb +43 -0
  59. data/examples/adapters/pg_transaction.rb +31 -0
  60. data/examples/adapters/redis_blpop.rb +12 -0
  61. data/examples/adapters/redis_channels.rb +122 -0
  62. data/examples/adapters/redis_client.rb +19 -0
  63. data/examples/adapters/redis_pubsub.rb +26 -0
  64. data/examples/adapters/redis_pubsub_perf.rb +68 -0
  65. data/examples/core/01-spinning-up-fibers.rb +18 -0
  66. data/examples/core/02-awaiting-fibers.rb +20 -0
  67. data/examples/core/03-interrupting.rb +39 -0
  68. data/examples/core/04-handling-signals.rb +19 -0
  69. data/examples/core/xx-agent.rb +102 -0
  70. data/examples/core/xx-at_exit.rb +29 -0
  71. data/examples/core/xx-caller.rb +12 -0
  72. data/examples/core/xx-channels.rb +45 -0
  73. data/examples/core/xx-daemon.rb +14 -0
  74. data/examples/core/xx-deadlock.rb +8 -0
  75. data/examples/core/xx-deferring-an-operation.rb +14 -0
  76. data/examples/core/xx-erlang-style-genserver.rb +81 -0
  77. data/examples/core/xx-exception-backtrace.rb +40 -0
  78. data/examples/core/xx-fork-cleanup.rb +22 -0
  79. data/examples/core/xx-fork-spin.rb +42 -0
  80. data/examples/core/xx-fork-terminate.rb +27 -0
  81. data/examples/core/xx-forking.rb +24 -0
  82. data/examples/core/xx-move_on.rb +23 -0
  83. data/examples/core/xx-pingpong.rb +18 -0
  84. data/examples/core/xx-queue-async.rb +120 -0
  85. data/examples/core/xx-readpartial.rb +18 -0
  86. data/examples/core/xx-recurrent-timer.rb +12 -0
  87. data/examples/core/xx-resource_delegate.rb +31 -0
  88. data/examples/core/xx-signals.rb +16 -0
  89. data/examples/core/xx-sleep-forever.rb +9 -0
  90. data/examples/core/xx-sleeping.rb +25 -0
  91. data/examples/core/xx-snooze-starve.rb +16 -0
  92. data/examples/core/xx-spin-fork.rb +49 -0
  93. data/examples/core/xx-spin_error_backtrace.rb +33 -0
  94. data/examples/core/xx-state-machine.rb +51 -0
  95. data/examples/core/xx-stop.rb +20 -0
  96. data/examples/core/xx-supervise-process.rb +30 -0
  97. data/examples/core/xx-supervisors.rb +21 -0
  98. data/examples/core/xx-thread-selector-sleep.rb +51 -0
  99. data/examples/core/xx-thread-selector-snooze.rb +46 -0
  100. data/examples/core/xx-thread-sleep.rb +17 -0
  101. data/examples/core/xx-thread-snooze.rb +34 -0
  102. data/examples/core/xx-thread_pool.rb +17 -0
  103. data/examples/core/xx-throttling.rb +18 -0
  104. data/examples/core/xx-timeout.rb +10 -0
  105. data/examples/core/xx-timer-gc.rb +17 -0
  106. data/examples/core/xx-trace.rb +79 -0
  107. data/examples/core/xx-using-a-mutex.rb +21 -0
  108. data/examples/core/xx-worker-thread.rb +30 -0
  109. data/examples/io/tunnel.rb +48 -0
  110. data/examples/io/xx-backticks.rb +11 -0
  111. data/examples/io/xx-echo_client.rb +25 -0
  112. data/examples/io/xx-echo_client_from_stdin.rb +21 -0
  113. data/examples/io/xx-echo_pipe.rb +16 -0
  114. data/examples/io/xx-echo_server.rb +17 -0
  115. data/examples/io/xx-echo_server_with_timeout.rb +34 -0
  116. data/examples/io/xx-echo_stdin.rb +14 -0
  117. data/examples/io/xx-happy-eyeballs.rb +36 -0
  118. data/examples/io/xx-httparty.rb +38 -0
  119. data/examples/io/xx-irb.rb +17 -0
  120. data/examples/io/xx-net-http.rb +15 -0
  121. data/examples/io/xx-open.rb +16 -0
  122. data/examples/io/xx-switch.rb +15 -0
  123. data/examples/io/xx-system.rb +11 -0
  124. data/examples/io/xx-tcpserver.rb +15 -0
  125. data/examples/io/xx-tcpsocket.rb +18 -0
  126. data/examples/io/xx-zip.rb +19 -0
  127. data/examples/performance/fiber_transfer.rb +47 -0
  128. data/examples/performance/fs_read.rb +38 -0
  129. data/examples/performance/mem-usage.rb +56 -0
  130. data/examples/performance/messaging.rb +29 -0
  131. data/examples/performance/multi_snooze.rb +33 -0
  132. data/examples/performance/snooze.rb +39 -0
  133. data/examples/performance/snooze_raw.rb +39 -0
  134. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +74 -0
  135. data/examples/performance/thread-vs-fiber/polyphony_server.rb +45 -0
  136. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
  137. data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
  138. data/examples/performance/thread-vs-fiber/xx-httparty_multi.rb +36 -0
  139. data/examples/performance/thread-vs-fiber/xx-httparty_threaded.rb +29 -0
  140. data/examples/performance/thread_pool_perf.rb +63 -0
  141. data/examples/performance/xx-array.rb +11 -0
  142. data/examples/performance/xx-fiber-switch.rb +9 -0
  143. data/examples/performance/xx-snooze.rb +15 -0
  144. data/examples/xx-spin.rb +32 -0
  145. data/ext/libev/Changes +548 -0
  146. data/ext/libev/LICENSE +37 -0
  147. data/ext/libev/README +59 -0
  148. data/ext/libev/README.embed +3 -0
  149. data/ext/libev/ev.c +5279 -0
  150. data/ext/libev/ev.h +856 -0
  151. data/ext/libev/ev_epoll.c +296 -0
  152. data/ext/libev/ev_kqueue.c +224 -0
  153. data/ext/libev/ev_linuxaio.c +642 -0
  154. data/ext/libev/ev_poll.c +156 -0
  155. data/ext/libev/ev_port.c +192 -0
  156. data/ext/libev/ev_select.c +316 -0
  157. data/ext/libev/ev_vars.h +215 -0
  158. data/ext/libev/ev_win32.c +162 -0
  159. data/ext/libev/ev_wrap.h +216 -0
  160. data/ext/libev/test_libev_win32.c +123 -0
  161. data/ext/polyphony/extconf.rb +20 -0
  162. data/ext/polyphony/fiber.c +109 -0
  163. data/ext/polyphony/libev.c +2 -0
  164. data/ext/polyphony/libev.h +9 -0
  165. data/ext/polyphony/libev_agent.c +882 -0
  166. data/ext/polyphony/polyphony.c +71 -0
  167. data/ext/polyphony/polyphony.h +97 -0
  168. data/ext/polyphony/polyphony_ext.c +21 -0
  169. data/ext/polyphony/queue.c +168 -0
  170. data/ext/polyphony/ring_buffer.c +96 -0
  171. data/ext/polyphony/ring_buffer.h +28 -0
  172. data/ext/polyphony/thread.c +208 -0
  173. data/ext/polyphony/tracing.c +11 -0
  174. data/lib/polyphony.rb +136 -0
  175. data/lib/polyphony/adapters/fs.rb +19 -0
  176. data/lib/polyphony/adapters/irb.rb +52 -0
  177. data/lib/polyphony/adapters/postgres.rb +110 -0
  178. data/lib/polyphony/adapters/process.rb +33 -0
  179. data/lib/polyphony/adapters/redis.rb +67 -0
  180. data/lib/polyphony/adapters/trace.rb +138 -0
  181. data/lib/polyphony/core/channel.rb +46 -0
  182. data/lib/polyphony/core/exceptions.rb +36 -0
  183. data/lib/polyphony/core/global_api.rb +124 -0
  184. data/lib/polyphony/core/resource_pool.rb +117 -0
  185. data/lib/polyphony/core/sync.rb +21 -0
  186. data/lib/polyphony/core/thread_pool.rb +64 -0
  187. data/lib/polyphony/core/throttler.rb +41 -0
  188. data/lib/polyphony/event.rb +17 -0
  189. data/lib/polyphony/extensions/core.rb +174 -0
  190. data/lib/polyphony/extensions/fiber.rb +379 -0
  191. data/lib/polyphony/extensions/io.rb +221 -0
  192. data/lib/polyphony/extensions/openssl.rb +81 -0
  193. data/lib/polyphony/extensions/socket.rb +150 -0
  194. data/lib/polyphony/extensions/thread.rb +108 -0
  195. data/lib/polyphony/net.rb +77 -0
  196. data/lib/polyphony/version.rb +5 -0
  197. data/polyphony.gemspec +40 -0
  198. data/test/coverage.rb +54 -0
  199. data/test/eg.rb +27 -0
  200. data/test/helper.rb +56 -0
  201. data/test/q.rb +24 -0
  202. data/test/run.rb +5 -0
  203. data/test/stress.rb +25 -0
  204. data/test/test_agent.rb +130 -0
  205. data/test/test_event.rb +59 -0
  206. data/test/test_ext.rb +196 -0
  207. data/test/test_fiber.rb +988 -0
  208. data/test/test_global_api.rb +352 -0
  209. data/test/test_io.rb +249 -0
  210. data/test/test_kernel.rb +57 -0
  211. data/test/test_process_supervision.rb +46 -0
  212. data/test/test_queue.rb +112 -0
  213. data/test/test_resource_pool.rb +138 -0
  214. data/test/test_signal.rb +100 -0
  215. data/test/test_socket.rb +34 -0
  216. data/test/test_supervise.rb +103 -0
  217. data/test/test_thread.rb +170 -0
  218. data/test/test_thread_pool.rb +101 -0
  219. data/test/test_throttler.rb +50 -0
  220. data/test/test_trace.rb +68 -0
  221. metadata +482 -0
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/exceptions'
4
+
5
+ # Thread extensions
6
+ class ::Thread
7
+ attr_reader :main_fiber, :result
8
+
9
+ alias_method :orig_initialize, :initialize
10
+ def initialize(*args, &block)
11
+ @join_wait_queue = []
12
+ @finalization_mutex = Mutex.new
13
+ @args = args
14
+ @block = block
15
+ orig_initialize { execute }
16
+ end
17
+
18
+ def execute
19
+ # agent must be created in the context of the new thread, therefore it
20
+ # cannot be created in Thread#initialize
21
+ @agent = Polyphony::LibevAgent.new
22
+ setup
23
+ @ready = true
24
+ result = @block.(*@args)
25
+ rescue Polyphony::MoveOn, Polyphony::Terminate => e
26
+ result = e.value
27
+ rescue Exception => result
28
+ ensure
29
+ @ready = true
30
+ finalize(result)
31
+ end
32
+
33
+ attr_accessor :agent
34
+
35
+ def setup
36
+ @main_fiber = Fiber.current
37
+ @main_fiber.setup_main_fiber
38
+ setup_fiber_scheduling
39
+ end
40
+
41
+ def finalize(result)
42
+ unless Fiber.current.children.empty?
43
+ Fiber.current.terminate_all_children
44
+ Fiber.current.await_all_children
45
+ end
46
+ @finalization_mutex.synchronize do
47
+ @terminated = true
48
+ @result = result
49
+ signal_waiters(result)
50
+ end
51
+ @agent.finalize
52
+ end
53
+
54
+ def signal_waiters(result)
55
+ @join_wait_queue.each { |w| w.signal(result) }
56
+ end
57
+
58
+ alias_method :orig_join, :join
59
+ def join(timeout = nil)
60
+ watcher = Fiber.current.auto_watcher
61
+
62
+ @finalization_mutex.synchronize do
63
+ if @terminated
64
+ @result.is_a?(Exception) ? (raise @result) : (return @result)
65
+ else
66
+ @join_wait_queue << watcher
67
+ end
68
+ end
69
+ timeout ? move_on_after(timeout) { watcher.await } : watcher.await
70
+ end
71
+ alias_method :await, :join
72
+
73
+ alias_method :orig_raise, :raise
74
+ def raise(error = nil)
75
+ Thread.pass until @main_fiber
76
+ error = RuntimeError.new if error.nil?
77
+ error = RuntimeError.new(error) if error.is_a?(String)
78
+ error = error.new if error.is_a?(Class)
79
+
80
+ sleep 0.0001 until @ready
81
+ main_fiber&.raise(error)
82
+ end
83
+
84
+ alias_method :orig_kill, :kill
85
+ def kill
86
+ return if @terminated
87
+
88
+ raise Polyphony::Terminate
89
+ end
90
+
91
+ alias_method :orig_inspect, :inspect
92
+ def inspect
93
+ return orig_inspect if self == Thread.main
94
+
95
+ state = status || 'dead'
96
+ "#<Thread:#{object_id} #{location} (#{state})>"
97
+ end
98
+ alias_method :to_s, :inspect
99
+
100
+ def location
101
+ @block.source_location.join(':')
102
+ end
103
+
104
+ def <<(value)
105
+ main_fiber << value
106
+ end
107
+ alias_method :send, :<<
108
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './extensions/socket'
4
+ require_relative './extensions/openssl'
5
+
6
+ module Polyphony
7
+ # A more elegant networking API
8
+ module Net
9
+ class << self
10
+ def tcp_connect(host, port, opts = {})
11
+ socket = ::Socket.new(:INET, :STREAM).tap do |s|
12
+ addr = ::Socket.sockaddr_in(port, host)
13
+ s.connect(addr)
14
+ end
15
+ if opts[:secure_context] || opts[:secure]
16
+ secure_socket(socket, opts[:secure_context], opts.merge(host: host))
17
+ else
18
+ socket
19
+ end
20
+ end
21
+
22
+ def tcp_listen(host = nil, port = nil, opts = {})
23
+ host ||= '0.0.0.0'
24
+ raise 'Port number not specified' unless port
25
+
26
+ socket = socket_from_options(host, port, opts)
27
+ if opts[:secure_context] || opts[:secure]
28
+ secure_server(socket, opts[:secure_context], opts)
29
+ else
30
+ socket
31
+ end
32
+ end
33
+
34
+ def socket_from_options(host, port, opts)
35
+ ::Socket.new(:INET, :STREAM).tap do |s|
36
+ s.reuse_addr if opts[:reuse_addr]
37
+ s.dont_linger if opts[:dont_linger]
38
+ addr = ::Socket.sockaddr_in(port, host)
39
+ s.bind(addr)
40
+ s.listen(0)
41
+ end
42
+ end
43
+
44
+ def secure_socket(socket, context, opts)
45
+ context ||= OpenSSL::SSL::SSLContext.new
46
+ setup_alpn(context, opts[:alpn_protocols]) if opts[:alpn_protocols]
47
+ socket = secure_socket_wrapper(socket, context)
48
+
49
+ socket.tap do |s|
50
+ s.hostname = opts[:host] if opts[:host]
51
+ s.connect
52
+ s.post_connection_check(opts[:host]) if opts[:host]
53
+ end
54
+ end
55
+
56
+ def secure_socket_wrapper(socket, context)
57
+ if context
58
+ OpenSSL::SSL::SSLSocket.new(socket, context)
59
+ else
60
+ OpenSSL::SSL::SSLSocket.new(socket)
61
+ end
62
+ end
63
+
64
+ def secure_server(socket, context, opts)
65
+ setup_alpn(context, opts[:alpn_protocols]) if opts[:alpn_protocols]
66
+ OpenSSL::SSL::SSLServer.new(socket, context)
67
+ end
68
+
69
+ def setup_alpn(context, protocols)
70
+ context.alpn_protocols = protocols
71
+ context.alpn_select_cb = lambda do |peer_protocols|
72
+ (protocols & peer_protocols).first
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Polyphony
4
+ VERSION = '0.43.8'
5
+ end
@@ -0,0 +1,40 @@
1
+ require_relative './lib/polyphony/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'polyphony'
5
+ s.version = Polyphony::VERSION
6
+ s.licenses = ['MIT']
7
+ s.summary = 'Fine grained concurrency for Ruby'
8
+ s.author = 'Sharon Rosner'
9
+ s.email = 'ciconia@gmail.com'
10
+ s.files = `git ls-files`.split
11
+ s.homepage = 'https://digital-fabric.github.io/polyphony'
12
+ s.metadata = {
13
+ "source_code_uri" => "https://github.com/digital-fabric/polyphony",
14
+ "documentation_uri" => "https://digital-fabric.github.io/polyphony/",
15
+ "homepage_uri" => "https://digital-fabric.github.io/polyphony/",
16
+ "changelog_uri" => "https://github.com/digital-fabric/polyphony/blob/master/CHANGELOG.md"
17
+ }
18
+ s.rdoc_options = ["--title", "polyphony", "--main", "README.md"]
19
+ s.extra_rdoc_files = ["README.md"]
20
+ s.extensions = ["ext/polyphony/extconf.rb"]
21
+ s.require_paths = ["lib"]
22
+ s.required_ruby_version = '>= 2.6'
23
+
24
+ s.add_development_dependency 'httparty', '0.17.0'
25
+ s.add_development_dependency 'localhost', '1.1.4'
26
+ s.add_development_dependency 'minitest', '5.13.0'
27
+ s.add_development_dependency 'minitest-reporters', '1.4.2'
28
+ s.add_development_dependency 'simplecov', '0.17.1'
29
+ s.add_development_dependency 'rubocop', '0.85.1'
30
+ s.add_development_dependency 'pg', '1.1.4'
31
+ s.add_development_dependency 'rake-compiler', '1.0.5'
32
+ s.add_development_dependency 'redis', '4.1.0'
33
+ s.add_development_dependency 'hiredis', '0.6.3'
34
+ s.add_development_dependency 'http_parser.rb', '~>0.6.0'
35
+
36
+ s.add_development_dependency 'jekyll', '~>3.8.6'
37
+ s.add_development_dependency 'jekyll-remote-theme', '~>0.4.1'
38
+ s.add_development_dependency 'jekyll-seo-tag', '~>2.6.1'
39
+ s.add_development_dependency 'just-the-docs', '~>0.3.0'
40
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'coverage'
4
+ require 'simplecov'
5
+
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.
11
+ module Coverage
12
+ EXCLUDE = %w{coverage eg helper run
13
+ }.map { |n| File.expand_path("test/#{n}.rb") }
14
+
15
+ LIB_FILES = Dir["#{File.join(FileUtils.pwd, 'lib')}/polyphony/**/*.rb"]
16
+
17
+ class << self
18
+ def relevant_lines_for_filename(filename)
19
+ @classifier ||= SimpleCov::LinesClassifier.new
20
+ @classifier.classify(IO.read(filename).lines)
21
+ end
22
+
23
+ def start
24
+ @result = {}
25
+ trace = TracePoint.new(:line) do |tp|
26
+ next if tp.path =~ /\(/
27
+
28
+ absolute = File.expand_path(tp.path)
29
+ next unless LIB_FILES.include?(absolute)# =~ /^#{LIB_DIR}/
30
+
31
+ @result[absolute] ||= relevant_lines_for_filename(absolute)
32
+ @result[absolute][tp.lineno - 1] = 1
33
+ end
34
+ trace.enable
35
+ end
36
+
37
+ def result
38
+ @result
39
+ end
40
+ end
41
+ end
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
+
54
+ SimpleCov.start
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kernel
4
+ RE_CONST = /^[A-Z]/.freeze
5
+ RE_ATTR = /^@(.+)$/.freeze
6
+
7
+ def eg(hash)
8
+ Module.new.tap do |m|
9
+ s = m.singleton_class
10
+ hash.each do |k, v|
11
+ case k
12
+ when RE_CONST
13
+ m.const_set(k, v)
14
+ when RE_ATTR
15
+ m.instance_variable_set(k, v)
16
+ else
17
+ block = if v.respond_to?(:to_proc)
18
+ proc { |*args| instance_exec(*args, &v) }
19
+ else
20
+ proc { v }
21
+ end
22
+ s.define_method(k, &block)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ require_relative './coverage' if ENV['COVERAGE']
6
+
7
+ require 'polyphony'
8
+
9
+ require 'fileutils'
10
+ require_relative './eg'
11
+
12
+ require 'minitest/autorun'
13
+ require 'minitest/reporters'
14
+
15
+ ::Exception.__disable_sanitized_backtrace__ = true
16
+
17
+ Minitest::Reporters.use! [
18
+ Minitest::Reporters::SpecReporter.new
19
+ ]
20
+
21
+ class ::Fiber
22
+ attr_writer :auto_watcher
23
+ end
24
+
25
+ class MiniTest::Test
26
+ def setup
27
+ # puts "* setup #{self.name}"
28
+ if Fiber.current.children.size > 0
29
+ puts "Children left: #{Fiber.current.children.inspect}"
30
+ exit!
31
+ end
32
+ Fiber.current.setup_main_fiber
33
+ Fiber.current.instance_variable_set(:@auto_watcher, nil)
34
+ Thread.current.agent = Polyphony::LibevAgent.new
35
+ sleep 0 # apparently this helps with timer accuracy
36
+ end
37
+
38
+ def teardown
39
+ # puts "* teardown #{self.name.inspect} Fiber.current: #{Fiber.current.inspect}"
40
+ Fiber.current.terminate_all_children
41
+ Fiber.current.await_all_children
42
+ Fiber.current.auto_watcher = nil
43
+ rescue => e
44
+ puts e
45
+ puts e.backtrace.join("\n")
46
+ exit!
47
+ end
48
+ end
49
+
50
+ module Kernel
51
+ def capture_exception
52
+ yield
53
+ rescue Exception => e
54
+ e
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'fiber'
5
+ require_relative '../lib/polyphony_ext'
6
+
7
+ queue = Polyphony::LibevQueue.new
8
+
9
+ queue.push :a
10
+ queue.push :b
11
+ queue.push :c
12
+ p [queue.shift_no_wait]
13
+ queue.push :d
14
+ p [queue.shift_no_wait]
15
+ p [queue.shift_no_wait]
16
+ p [queue.shift_no_wait]
17
+ p [queue.shift_no_wait]
18
+
19
+ queue.unshift :e
20
+ p [queue.shift_no_wait]
21
+
22
+ queue.push :f
23
+ queue.push :g
24
+ p [queue.shift_no_wait]
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir.glob("#{__dir__}/test_*.rb").each do |path|
4
+ require(path)
5
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ count = ARGV[0] ? ARGV[0].to_i : 100
4
+
5
+ TEST_CMD = 'ruby test/run.rb'
6
+
7
+ def run_test(count)
8
+ puts "#{count}: running tests..."
9
+ system(TEST_CMD)
10
+ return if $?.exitstatus == 0
11
+
12
+ puts "Failure after #{count} tests"
13
+ exit!
14
+ end
15
+
16
+ trap('INT') { exit! }
17
+ t0 = Time.now
18
+ count.times { |i| run_test(i + 1) }
19
+ elapsed = Time.now - t0
20
+ puts format(
21
+ "Successfully ran %d tests in %f seconds (%f per test)",
22
+ count,
23
+ elapsed,
24
+ elapsed / count
25
+ )
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ class AgentTest < MiniTest::Test
6
+ def setup
7
+ super
8
+ @prev_agent = Thread.current.agent
9
+ @agent = Polyphony::LibevAgent.new
10
+ Thread.current.agent = @agent
11
+ end
12
+
13
+ def teardown
14
+ @agent.finalize
15
+ Thread.current.agent = @prev_agent
16
+ end
17
+
18
+ def test_sleep
19
+ count = 0
20
+ t0 = Time.now
21
+ spin {
22
+ @agent.sleep 0.01
23
+ count += 1
24
+ @agent.sleep 0.01
25
+ count += 1
26
+ @agent.sleep 0.01
27
+ count += 1
28
+ }.await
29
+ assert Time.now - t0 >= 0.03
30
+ assert_equal 3, count
31
+ end
32
+
33
+ def test_write_read_partial
34
+ i, o = IO.pipe
35
+ buf = +''
36
+ f = spin { @agent.read(i, buf, 5, false) }
37
+ @agent.write(o, 'Hello world')
38
+ return_value = f.await
39
+
40
+ assert_equal 'Hello', buf
41
+ assert_equal return_value, buf
42
+ end
43
+
44
+ def test_write_read_to_eof_limited_buffer
45
+ i, o = IO.pipe
46
+ buf = +''
47
+ f = spin { @agent.read(i, buf, 5, true) }
48
+ @agent.write(o, 'Hello')
49
+ snooze
50
+ @agent.write(o, ' world')
51
+ snooze
52
+ o.close
53
+ return_value = f.await
54
+
55
+ assert_equal 'Hello', buf
56
+ assert_equal return_value, buf
57
+ end
58
+
59
+ def test_write_read_to_eof
60
+ i, o = IO.pipe
61
+ buf = +''
62
+ f = spin { @agent.read(i, buf, 10**6, true) }
63
+ @agent.write(o, 'Hello')
64
+ snooze
65
+ @agent.write(o, ' world')
66
+ snooze
67
+ o.close
68
+ return_value = f.await
69
+
70
+ assert_equal 'Hello world', buf
71
+ assert_equal return_value, buf
72
+ end
73
+
74
+ def test_waitpid
75
+ pid = fork do
76
+ @agent.post_fork
77
+ exit(42)
78
+ end
79
+
80
+ result = @agent.waitpid(pid)
81
+ assert_equal [pid, 42], result
82
+ end
83
+
84
+ def test_read_loop
85
+ i, o = IO.pipe
86
+
87
+ buf = []
88
+ spin do
89
+ buf << :ready
90
+ @agent.read_loop(i) { |d| buf << d }
91
+ buf << :done
92
+ end
93
+
94
+ # writing always causes snoozing
95
+ o << 'foo'
96
+ o << 'bar'
97
+ o.close
98
+
99
+ # read_loop will snooze after every read
100
+ 6.times { snooze }
101
+
102
+ assert_equal [:ready, 'foo', 'bar', :done], buf
103
+ end
104
+
105
+ def test_accept_loop
106
+ server = TCPServer.new('127.0.0.1', 1234)
107
+
108
+ clients = []
109
+ server_fiber = spin do
110
+ @agent.accept_loop(server) { |c| clients << c }
111
+ end
112
+
113
+ c1 = TCPSocket.new('127.0.0.1', 1234)
114
+ 10.times { snooze }
115
+
116
+ assert_equal 1, clients.size
117
+
118
+ c2 = TCPSocket.new('127.0.0.1', 1234)
119
+ 10.times { snooze }
120
+
121
+ assert_equal 2, clients.size
122
+
123
+ ensure
124
+ c1&.close
125
+ c2&.close
126
+ server_fiber.stop
127
+ snooze
128
+ server&.close
129
+ end
130
+ end