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.
- checksums.yaml +7 -0
- data/.gitbook.yaml +4 -0
- data/.github/workflows/test.yml +29 -0
- data/.gitignore +59 -0
- data/.rubocop.yml +175 -0
- data/CHANGELOG.md +393 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +141 -0
- data/LICENSE +21 -0
- data/README.md +51 -0
- data/Rakefile +26 -0
- data/TODO.md +201 -0
- data/bin/polyphony-debug +87 -0
- data/docs/_config.yml +64 -0
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/title.html +1 -0
- data/docs/_sass/custom/custom.scss +10 -0
- data/docs/_sass/overrides.scss +0 -0
- data/docs/_user-guide/all-about-timers.md +126 -0
- data/docs/_user-guide/index.md +9 -0
- data/docs/_user-guide/web-server.md +136 -0
- data/docs/api-reference/exception.md +27 -0
- data/docs/api-reference/fiber.md +425 -0
- data/docs/api-reference/index.md +9 -0
- data/docs/api-reference/io.md +36 -0
- data/docs/api-reference/object.md +99 -0
- data/docs/api-reference/polyphony-baseexception.md +33 -0
- data/docs/api-reference/polyphony-cancel.md +26 -0
- data/docs/api-reference/polyphony-moveon.md +24 -0
- data/docs/api-reference/polyphony-net.md +20 -0
- data/docs/api-reference/polyphony-process.md +28 -0
- data/docs/api-reference/polyphony-resourcepool.md +59 -0
- data/docs/api-reference/polyphony-restart.md +18 -0
- data/docs/api-reference/polyphony-terminate.md +18 -0
- data/docs/api-reference/polyphony-threadpool.md +67 -0
- data/docs/api-reference/polyphony-throttler.md +77 -0
- data/docs/api-reference/polyphony.md +36 -0
- data/docs/api-reference/thread.md +88 -0
- data/docs/assets/img/echo-fibers.svg +1 -0
- data/docs/assets/img/sleeping-fiber.svg +1 -0
- data/docs/faq.md +195 -0
- data/docs/favicon.ico +0 -0
- data/docs/getting-started/index.md +10 -0
- data/docs/getting-started/installing.md +34 -0
- data/docs/getting-started/overview.md +486 -0
- data/docs/getting-started/tutorial.md +359 -0
- data/docs/index.md +94 -0
- data/docs/main-concepts/concurrency.md +151 -0
- data/docs/main-concepts/design-principles.md +161 -0
- data/docs/main-concepts/exception-handling.md +291 -0
- data/docs/main-concepts/extending.md +89 -0
- data/docs/main-concepts/fiber-scheduling.md +197 -0
- data/docs/main-concepts/index.md +9 -0
- data/docs/polyphony-logo.png +0 -0
- data/examples/adapters/concurrent-ruby.rb +9 -0
- data/examples/adapters/pg_client.rb +36 -0
- data/examples/adapters/pg_notify.rb +35 -0
- data/examples/adapters/pg_pool.rb +43 -0
- data/examples/adapters/pg_transaction.rb +31 -0
- data/examples/adapters/redis_blpop.rb +12 -0
- data/examples/adapters/redis_channels.rb +122 -0
- data/examples/adapters/redis_client.rb +19 -0
- data/examples/adapters/redis_pubsub.rb +26 -0
- data/examples/adapters/redis_pubsub_perf.rb +68 -0
- data/examples/core/01-spinning-up-fibers.rb +18 -0
- data/examples/core/02-awaiting-fibers.rb +20 -0
- data/examples/core/03-interrupting.rb +39 -0
- data/examples/core/04-handling-signals.rb +19 -0
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-at_exit.rb +29 -0
- data/examples/core/xx-caller.rb +12 -0
- data/examples/core/xx-channels.rb +45 -0
- data/examples/core/xx-daemon.rb +14 -0
- data/examples/core/xx-deadlock.rb +8 -0
- data/examples/core/xx-deferring-an-operation.rb +14 -0
- data/examples/core/xx-erlang-style-genserver.rb +81 -0
- data/examples/core/xx-exception-backtrace.rb +40 -0
- data/examples/core/xx-fork-cleanup.rb +22 -0
- data/examples/core/xx-fork-spin.rb +42 -0
- data/examples/core/xx-fork-terminate.rb +27 -0
- data/examples/core/xx-forking.rb +24 -0
- data/examples/core/xx-move_on.rb +23 -0
- data/examples/core/xx-pingpong.rb +18 -0
- data/examples/core/xx-queue-async.rb +120 -0
- data/examples/core/xx-readpartial.rb +18 -0
- data/examples/core/xx-recurrent-timer.rb +12 -0
- data/examples/core/xx-resource_delegate.rb +31 -0
- data/examples/core/xx-signals.rb +16 -0
- data/examples/core/xx-sleep-forever.rb +9 -0
- data/examples/core/xx-sleeping.rb +25 -0
- data/examples/core/xx-snooze-starve.rb +16 -0
- data/examples/core/xx-spin-fork.rb +49 -0
- data/examples/core/xx-spin_error_backtrace.rb +33 -0
- data/examples/core/xx-state-machine.rb +51 -0
- data/examples/core/xx-stop.rb +20 -0
- data/examples/core/xx-supervise-process.rb +30 -0
- data/examples/core/xx-supervisors.rb +21 -0
- data/examples/core/xx-thread-selector-sleep.rb +51 -0
- data/examples/core/xx-thread-selector-snooze.rb +46 -0
- data/examples/core/xx-thread-sleep.rb +17 -0
- data/examples/core/xx-thread-snooze.rb +34 -0
- data/examples/core/xx-thread_pool.rb +17 -0
- data/examples/core/xx-throttling.rb +18 -0
- data/examples/core/xx-timeout.rb +10 -0
- data/examples/core/xx-timer-gc.rb +17 -0
- data/examples/core/xx-trace.rb +79 -0
- data/examples/core/xx-using-a-mutex.rb +21 -0
- data/examples/core/xx-worker-thread.rb +30 -0
- data/examples/io/tunnel.rb +48 -0
- data/examples/io/xx-backticks.rb +11 -0
- data/examples/io/xx-echo_client.rb +25 -0
- data/examples/io/xx-echo_client_from_stdin.rb +21 -0
- data/examples/io/xx-echo_pipe.rb +16 -0
- data/examples/io/xx-echo_server.rb +17 -0
- data/examples/io/xx-echo_server_with_timeout.rb +34 -0
- data/examples/io/xx-echo_stdin.rb +14 -0
- data/examples/io/xx-happy-eyeballs.rb +36 -0
- data/examples/io/xx-httparty.rb +38 -0
- data/examples/io/xx-irb.rb +17 -0
- data/examples/io/xx-net-http.rb +15 -0
- data/examples/io/xx-open.rb +16 -0
- data/examples/io/xx-switch.rb +15 -0
- data/examples/io/xx-system.rb +11 -0
- data/examples/io/xx-tcpserver.rb +15 -0
- data/examples/io/xx-tcpsocket.rb +18 -0
- data/examples/io/xx-zip.rb +19 -0
- data/examples/performance/fiber_transfer.rb +47 -0
- data/examples/performance/fs_read.rb +38 -0
- data/examples/performance/mem-usage.rb +56 -0
- data/examples/performance/messaging.rb +29 -0
- data/examples/performance/multi_snooze.rb +33 -0
- data/examples/performance/snooze.rb +39 -0
- data/examples/performance/snooze_raw.rb +39 -0
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +74 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +45 -0
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
- data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_multi.rb +36 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_threaded.rb +29 -0
- data/examples/performance/thread_pool_perf.rb +63 -0
- data/examples/performance/xx-array.rb +11 -0
- data/examples/performance/xx-fiber-switch.rb +9 -0
- data/examples/performance/xx-snooze.rb +15 -0
- data/examples/xx-spin.rb +32 -0
- data/ext/libev/Changes +548 -0
- data/ext/libev/LICENSE +37 -0
- data/ext/libev/README +59 -0
- data/ext/libev/README.embed +3 -0
- data/ext/libev/ev.c +5279 -0
- data/ext/libev/ev.h +856 -0
- data/ext/libev/ev_epoll.c +296 -0
- data/ext/libev/ev_kqueue.c +224 -0
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +156 -0
- data/ext/libev/ev_port.c +192 -0
- data/ext/libev/ev_select.c +316 -0
- data/ext/libev/ev_vars.h +215 -0
- data/ext/libev/ev_win32.c +162 -0
- data/ext/libev/ev_wrap.h +216 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/ext/polyphony/extconf.rb +20 -0
- data/ext/polyphony/fiber.c +109 -0
- data/ext/polyphony/libev.c +2 -0
- data/ext/polyphony/libev.h +9 -0
- data/ext/polyphony/libev_agent.c +882 -0
- data/ext/polyphony/polyphony.c +71 -0
- data/ext/polyphony/polyphony.h +97 -0
- data/ext/polyphony/polyphony_ext.c +21 -0
- data/ext/polyphony/queue.c +168 -0
- data/ext/polyphony/ring_buffer.c +96 -0
- data/ext/polyphony/ring_buffer.h +28 -0
- data/ext/polyphony/thread.c +208 -0
- data/ext/polyphony/tracing.c +11 -0
- data/lib/polyphony.rb +136 -0
- data/lib/polyphony/adapters/fs.rb +19 -0
- data/lib/polyphony/adapters/irb.rb +52 -0
- data/lib/polyphony/adapters/postgres.rb +110 -0
- data/lib/polyphony/adapters/process.rb +33 -0
- data/lib/polyphony/adapters/redis.rb +67 -0
- data/lib/polyphony/adapters/trace.rb +138 -0
- data/lib/polyphony/core/channel.rb +46 -0
- data/lib/polyphony/core/exceptions.rb +36 -0
- data/lib/polyphony/core/global_api.rb +124 -0
- data/lib/polyphony/core/resource_pool.rb +117 -0
- data/lib/polyphony/core/sync.rb +21 -0
- data/lib/polyphony/core/thread_pool.rb +64 -0
- data/lib/polyphony/core/throttler.rb +41 -0
- data/lib/polyphony/event.rb +17 -0
- data/lib/polyphony/extensions/core.rb +174 -0
- data/lib/polyphony/extensions/fiber.rb +379 -0
- data/lib/polyphony/extensions/io.rb +221 -0
- data/lib/polyphony/extensions/openssl.rb +81 -0
- data/lib/polyphony/extensions/socket.rb +150 -0
- data/lib/polyphony/extensions/thread.rb +108 -0
- data/lib/polyphony/net.rb +77 -0
- data/lib/polyphony/version.rb +5 -0
- data/polyphony.gemspec +40 -0
- data/test/coverage.rb +54 -0
- data/test/eg.rb +27 -0
- data/test/helper.rb +56 -0
- data/test/q.rb +24 -0
- data/test/run.rb +5 -0
- data/test/stress.rb +25 -0
- data/test/test_agent.rb +130 -0
- data/test/test_event.rb +59 -0
- data/test/test_ext.rb +196 -0
- data/test/test_fiber.rb +988 -0
- data/test/test_global_api.rb +352 -0
- data/test/test_io.rb +249 -0
- data/test/test_kernel.rb +57 -0
- data/test/test_process_supervision.rb +46 -0
- data/test/test_queue.rb +112 -0
- data/test/test_resource_pool.rb +138 -0
- data/test/test_signal.rb +100 -0
- data/test/test_socket.rb +34 -0
- data/test/test_supervise.rb +103 -0
- data/test/test_thread.rb +170 -0
- data/test/test_thread_pool.rb +101 -0
- data/test/test_throttler.rb +50 -0
- data/test/test_trace.rb +68 -0
- 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
|
data/polyphony.gemspec
ADDED
@@ -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
|
data/test/coverage.rb
ADDED
@@ -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
|
data/test/eg.rb
ADDED
@@ -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
|
data/test/helper.rb
ADDED
@@ -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
|
data/test/q.rb
ADDED
@@ -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]
|
data/test/run.rb
ADDED
data/test/stress.rb
ADDED
@@ -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
|
+
)
|
data/test/test_agent.rb
ADDED
@@ -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
|