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,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../polyphony'
|
4
|
+
require 'pg'
|
5
|
+
|
6
|
+
# PG overrides
|
7
|
+
module ::PG
|
8
|
+
def self.connect(*args)
|
9
|
+
Connection.connect_start(*args).tap(&method(:connect_async))
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.connect_async(conn)
|
13
|
+
socket_io = conn.socket_io
|
14
|
+
loop do
|
15
|
+
res = conn.connect_poll
|
16
|
+
case res
|
17
|
+
when PGRES_POLLING_FAILED then raise Error, conn.error_message
|
18
|
+
when PGRES_POLLING_READING then Thread.current.agent.wait_io(socket_io, false)
|
19
|
+
when PGRES_POLLING_WRITING then Thread.current.agent.wait_io(socket_io, true)
|
20
|
+
when PGRES_POLLING_OK then return conn.setnonblocking(true)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.connect_sync(conn)
|
26
|
+
loop do
|
27
|
+
res = conn.connect_poll
|
28
|
+
case res
|
29
|
+
when PGRES_POLLING_FAILED
|
30
|
+
raise Error, conn.error_message
|
31
|
+
when PGRES_POLLING_OK
|
32
|
+
conn.setnonblocking(true)
|
33
|
+
return
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Overrides for PG connection
|
40
|
+
class ::PG::Connection
|
41
|
+
alias_method :orig_get_result, :get_result
|
42
|
+
|
43
|
+
def get_result(&block)
|
44
|
+
while is_busy
|
45
|
+
Thread.current.agent.wait_io(socket_io, false)
|
46
|
+
consume_input
|
47
|
+
end
|
48
|
+
orig_get_result(&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
alias_method :orig_async_exec, :async_exec
|
52
|
+
def async_exec(*args, &block)
|
53
|
+
send_query(*args)
|
54
|
+
get_result(&block)
|
55
|
+
ensure
|
56
|
+
# cleanup result in order to allow next query
|
57
|
+
while get_result; end
|
58
|
+
end
|
59
|
+
|
60
|
+
def block(_timeout = 0)
|
61
|
+
while is_busy
|
62
|
+
Thread.current.agent.wait_io(socket_io, false)
|
63
|
+
consume_input
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
SQL_BEGIN = 'begin'
|
68
|
+
SQL_COMMIT = 'commit'
|
69
|
+
SQL_ROLLBACK = 'rollback'
|
70
|
+
|
71
|
+
# Starts a transaction, runs given block, and commits transaction. If an
|
72
|
+
# error is raised, the transaction is rolled back and the error is raised
|
73
|
+
# again.
|
74
|
+
# @return [void]
|
75
|
+
def transaction(&block)
|
76
|
+
return yield if @transaction # allow nesting of calls to #transactions
|
77
|
+
|
78
|
+
perform_transaction(&block)
|
79
|
+
end
|
80
|
+
|
81
|
+
def perform_transaction
|
82
|
+
query(SQL_BEGIN)
|
83
|
+
began = true
|
84
|
+
@transaction = true
|
85
|
+
yield
|
86
|
+
query(SQL_COMMIT)
|
87
|
+
rescue StandardError => e
|
88
|
+
query(SQL_ROLLBACK) if began
|
89
|
+
raise e
|
90
|
+
ensure
|
91
|
+
@transaction = false
|
92
|
+
end
|
93
|
+
|
94
|
+
self.async_api = true
|
95
|
+
|
96
|
+
def wait_for_notify(timeout = nil, &block)
|
97
|
+
return move_on_after(timeout) { wait_for_notify(&block) } if timeout
|
98
|
+
|
99
|
+
loop do
|
100
|
+
Thread.current.agent.wait_io(socket_io, false)
|
101
|
+
consume_input
|
102
|
+
notice = notifies
|
103
|
+
next unless notice
|
104
|
+
|
105
|
+
values = notice.values
|
106
|
+
block&.(*values)
|
107
|
+
return values.first
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polyphony
|
4
|
+
# Process patches
|
5
|
+
module Process
|
6
|
+
class << self
|
7
|
+
def watch(cmd = nil, &block)
|
8
|
+
terminated = nil
|
9
|
+
pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
|
10
|
+
Thread.current.agent.waitpid(pid)
|
11
|
+
terminated = true
|
12
|
+
ensure
|
13
|
+
kill_process(pid) unless terminated || pid.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def kill_process(pid)
|
17
|
+
cancel_after(5) do
|
18
|
+
kill_and_await('TERM', pid)
|
19
|
+
end
|
20
|
+
rescue Polyphony::Cancel
|
21
|
+
kill_and_await(-9, pid)
|
22
|
+
end
|
23
|
+
|
24
|
+
def kill_and_await(sig, pid)
|
25
|
+
::Process.kill(sig, pid)
|
26
|
+
Thread.current.agent.waitpid(pid)
|
27
|
+
rescue SystemCallError
|
28
|
+
# ignore
|
29
|
+
puts 'SystemCallError in kill_and_await'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../polyphony'
|
4
|
+
|
5
|
+
require 'redis'
|
6
|
+
require 'hiredis/reader'
|
7
|
+
|
8
|
+
# Polyphony-based Redis driver
|
9
|
+
class Polyphony::RedisDriver
|
10
|
+
def self.connect(config)
|
11
|
+
raise 'unix sockets not supported' if config[:scheme] == 'unix'
|
12
|
+
|
13
|
+
# connection.connect_unix(config[:path], connect_timeout)
|
14
|
+
|
15
|
+
raise 'ssl not supported' if config[:scheme] == 'rediss' || config[:ssl]
|
16
|
+
|
17
|
+
# raise NotImplementedError, "SSL not supported by hiredis driver"
|
18
|
+
|
19
|
+
new(config[:host], config[:port])
|
20
|
+
# connection.connect(config[:host], config[:port], connect_timeout)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(host, port)
|
24
|
+
@connection = Polyphony::Net.tcp_connect(host, port)
|
25
|
+
@reader = ::Hiredis::Reader.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def connected?
|
29
|
+
@connection && !@connection.closed?
|
30
|
+
end
|
31
|
+
|
32
|
+
def timeout=(timeout)
|
33
|
+
# ignore timeout for now
|
34
|
+
end
|
35
|
+
|
36
|
+
def disconnect
|
37
|
+
@connection.close
|
38
|
+
@connection = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def write(command)
|
42
|
+
@connection.write(format_command(command))
|
43
|
+
end
|
44
|
+
|
45
|
+
def format_command(args)
|
46
|
+
args = args.flatten
|
47
|
+
(+"*#{args.size}\r\n").tap do |s|
|
48
|
+
args.each do |a|
|
49
|
+
a = a.to_s
|
50
|
+
s << "$#{a.bytesize}\r\n#{a}\r\n"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def read
|
56
|
+
reply = @reader.gets
|
57
|
+
return reply if reply
|
58
|
+
|
59
|
+
while (data = @connection.readpartial(8192))
|
60
|
+
@reader.feed(data)
|
61
|
+
reply = @reader.gets
|
62
|
+
return reply unless reply == false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
Redis::Connection.drivers << Polyphony::RedisDriver
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../polyphony'
|
4
|
+
|
5
|
+
STOCK_EVENTS = %i[line call return c_call c_return b_call b_return].freeze
|
6
|
+
|
7
|
+
module Polyphony
|
8
|
+
# Tracing functionality for Polyphony
|
9
|
+
module Trace
|
10
|
+
class << self
|
11
|
+
def new(*events)
|
12
|
+
start_stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
13
|
+
events = STOCK_EVENTS if events.empty?
|
14
|
+
::TracePoint.new(*events) { |tp| yield trace_record(tp, start_stamp) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def trace_record(trp, start_stamp)
|
18
|
+
stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_stamp
|
19
|
+
|
20
|
+
{ stamp: stamp, event: trp.event, location: "#{trp.path}:#{trp.lineno}",
|
21
|
+
self: trp.self, binding: trp.binding, fiber: tp_fiber(trp),
|
22
|
+
lineno: trp.lineno, method_id: trp.method_id,
|
23
|
+
path: trp.path, parameters: tp_params(trp),
|
24
|
+
return_value: tp_return_value(trp), schedule_value: tp_schedule_value(trp),
|
25
|
+
exception: tp_raised_exception(trp) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def tp_fiber(trp)
|
29
|
+
trp.is_a?(FiberTracePoint) ? trp.fiber : Fiber.current
|
30
|
+
end
|
31
|
+
|
32
|
+
PARAMS_EVENTS = %i[call c_call b_call].freeze
|
33
|
+
|
34
|
+
def tp_params(trp)
|
35
|
+
PARAMS_EVENTS.include?(trp.event) ? trp.parameters : nil
|
36
|
+
end
|
37
|
+
|
38
|
+
RETURN_VALUE_EVENTS = %i[return c_return b_return].freeze
|
39
|
+
|
40
|
+
def tp_return_value(trp)
|
41
|
+
RETURN_VALUE_EVENTS.include?(trp.event) ? trp.return_value : nil
|
42
|
+
end
|
43
|
+
|
44
|
+
SCHEDULE_VALUE_EVENTS = %i[fiber_schedule fiber_run].freeze
|
45
|
+
|
46
|
+
def tp_schedule_value(trp)
|
47
|
+
SCHEDULE_VALUE_EVENTS.include?(trp.event) ? trp.value : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def tp_raised_exception(trp)
|
51
|
+
trp.event == :raise && trp.raised_exception
|
52
|
+
end
|
53
|
+
|
54
|
+
def analyze(records)
|
55
|
+
by_fiber = Hash.new { |h, f| h[f] = [] }
|
56
|
+
records.each_with_object(by_fiber) { |r, h| h[r[:fiber]] << r }
|
57
|
+
{ by_fiber: by_fiber }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Implements fake TracePoint instances for fiber-related events
|
61
|
+
class FiberTracePoint
|
62
|
+
attr_reader :event, :fiber, :value
|
63
|
+
|
64
|
+
def initialize(tpoint)
|
65
|
+
@tp = tpoint
|
66
|
+
@event = tpoint.return_value[0]
|
67
|
+
@fiber = tpoint.return_value[1]
|
68
|
+
@value = tpoint.return_value[2]
|
69
|
+
end
|
70
|
+
|
71
|
+
def lineno
|
72
|
+
@tp.lineno
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_id
|
76
|
+
@tp.method_id
|
77
|
+
end
|
78
|
+
|
79
|
+
def path
|
80
|
+
@tp.path
|
81
|
+
end
|
82
|
+
|
83
|
+
def self
|
84
|
+
@tp.self
|
85
|
+
end
|
86
|
+
|
87
|
+
def binding
|
88
|
+
@tp.binding
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class << ::TracePoint
|
93
|
+
POLYPHONY_FILE_REGEXP = /^#{::Exception::POLYPHONY_DIR}/.freeze
|
94
|
+
|
95
|
+
alias_method :orig_new, :new
|
96
|
+
def new(*args, &block)
|
97
|
+
events_mask, fiber_events_mask = event_masks(args)
|
98
|
+
|
99
|
+
orig_new(*events_mask) do |tp|
|
100
|
+
handle_tp_event(tp, fiber_events_mask, &block)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def handle_tp_event(tpoint, fiber_events_mask)
|
105
|
+
# next unless !$watched_fiber || Fiber.current == $watched_fiber
|
106
|
+
|
107
|
+
if tpoint.method_id == :__fiber_trace__
|
108
|
+
return if tpoint.event != :c_return
|
109
|
+
return unless fiber_events_mask.include?(tpoint.return_value[0])
|
110
|
+
|
111
|
+
tpoint = FiberTracePoint.new(tpoint)
|
112
|
+
elsif tpoint.path =~ POLYPHONY_FILE_REGEXP
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
yield tpoint
|
117
|
+
end
|
118
|
+
|
119
|
+
ALL_FIBER_EVENTS = %i[
|
120
|
+
fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
|
121
|
+
fiber_ev_loop_enter fiber_ev_loop_leave
|
122
|
+
].freeze
|
123
|
+
|
124
|
+
def event_masks(events)
|
125
|
+
events.each_with_object([[], []]) do |e, masks|
|
126
|
+
case e
|
127
|
+
when /fiber_/
|
128
|
+
masks[1] += e == :fiber_all ? ALL_FIBER_EVENTS : [e]
|
129
|
+
masks[0] << :c_return unless masks[0].include?(:c_return)
|
130
|
+
else
|
131
|
+
masks[0] << e
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './exceptions'
|
4
|
+
|
5
|
+
module Polyphony
|
6
|
+
# Implements a unidirectional communication channel along the lines of Go
|
7
|
+
# (buffered) channels.
|
8
|
+
class Channel
|
9
|
+
def initialize
|
10
|
+
@payload_queue = []
|
11
|
+
@waiting_queue = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def close
|
15
|
+
stop = Polyphony::MoveOn.new
|
16
|
+
@waiting_queue.slice(0..-1).each { |f| f.schedule(stop) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def <<(value)
|
20
|
+
if @waiting_queue.empty?
|
21
|
+
@payload_queue << value
|
22
|
+
else
|
23
|
+
@waiting_queue.shift&.schedule(value)
|
24
|
+
end
|
25
|
+
snooze
|
26
|
+
end
|
27
|
+
|
28
|
+
def receive
|
29
|
+
Thread.current.agent.ref
|
30
|
+
if @payload_queue.empty?
|
31
|
+
@waiting_queue << Fiber.current
|
32
|
+
suspend
|
33
|
+
else
|
34
|
+
receive_from_queue
|
35
|
+
end
|
36
|
+
ensure
|
37
|
+
Thread.current.agent.unref
|
38
|
+
end
|
39
|
+
|
40
|
+
def receive_from_queue
|
41
|
+
payload = @payload_queue.shift
|
42
|
+
snooze
|
43
|
+
payload
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polyphony
|
4
|
+
# Common exception class for interrupting fibers. These exceptions allow
|
5
|
+
# control of fibers. BaseException exceptions can encapsulate a value and thus
|
6
|
+
# provide a way to interrupt long-running blocking operations while still
|
7
|
+
# passing a value back to the call site. BaseException exceptions can also
|
8
|
+
# references a cancel scope in order to allow correct bubbling of exceptions
|
9
|
+
# through nested cancel scopes.
|
10
|
+
class BaseException < ::Exception
|
11
|
+
attr_reader :value
|
12
|
+
|
13
|
+
def initialize(value = nil)
|
14
|
+
@caller_backtrace = caller
|
15
|
+
@value = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def backtrace
|
19
|
+
sanitize(@caller_backtrace)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# MoveOn is used to interrupt a long-running blocking operation, while
|
24
|
+
# continuing the rest of the computation.
|
25
|
+
class MoveOn < BaseException; end
|
26
|
+
|
27
|
+
# Cancel is used to interrupt a long-running blocking operation, bubbling the
|
28
|
+
# exception up through cancel scopes and supervisors.
|
29
|
+
class Cancel < BaseException; end
|
30
|
+
|
31
|
+
# Terminate is used to interrupt a fiber once its parent fiber has terminated.
|
32
|
+
class Terminate < BaseException; end
|
33
|
+
|
34
|
+
# Restart is used to restart a fiber
|
35
|
+
class Restart < BaseException; end
|
36
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../extensions/core'
|
4
|
+
require_relative '../extensions/fiber'
|
5
|
+
require_relative './exceptions'
|
6
|
+
require_relative './throttler'
|
7
|
+
|
8
|
+
module Polyphony
|
9
|
+
# Global API methods to be included in ::Object
|
10
|
+
module GlobalAPI
|
11
|
+
def after(interval, &block)
|
12
|
+
spin do
|
13
|
+
sleep interval
|
14
|
+
block.()
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def cancel_after(interval, with_exception: Polyphony::Cancel, &block)
|
19
|
+
fiber = ::Fiber.current
|
20
|
+
canceller = spin do
|
21
|
+
sleep interval
|
22
|
+
exception = with_exception.is_a?(Class) ?
|
23
|
+
with_exception.new : RuntimeError.new(with_exception)
|
24
|
+
fiber.schedule exception
|
25
|
+
end
|
26
|
+
block ? cancel_after_wrap_block(canceller, &block) : canceller
|
27
|
+
end
|
28
|
+
|
29
|
+
def cancel_after_wrap_block(canceller, &block)
|
30
|
+
block.call
|
31
|
+
ensure
|
32
|
+
canceller.stop
|
33
|
+
end
|
34
|
+
|
35
|
+
def spin(tag = nil, &block)
|
36
|
+
Fiber.current.spin(tag, caller, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def spin_loop(tag = nil, rate: nil, &block)
|
40
|
+
if rate
|
41
|
+
Fiber.current.spin(tag, caller) do
|
42
|
+
throttled_loop(rate, &block)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
Fiber.current.spin(tag, caller) { loop(&block) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def every(interval)
|
50
|
+
next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + interval
|
51
|
+
loop do
|
52
|
+
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
53
|
+
Thread.current.agent.sleep(next_time - now)
|
54
|
+
yield
|
55
|
+
loop do
|
56
|
+
next_time += interval
|
57
|
+
break if next_time > now
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def move_on_after(interval, with_value: nil, &block)
|
63
|
+
fiber = ::Fiber.current
|
64
|
+
unless block
|
65
|
+
return spin do
|
66
|
+
sleep interval
|
67
|
+
fiber.schedule with_value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
move_on_after_with_block(fiber, interval, with_value, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
def move_on_after_with_block(fiber, interval, with_value, &block)
|
75
|
+
canceller = spin do
|
76
|
+
sleep interval
|
77
|
+
fiber.schedule Polyphony::MoveOn.new(with_value)
|
78
|
+
end
|
79
|
+
block.call
|
80
|
+
rescue Polyphony::MoveOn => e
|
81
|
+
e.value
|
82
|
+
ensure
|
83
|
+
canceller.stop
|
84
|
+
end
|
85
|
+
|
86
|
+
def receive
|
87
|
+
Fiber.current.receive
|
88
|
+
end
|
89
|
+
|
90
|
+
def receive_pending
|
91
|
+
Fiber.current.receive_pending
|
92
|
+
end
|
93
|
+
|
94
|
+
def supervise(*args, &block)
|
95
|
+
Fiber.current.supervise(*args, &block)
|
96
|
+
end
|
97
|
+
|
98
|
+
def sleep(duration = nil)
|
99
|
+
return sleep_forever unless duration
|
100
|
+
|
101
|
+
Thread.current.agent.sleep duration
|
102
|
+
end
|
103
|
+
|
104
|
+
def sleep_forever
|
105
|
+
Thread.current.agent.ref
|
106
|
+
loop { sleep 60 }
|
107
|
+
ensure
|
108
|
+
Thread.current.agent.unref
|
109
|
+
end
|
110
|
+
|
111
|
+
def throttled_loop(rate, count: nil, &block)
|
112
|
+
throttler = Polyphony::Throttler.new(rate)
|
113
|
+
if count
|
114
|
+
count.times { |_i| throttler.(&block) }
|
115
|
+
else
|
116
|
+
loop { throttler.(&block) }
|
117
|
+
end
|
118
|
+
ensure
|
119
|
+
throttler&.stop
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
Object.include Polyphony::GlobalAPI
|