polyphony 0.43.10 → 0.45.2
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 +4 -4
- data/.rubocop.yml +8 -1
- data/CHANGELOG.md +37 -0
- data/Gemfile.lock +16 -6
- data/Rakefile +1 -1
- data/TODO.md +15 -10
- data/docs/_posts/2020-07-26-polyphony-0.44.md +77 -0
- data/docs/api-reference/thread.md +1 -1
- data/docs/getting-started/overview.md +14 -14
- data/docs/getting-started/tutorial.md +1 -1
- data/examples/adapters/redis_client.rb +3 -1
- data/examples/adapters/redis_pubsub_perf.rb +11 -8
- data/examples/adapters/sequel_mysql.rb +23 -0
- data/examples/adapters/sequel_mysql_pool.rb +33 -0
- data/examples/adapters/sequel_pg.rb +24 -0
- data/examples/core/{02-awaiting-fibers.rb → await.rb} +0 -0
- data/examples/core/{xx-channels.rb → channels.rb} +0 -0
- data/examples/core/deferring-an-operation.rb +16 -0
- data/examples/core/{xx-erlang-style-genserver.rb → erlang-style-genserver.rb} +16 -9
- data/examples/core/{xx-forking.rb → forking.rb} +1 -1
- data/examples/core/handling-signals.rb +11 -0
- data/examples/core/{03-interrupting.rb → interrupt.rb} +0 -0
- data/examples/core/{xx-pingpong.rb → pingpong.rb} +7 -5
- data/examples/core/{xx-recurrent-timer.rb → recurrent-timer.rb} +1 -1
- data/examples/core/{xx-resource_delegate.rb → resource_delegate.rb} +3 -4
- data/examples/core/{01-spinning-up-fibers.rb → spin.rb} +1 -1
- data/examples/core/{xx-spin_error_backtrace.rb → spin_error_backtrace.rb} +1 -1
- data/examples/core/{xx-supervise-process.rb → supervise-process.rb} +8 -5
- data/examples/core/supervisor.rb +20 -0
- data/examples/core/{xx-thread-sleep.rb → thread-sleep.rb} +0 -0
- data/examples/core/{xx-thread_pool.rb → thread_pool.rb} +0 -0
- data/examples/core/{xx-throttling.rb → throttling.rb} +0 -0
- data/examples/core/{xx-timeout.rb → timeout.rb} +0 -0
- data/examples/core/{xx-using-a-mutex.rb → using-a-mutex.rb} +0 -0
- data/examples/core/{xx-worker-thread.rb → worker-thread.rb} +2 -2
- data/examples/io/{xx-backticks.rb → backticks.rb} +0 -0
- data/examples/io/{xx-echo_client.rb → echo_client.rb} +1 -1
- data/examples/io/{xx-echo_client_from_stdin.rb → echo_client_from_stdin.rb} +2 -2
- data/examples/io/{xx-echo_pipe.rb → echo_pipe.rb} +1 -1
- data/examples/io/{xx-echo_server.rb → echo_server.rb} +0 -0
- data/examples/io/{xx-echo_server_with_timeout.rb → echo_server_with_timeout.rb} +1 -1
- data/examples/io/{xx-echo_stdin.rb → echo_stdin.rb} +0 -0
- data/examples/io/{xx-happy-eyeballs.rb → happy-eyeballs.rb} +0 -0
- data/examples/io/{xx-httparty.rb → httparty.rb} +4 -13
- data/examples/io/{xx-irb.rb → irb.rb} +0 -0
- data/examples/io/{xx-net-http.rb → net-http.rb} +0 -0
- data/examples/io/{xx-open.rb → open.rb} +0 -0
- data/examples/io/pry.rb +18 -0
- data/examples/io/rack_server.rb +71 -0
- data/examples/io/{xx-system.rb → system.rb} +1 -1
- data/examples/io/{xx-tcpserver.rb → tcpserver.rb} +0 -0
- data/examples/io/{xx-tcpsocket.rb → tcpsocket.rb} +0 -0
- data/examples/io/tunnel.rb +6 -1
- data/examples/io/{xx-zip.rb → zip.rb} +0 -0
- data/examples/performance/fiber_transfer.rb +2 -1
- data/examples/performance/fs_read.rb +5 -6
- data/examples/{io/xx-switch.rb → performance/switch.rb} +2 -1
- data/examples/performance/thread-vs-fiber/{xx-httparty_multi.rb → httparty_multi.rb} +3 -4
- data/examples/performance/thread-vs-fiber/{xx-httparty_threaded.rb → httparty_threaded.rb} +0 -0
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +1 -1
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -1
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +1 -1
- data/examples/performance/thread-vs-fiber/threaded_server.rb +1 -5
- data/examples/performance/thread_pool_perf.rb +6 -7
- data/ext/polyphony/backend.h +40 -0
- data/ext/polyphony/event.c +3 -3
- data/ext/polyphony/extconf.rb +1 -1
- data/ext/polyphony/fiber.c +66 -6
- data/ext/polyphony/{libev_agent.c → libev_backend.c} +239 -235
- data/ext/polyphony/polyphony.c +3 -3
- data/ext/polyphony/polyphony.h +15 -23
- data/ext/polyphony/polyphony_ext.c +3 -4
- data/ext/polyphony/queue.c +25 -12
- data/ext/polyphony/ring_buffer.c +0 -1
- data/ext/polyphony/thread.c +36 -33
- data/lib/polyphony.rb +25 -38
- data/lib/polyphony/adapters/fs.rb +1 -1
- data/lib/polyphony/adapters/irb.rb +2 -17
- data/lib/polyphony/adapters/mysql2.rb +19 -0
- data/lib/polyphony/adapters/postgres.rb +5 -5
- data/lib/polyphony/adapters/process.rb +2 -2
- data/lib/polyphony/adapters/readline.rb +17 -0
- data/lib/polyphony/adapters/redis.rb +1 -1
- data/lib/polyphony/adapters/sequel.rb +45 -0
- data/lib/polyphony/core/exceptions.rb +11 -0
- data/lib/polyphony/core/global_api.rb +17 -12
- data/lib/polyphony/core/resource_pool.rb +20 -7
- data/lib/polyphony/core/sync.rb +46 -8
- data/lib/polyphony/core/throttler.rb +1 -1
- data/lib/polyphony/extensions/core.rb +38 -25
- data/lib/polyphony/extensions/fiber.rb +12 -45
- data/lib/polyphony/extensions/io.rb +45 -12
- data/lib/polyphony/extensions/openssl.rb +6 -6
- data/lib/polyphony/extensions/socket.rb +22 -15
- data/lib/polyphony/extensions/thread.rb +6 -5
- data/lib/polyphony/net.rb +2 -1
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +7 -3
- data/test/helper.rb +1 -1
- data/test/{test_agent.rb → test_backend.rb} +22 -22
- data/test/test_fiber.rb +28 -11
- data/test/test_io.rb +17 -1
- data/test/test_kernel.rb +5 -0
- data/test/test_resource_pool.rb +50 -16
- data/test/test_signal.rb +5 -29
- data/test/test_socket.rb +17 -0
- data/test/test_sync.rb +52 -0
- metadata +126 -98
- data/.gitbook.yaml +0 -4
- data/examples/adapters/concurrent-ruby.rb +0 -9
- data/examples/core/04-handling-signals.rb +0 -19
- data/examples/core/xx-agent.rb +0 -102
- data/examples/core/xx-at_exit.rb +0 -29
- data/examples/core/xx-caller.rb +0 -12
- data/examples/core/xx-daemon.rb +0 -14
- data/examples/core/xx-deadlock.rb +0 -8
- data/examples/core/xx-deferring-an-operation.rb +0 -14
- data/examples/core/xx-exception-backtrace.rb +0 -40
- data/examples/core/xx-fork-cleanup.rb +0 -22
- data/examples/core/xx-fork-spin.rb +0 -42
- data/examples/core/xx-fork-terminate.rb +0 -27
- data/examples/core/xx-move_on.rb +0 -23
- data/examples/core/xx-queue-async.rb +0 -120
- data/examples/core/xx-readpartial.rb +0 -18
- data/examples/core/xx-signals.rb +0 -16
- data/examples/core/xx-sleep-forever.rb +0 -9
- data/examples/core/xx-sleeping.rb +0 -25
- data/examples/core/xx-snooze-starve.rb +0 -16
- data/examples/core/xx-spin-fork.rb +0 -49
- data/examples/core/xx-state-machine.rb +0 -51
- data/examples/core/xx-stop.rb +0 -20
- data/examples/core/xx-supervisors.rb +0 -21
- data/examples/core/xx-thread-selector-sleep.rb +0 -51
- data/examples/core/xx-thread-selector-snooze.rb +0 -46
- data/examples/core/xx-thread-snooze.rb +0 -34
- data/examples/core/xx-timer-gc.rb +0 -17
- data/examples/core/xx-trace.rb +0 -79
- data/examples/performance/xx-array.rb +0 -11
- data/examples/performance/xx-fiber-switch.rb +0 -9
- data/examples/performance/xx-snooze.rb +0 -15
- data/examples/xx-spin.rb +0 -32
- data/ext/polyphony/agent.h +0 -39
|
@@ -16,7 +16,7 @@ if Object.constants.include?(:Reline)
|
|
|
16
16
|
fiber.cancel
|
|
17
17
|
end
|
|
18
18
|
read_ios.each do |io|
|
|
19
|
-
Thread.current.
|
|
19
|
+
Thread.current.backend.wait_io(io, false)
|
|
20
20
|
return [io]
|
|
21
21
|
end
|
|
22
22
|
rescue Polyphony::Cancel
|
|
@@ -26,22 +26,7 @@ if Object.constants.include?(:Reline)
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
else
|
|
29
|
-
|
|
30
|
-
# thread pool. That way, the reactor loop can keep running while waiting for
|
|
31
|
-
# readline to return
|
|
32
|
-
module ::Readline
|
|
33
|
-
alias_method :orig_readline, :readline
|
|
34
|
-
|
|
35
|
-
Workers = Polyphony::ThreadPool.new
|
|
36
|
-
|
|
37
|
-
def readline(*args)
|
|
38
|
-
p :readline
|
|
39
|
-
# caller.each do |l|
|
|
40
|
-
# STDOUT.orig_puts l
|
|
41
|
-
# end
|
|
42
|
-
Workers.process { orig_readline(*args) }
|
|
43
|
-
end
|
|
44
|
-
end
|
|
29
|
+
require_relative './readline'
|
|
45
30
|
|
|
46
31
|
# RubyLex patches
|
|
47
32
|
class ::RubyLex
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../polyphony'
|
|
4
|
+
require 'mysql2/client'
|
|
5
|
+
|
|
6
|
+
# Mysql2::Client overrides
|
|
7
|
+
Mysql2::Client.prepend(Module.new do
|
|
8
|
+
def initialize(config)
|
|
9
|
+
config[:async] = true
|
|
10
|
+
super
|
|
11
|
+
@io = ::IO.for_fd(socket)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def query(sql, **options)
|
|
15
|
+
super
|
|
16
|
+
Thread.current.backend.wait_io(@io, false)
|
|
17
|
+
async_result
|
|
18
|
+
end
|
|
19
|
+
end)
|
|
@@ -15,8 +15,8 @@ module ::PG
|
|
|
15
15
|
res = conn.connect_poll
|
|
16
16
|
case res
|
|
17
17
|
when PGRES_POLLING_FAILED then raise Error, conn.error_message
|
|
18
|
-
when PGRES_POLLING_READING then Thread.current.
|
|
19
|
-
when PGRES_POLLING_WRITING then Thread.current.
|
|
18
|
+
when PGRES_POLLING_READING then Thread.current.backend.wait_io(socket_io, false)
|
|
19
|
+
when PGRES_POLLING_WRITING then Thread.current.backend.wait_io(socket_io, true)
|
|
20
20
|
when PGRES_POLLING_OK then return conn.setnonblocking(true)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -42,7 +42,7 @@ class ::PG::Connection
|
|
|
42
42
|
|
|
43
43
|
def get_result(&block)
|
|
44
44
|
while is_busy
|
|
45
|
-
Thread.current.
|
|
45
|
+
Thread.current.backend.wait_io(socket_io, false)
|
|
46
46
|
consume_input
|
|
47
47
|
end
|
|
48
48
|
orig_get_result(&block)
|
|
@@ -59,7 +59,7 @@ class ::PG::Connection
|
|
|
59
59
|
|
|
60
60
|
def block(_timeout = 0)
|
|
61
61
|
while is_busy
|
|
62
|
-
Thread.current.
|
|
62
|
+
Thread.current.backend.wait_io(socket_io, false)
|
|
63
63
|
consume_input
|
|
64
64
|
end
|
|
65
65
|
end
|
|
@@ -97,7 +97,7 @@ class ::PG::Connection
|
|
|
97
97
|
return move_on_after(timeout) { wait_for_notify(&block) } if timeout
|
|
98
98
|
|
|
99
99
|
loop do
|
|
100
|
-
Thread.current.
|
|
100
|
+
Thread.current.backend.wait_io(socket_io, false)
|
|
101
101
|
consume_input
|
|
102
102
|
notice = notifies
|
|
103
103
|
next unless notice
|
|
@@ -7,7 +7,7 @@ module Polyphony
|
|
|
7
7
|
def watch(cmd = nil, &block)
|
|
8
8
|
terminated = nil
|
|
9
9
|
pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
|
|
10
|
-
Thread.current.
|
|
10
|
+
Thread.current.backend.waitpid(pid)
|
|
11
11
|
terminated = true
|
|
12
12
|
ensure
|
|
13
13
|
kill_process(pid) unless terminated || pid.nil?
|
|
@@ -23,7 +23,7 @@ module Polyphony
|
|
|
23
23
|
|
|
24
24
|
def kill_and_await(sig, pid)
|
|
25
25
|
::Process.kill(sig, pid)
|
|
26
|
-
Thread.current.
|
|
26
|
+
Thread.current.backend.waitpid(pid)
|
|
27
27
|
rescue SystemCallError
|
|
28
28
|
# ignore
|
|
29
29
|
puts 'SystemCallError in kill_and_await'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'polyphony'
|
|
4
|
+
require 'readline'
|
|
5
|
+
|
|
6
|
+
# readline blocks the current thread, so we offload it to the blocking-ops
|
|
7
|
+
# thread pool. That way, the reactor loop can keep running while waiting for
|
|
8
|
+
# readline to return
|
|
9
|
+
module ::Readline
|
|
10
|
+
alias_method :orig_readline, :readline
|
|
11
|
+
|
|
12
|
+
Worker = Polyphony::ThreadPool.new(1)
|
|
13
|
+
|
|
14
|
+
def readline(*args)
|
|
15
|
+
Worker.process { orig_readline(*args) }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../polyphony'
|
|
4
|
+
require 'sequel'
|
|
5
|
+
|
|
6
|
+
module Polyphony
|
|
7
|
+
# Sequel ConnectionPool that delegates to Polyphony::ResourcePool.
|
|
8
|
+
class FiberConnectionPool < Sequel::ConnectionPool
|
|
9
|
+
def initialize(db, opts = OPTS)
|
|
10
|
+
super
|
|
11
|
+
max_size = Integer(opts[:max_connections] || 4)
|
|
12
|
+
@pool = Polyphony::ResourcePool.new(limit: max_size) { make_new(:default) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def hold(_server = nil)
|
|
16
|
+
@pool.acquire do |conn|
|
|
17
|
+
yield conn
|
|
18
|
+
rescue Polyphony::BaseException
|
|
19
|
+
# The connection may be in an unrecoverable state if interrupted,
|
|
20
|
+
# discard the connection from the pool so it isn't reused.
|
|
21
|
+
@pool.discard!
|
|
22
|
+
raise
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def size
|
|
27
|
+
@pool.size
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def max_size
|
|
31
|
+
@pool.limit
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def preconnect(_concurrent = false)
|
|
35
|
+
@pool.preheat!
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Override Sequel::Database to use FiberConnectionPool by default.
|
|
40
|
+
Sequel::Database.prepend(Module.new do
|
|
41
|
+
def connection_pool_default_options
|
|
42
|
+
{ pool_class: FiberConnectionPool }
|
|
43
|
+
end
|
|
44
|
+
end)
|
|
45
|
+
end
|
|
@@ -33,4 +33,15 @@ module Polyphony
|
|
|
33
33
|
|
|
34
34
|
# Restart is used to restart a fiber
|
|
35
35
|
class Restart < BaseException; end
|
|
36
|
+
|
|
37
|
+
# Interjection is used to run arbitrary code on arbitrary fibers at any point
|
|
38
|
+
class Interjection < BaseException
|
|
39
|
+
def initialize(proc)
|
|
40
|
+
@proc = proc
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def invoke
|
|
44
|
+
@proc.call
|
|
45
|
+
end
|
|
46
|
+
end
|
|
36
47
|
end
|
|
@@ -19,13 +19,18 @@ module Polyphony
|
|
|
19
19
|
fiber = ::Fiber.current
|
|
20
20
|
canceller = spin do
|
|
21
21
|
sleep interval
|
|
22
|
-
exception = with_exception
|
|
23
|
-
with_exception.new : RuntimeError.new(with_exception)
|
|
22
|
+
exception = cancel_exception(with_exception)
|
|
24
23
|
fiber.schedule exception
|
|
25
24
|
end
|
|
26
25
|
block ? cancel_after_wrap_block(canceller, &block) : canceller
|
|
27
26
|
end
|
|
28
27
|
|
|
28
|
+
def cancel_exception(exception)
|
|
29
|
+
return exception.new if exception.is_a?(Class)
|
|
30
|
+
|
|
31
|
+
RuntimeError.new(exception)
|
|
32
|
+
end
|
|
33
|
+
|
|
29
34
|
def cancel_after_wrap_block(canceller, &block)
|
|
30
35
|
block.call
|
|
31
36
|
ensure
|
|
@@ -50,7 +55,7 @@ module Polyphony
|
|
|
50
55
|
next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + interval
|
|
51
56
|
loop do
|
|
52
57
|
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
53
|
-
Thread.current.
|
|
58
|
+
Thread.current.backend.sleep(next_time - now)
|
|
54
59
|
yield
|
|
55
60
|
loop do
|
|
56
61
|
next_time += interval
|
|
@@ -87,8 +92,8 @@ module Polyphony
|
|
|
87
92
|
Fiber.current.receive
|
|
88
93
|
end
|
|
89
94
|
|
|
90
|
-
def
|
|
91
|
-
Fiber.current.
|
|
95
|
+
def receive_all_pending
|
|
96
|
+
Fiber.current.receive_all_pending
|
|
92
97
|
end
|
|
93
98
|
|
|
94
99
|
def supervise(*args, &block)
|
|
@@ -98,20 +103,20 @@ module Polyphony
|
|
|
98
103
|
def sleep(duration = nil)
|
|
99
104
|
return sleep_forever unless duration
|
|
100
105
|
|
|
101
|
-
Thread.current.
|
|
106
|
+
Thread.current.backend.sleep duration
|
|
102
107
|
end
|
|
103
108
|
|
|
104
109
|
def sleep_forever
|
|
105
|
-
Thread.current.
|
|
110
|
+
Thread.current.backend.ref
|
|
106
111
|
loop { sleep 60 }
|
|
107
112
|
ensure
|
|
108
|
-
Thread.current.
|
|
113
|
+
Thread.current.backend.unref
|
|
109
114
|
end
|
|
110
115
|
|
|
111
|
-
def throttled_loop(rate
|
|
112
|
-
throttler = Polyphony::Throttler.new(rate)
|
|
113
|
-
if count
|
|
114
|
-
count.times { |_i| throttler.(&block) }
|
|
116
|
+
def throttled_loop(rate = nil, **opts, &block)
|
|
117
|
+
throttler = Polyphony::Throttler.new(rate || opts)
|
|
118
|
+
if opts[:count]
|
|
119
|
+
opts[:count].times { |_i| throttler.(&block) }
|
|
115
120
|
else
|
|
116
121
|
loop { throttler.(&block) }
|
|
117
122
|
end
|
|
@@ -20,19 +20,25 @@ module Polyphony
|
|
|
20
20
|
@stock.size
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def acquire
|
|
23
|
+
def acquire(&block)
|
|
24
24
|
fiber = Fiber.current
|
|
25
|
-
return @acquired_resources[fiber] if @acquired_resources[fiber]
|
|
25
|
+
return yield @acquired_resources[fiber] if @acquired_resources[fiber]
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
acquire_from_stock(fiber, &block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def acquire_from_stock(fiber)
|
|
31
|
+
add_to_stock if (@stock.empty? || @stock.pending?) && @size < @limit
|
|
28
32
|
resource = @stock.shift
|
|
29
33
|
@acquired_resources[fiber] = resource
|
|
30
34
|
yield resource
|
|
31
35
|
ensure
|
|
32
|
-
@acquired_resources
|
|
33
|
-
|
|
36
|
+
if resource && @acquired_resources[fiber] == resource
|
|
37
|
+
@acquired_resources.delete(fiber)
|
|
38
|
+
@stock.push resource
|
|
39
|
+
end
|
|
34
40
|
end
|
|
35
|
-
|
|
41
|
+
|
|
36
42
|
def method_missing(sym, *args, &block)
|
|
37
43
|
acquire { |r| r.send(sym, *args, &block) }
|
|
38
44
|
end
|
|
@@ -45,7 +51,14 @@ module Polyphony
|
|
|
45
51
|
# @return [any] allocated resource
|
|
46
52
|
def add_to_stock
|
|
47
53
|
@size += 1
|
|
48
|
-
|
|
54
|
+
resource = @allocator.call
|
|
55
|
+
@stock << resource
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Discards the currently-acquired resource
|
|
59
|
+
# instead of returning it to the pool when done.
|
|
60
|
+
def discard!
|
|
61
|
+
@size -= 1 if @acquired_resources.delete(Fiber.current)
|
|
49
62
|
end
|
|
50
63
|
|
|
51
64
|
def preheat!
|
data/lib/polyphony/core/sync.rb
CHANGED
|
@@ -8,16 +8,54 @@ module Polyphony
|
|
|
8
8
|
@store << :token
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def synchronize
|
|
11
|
+
def synchronize(&block)
|
|
12
12
|
return yield if @holding_fiber == Fiber.current
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
synchronize_not_holding(&block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def synchronize_not_holding
|
|
18
|
+
@token = @store.shift
|
|
19
|
+
@holding_fiber = Fiber.current
|
|
20
|
+
yield
|
|
21
|
+
ensure
|
|
22
|
+
@holding_fiber = nil
|
|
23
|
+
@store << @token if @token
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def conditional_release
|
|
27
|
+
@store << @token
|
|
28
|
+
@token = nil
|
|
29
|
+
@holding_fiber = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def conditional_reacquire
|
|
33
|
+
@token = @store.shift
|
|
34
|
+
@holding_fiber = Fiber.current
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Implements a fiber-aware ConditionVariable
|
|
39
|
+
class ConditionVariable
|
|
40
|
+
def initialize
|
|
41
|
+
@queue = Polyphony::Queue.new
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def wait(mutex, _timeout = nil)
|
|
45
|
+
mutex.conditional_release
|
|
46
|
+
@queue << Fiber.current
|
|
47
|
+
Thread.current.backend.wait_event(true)
|
|
48
|
+
mutex.conditional_reacquire
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def signal
|
|
52
|
+
fiber = @queue.shift
|
|
53
|
+
fiber.schedule
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def broadcast
|
|
57
|
+
while (fiber = @queue.shift)
|
|
58
|
+
fiber.schedule
|
|
21
59
|
end
|
|
22
60
|
end
|
|
23
61
|
end
|
|
@@ -46,6 +46,10 @@ class ::Exception
|
|
|
46
46
|
|
|
47
47
|
backtrace.reject { |l| l[POLYPHONY_DIR] }
|
|
48
48
|
end
|
|
49
|
+
|
|
50
|
+
def invoke
|
|
51
|
+
Kernel.raise(self)
|
|
52
|
+
end
|
|
49
53
|
end
|
|
50
54
|
|
|
51
55
|
# Overrides for Process
|
|
@@ -53,7 +57,7 @@ module ::Process
|
|
|
53
57
|
class << self
|
|
54
58
|
alias_method :orig_detach, :detach
|
|
55
59
|
def detach(pid)
|
|
56
|
-
fiber = spin { Thread.current.
|
|
60
|
+
fiber = spin { Thread.current.backend.waitpid(pid) }
|
|
57
61
|
fiber.define_singleton_method(:pid) { pid }
|
|
58
62
|
fiber
|
|
59
63
|
end
|
|
@@ -112,56 +116,65 @@ module ::Kernel
|
|
|
112
116
|
strs = args.inject([]) do |m, a|
|
|
113
117
|
m << a.inspect << "\n"
|
|
114
118
|
end
|
|
115
|
-
STDOUT.write
|
|
119
|
+
STDOUT.write(*strs)
|
|
116
120
|
args.size == 1 ? args.first : args
|
|
117
121
|
end
|
|
118
122
|
|
|
119
123
|
alias_method :orig_system, :system
|
|
120
124
|
def system(*args)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
Kernel.system(*args)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class << self
|
|
129
|
+
alias_method :orig_system, :system
|
|
130
|
+
def system(*args)
|
|
131
|
+
waiter = nil
|
|
132
|
+
Open3.popen2(*args) do |i, o, t|
|
|
133
|
+
waiter = t
|
|
134
|
+
i.close
|
|
135
|
+
pipe_to_eof(o, $stdout)
|
|
136
|
+
end
|
|
137
|
+
waiter.await.last == 0
|
|
138
|
+
rescue SystemCallError
|
|
139
|
+
nil
|
|
124
140
|
end
|
|
125
|
-
true
|
|
126
|
-
rescue SystemCallError
|
|
127
|
-
nil
|
|
128
141
|
end
|
|
129
142
|
|
|
130
143
|
def pipe_to_eof(src, dest)
|
|
131
|
-
|
|
132
|
-
data = src.readpartial(8192)
|
|
133
|
-
dest << data
|
|
134
|
-
rescue EOFError
|
|
135
|
-
break
|
|
136
|
-
end
|
|
144
|
+
src.read_loop { |data| dest << data }
|
|
137
145
|
end
|
|
138
146
|
|
|
139
147
|
alias_method :orig_trap, :trap
|
|
140
148
|
def trap(sig, command = nil, &block)
|
|
141
149
|
return orig_trap(sig, command) if command.is_a? String
|
|
142
|
-
|
|
143
|
-
block = command if command.respond_to?(:call)
|
|
144
|
-
exception =
|
|
150
|
+
|
|
151
|
+
block = command if !block && command.respond_to?(:call)
|
|
152
|
+
exception = signal_exception(block, command)
|
|
145
153
|
|
|
146
154
|
# The signal trap can be invoked at any time, including while the system
|
|
147
|
-
#
|
|
155
|
+
# backend is blocking while polling for events. In order to deal with this
|
|
148
156
|
# correctly, we spin a fiber that will run the signal handler code, then
|
|
149
157
|
# call break_out_of_ev_loop, which will put the fiber at the front of the
|
|
150
|
-
# run queue, then wake up the
|
|
158
|
+
# run queue, then wake up the backend.
|
|
151
159
|
#
|
|
152
160
|
# If the command argument is an exception class however, it will be raised
|
|
153
161
|
# directly in the context of the main fiber.
|
|
154
162
|
orig_trap(sig) do
|
|
155
|
-
|
|
156
|
-
Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, exception)
|
|
157
|
-
else
|
|
158
|
-
fiber = spin { snooze; block.call }
|
|
159
|
-
Thread.current.break_out_of_ev_loop(fiber, nil)
|
|
160
|
-
end
|
|
163
|
+
Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, exception)
|
|
161
164
|
end
|
|
162
165
|
end
|
|
163
166
|
end
|
|
164
167
|
|
|
168
|
+
def signal_exception(block, command)
|
|
169
|
+
if block
|
|
170
|
+
Polyphony::Interjection.new(block)
|
|
171
|
+
elsif command.is_a?(Class)
|
|
172
|
+
command.new
|
|
173
|
+
else
|
|
174
|
+
raise ArgumentError, 'Must supply block or exception or callable object'
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
165
178
|
# Override Timeout to use cancel scope
|
|
166
179
|
module ::Timeout
|
|
167
180
|
def self.timeout(sec, klass = nil, message = nil, &block)
|