omq 0.9.0 → 0.11.0
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/CHANGELOG.md +129 -0
- data/README.md +28 -3
- data/lib/omq/channel.rb +5 -5
- data/lib/omq/client_server.rb +10 -10
- data/lib/omq/engine.rb +702 -0
- data/lib/omq/options.rb +48 -0
- data/lib/omq/pair.rb +4 -4
- data/lib/omq/peer.rb +5 -5
- data/lib/omq/pub_sub.rb +18 -18
- data/lib/omq/push_pull.rb +6 -6
- data/lib/omq/queue_interface.rb +73 -0
- data/lib/omq/radio_dish.rb +6 -6
- data/lib/omq/reactor.rb +128 -0
- data/lib/omq/readable.rb +44 -0
- data/lib/omq/req_rep.rb +8 -8
- data/lib/omq/router_dealer.rb +8 -8
- data/lib/omq/routing/channel.rb +83 -0
- data/lib/omq/routing/client.rb +56 -0
- data/lib/omq/routing/dealer.rb +57 -0
- data/lib/omq/routing/dish.rb +78 -0
- data/lib/omq/routing/fan_out.rb +140 -0
- data/lib/omq/routing/gather.rb +46 -0
- data/lib/omq/routing/pair.rb +86 -0
- data/lib/omq/routing/peer.rb +101 -0
- data/lib/omq/routing/pub.rb +60 -0
- data/lib/omq/routing/pull.rb +46 -0
- data/lib/omq/routing/push.rb +81 -0
- data/lib/omq/routing/radio.rb +150 -0
- data/lib/omq/routing/rep.rb +101 -0
- data/lib/omq/routing/req.rb +65 -0
- data/lib/omq/routing/round_robin.rb +168 -0
- data/lib/omq/routing/router.rb +110 -0
- data/lib/omq/routing/scatter.rb +82 -0
- data/lib/omq/routing/server.rb +101 -0
- data/lib/omq/routing/sub.rb +78 -0
- data/lib/omq/routing/xpub.rb +72 -0
- data/lib/omq/routing/xsub.rb +83 -0
- data/lib/omq/routing.rb +66 -0
- data/lib/omq/scatter_gather.rb +8 -8
- data/lib/omq/single_frame.rb +18 -0
- data/lib/omq/socket.rb +32 -11
- data/lib/omq/transport/inproc.rb +355 -0
- data/lib/omq/transport/ipc.rb +117 -0
- data/lib/omq/transport/tcp.rb +111 -0
- data/lib/omq/transport/tls.rb +146 -0
- data/lib/omq/version.rb +1 -1
- data/lib/omq/writable.rb +66 -0
- data/lib/omq.rb +64 -4
- metadata +34 -33
- data/lib/omq/zmtp/engine.rb +0 -551
- data/lib/omq/zmtp/options.rb +0 -48
- data/lib/omq/zmtp/reactor.rb +0 -131
- data/lib/omq/zmtp/readable.rb +0 -29
- data/lib/omq/zmtp/routing/channel.rb +0 -81
- data/lib/omq/zmtp/routing/client.rb +0 -56
- data/lib/omq/zmtp/routing/dealer.rb +0 -57
- data/lib/omq/zmtp/routing/dish.rb +0 -80
- data/lib/omq/zmtp/routing/fan_out.rb +0 -131
- data/lib/omq/zmtp/routing/gather.rb +0 -48
- data/lib/omq/zmtp/routing/pair.rb +0 -84
- data/lib/omq/zmtp/routing/peer.rb +0 -100
- data/lib/omq/zmtp/routing/pub.rb +0 -62
- data/lib/omq/zmtp/routing/pull.rb +0 -48
- data/lib/omq/zmtp/routing/push.rb +0 -80
- data/lib/omq/zmtp/routing/radio.rb +0 -139
- data/lib/omq/zmtp/routing/rep.rb +0 -101
- data/lib/omq/zmtp/routing/req.rb +0 -65
- data/lib/omq/zmtp/routing/round_robin.rb +0 -143
- data/lib/omq/zmtp/routing/router.rb +0 -109
- data/lib/omq/zmtp/routing/scatter.rb +0 -81
- data/lib/omq/zmtp/routing/server.rb +0 -100
- data/lib/omq/zmtp/routing/sub.rb +0 -80
- data/lib/omq/zmtp/routing/xpub.rb +0 -74
- data/lib/omq/zmtp/routing/xsub.rb +0 -86
- data/lib/omq/zmtp/routing.rb +0 -65
- data/lib/omq/zmtp/single_frame.rb +0 -20
- data/lib/omq/zmtp/transport/inproc.rb +0 -359
- data/lib/omq/zmtp/transport/ipc.rb +0 -118
- data/lib/omq/zmtp/transport/tcp.rb +0 -117
- data/lib/omq/zmtp/writable.rb +0 -61
- data/lib/omq/zmtp.rb +0 -81
data/lib/omq/options.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
# Pure Ruby socket options.
|
|
5
|
+
#
|
|
6
|
+
# All timeouts are in seconds (Numeric) or nil (no timeout).
|
|
7
|
+
# HWM values are integers.
|
|
8
|
+
#
|
|
9
|
+
class Options
|
|
10
|
+
DEFAULT_HWM = 1000
|
|
11
|
+
|
|
12
|
+
# @param linger [Integer] linger period in seconds (default 0)
|
|
13
|
+
#
|
|
14
|
+
def initialize(linger: 0)
|
|
15
|
+
@send_hwm = DEFAULT_HWM
|
|
16
|
+
@recv_hwm = DEFAULT_HWM
|
|
17
|
+
@linger = linger
|
|
18
|
+
@identity = "".b
|
|
19
|
+
@router_mandatory = false
|
|
20
|
+
@read_timeout = nil # seconds, nil = no timeout
|
|
21
|
+
@write_timeout = nil
|
|
22
|
+
@reconnect_interval = 0.1 # seconds, or Range for backoff (e.g. 0.1..5.0)
|
|
23
|
+
@heartbeat_interval = nil # seconds, nil = disabled
|
|
24
|
+
@heartbeat_ttl = nil # seconds, nil = use heartbeat_interval
|
|
25
|
+
@heartbeat_timeout = nil # seconds, nil = use heartbeat_interval
|
|
26
|
+
@max_message_size = nil # bytes, nil = unlimited
|
|
27
|
+
@conflate = false
|
|
28
|
+
@mechanism = Protocol::ZMTP::Mechanism::Null.new
|
|
29
|
+
@tls_context = nil # OpenSSL::SSL::SSLContext for tls+tcp://
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
attr_accessor :send_hwm, :recv_hwm,
|
|
33
|
+
:linger, :identity,
|
|
34
|
+
:router_mandatory, :conflate,
|
|
35
|
+
:read_timeout, :write_timeout,
|
|
36
|
+
:reconnect_interval,
|
|
37
|
+
:heartbeat_interval, :heartbeat_ttl, :heartbeat_timeout,
|
|
38
|
+
:max_message_size,
|
|
39
|
+
:mechanism,
|
|
40
|
+
:tls_context
|
|
41
|
+
|
|
42
|
+
alias_method :router_mandatory?, :router_mandatory
|
|
43
|
+
alias_method :recv_timeout, :read_timeout
|
|
44
|
+
alias_method :recv_timeout=, :read_timeout=
|
|
45
|
+
alias_method :send_timeout, :write_timeout
|
|
46
|
+
alias_method :send_timeout=, :write_timeout=
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/omq/pair.rb
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
4
|
class PAIR < Socket
|
|
5
|
-
include
|
|
6
|
-
include
|
|
5
|
+
include Readable
|
|
6
|
+
include Writable
|
|
7
7
|
|
|
8
|
-
def initialize(endpoints = nil, linger: 0)
|
|
9
|
-
_init_engine(:PAIR, linger: linger)
|
|
8
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
9
|
+
_init_engine(:PAIR, linger: linger, backend: backend)
|
|
10
10
|
_attach(endpoints, default: :connect)
|
|
11
11
|
end
|
|
12
12
|
end
|
data/lib/omq/peer.rb
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
4
|
class PEER < Socket
|
|
5
|
-
include
|
|
6
|
-
include
|
|
7
|
-
include
|
|
5
|
+
include Readable
|
|
6
|
+
include Writable
|
|
7
|
+
include SingleFrame
|
|
8
8
|
|
|
9
|
-
def initialize(endpoints = nil, linger: 0)
|
|
10
|
-
_init_engine(:PEER, linger: linger)
|
|
9
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
10
|
+
_init_engine(:PEER, linger: linger, backend: backend)
|
|
11
11
|
_attach(endpoints, default: :connect)
|
|
12
12
|
end
|
|
13
13
|
|
data/lib/omq/pub_sub.rb
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
4
|
class PUB < Socket
|
|
5
|
-
include
|
|
5
|
+
include Writable
|
|
6
6
|
|
|
7
|
-
def initialize(endpoints = nil, linger: 0, conflate: false)
|
|
8
|
-
_init_engine(:PUB, linger: linger, conflate: conflate)
|
|
7
|
+
def initialize(endpoints = nil, linger: 0, conflate: false, backend: nil)
|
|
8
|
+
_init_engine(:PUB, linger: linger, conflate: conflate, backend: backend)
|
|
9
9
|
_attach(endpoints, default: :bind)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -13,7 +13,7 @@ module OMQ
|
|
|
13
13
|
# SUB socket.
|
|
14
14
|
#
|
|
15
15
|
class SUB < Socket
|
|
16
|
-
include
|
|
16
|
+
include Readable
|
|
17
17
|
|
|
18
18
|
# @return [String] subscription prefix to subscribe to everything
|
|
19
19
|
#
|
|
@@ -21,13 +21,13 @@ module OMQ
|
|
|
21
21
|
|
|
22
22
|
# @param endpoints [String, nil]
|
|
23
23
|
# @param linger [Integer]
|
|
24
|
-
# @param
|
|
24
|
+
# @param subscribe [String, nil] subscription prefix; +nil+ (default)
|
|
25
25
|
# means no subscription — call {#subscribe} explicitly.
|
|
26
26
|
#
|
|
27
|
-
def initialize(endpoints = nil, linger: 0,
|
|
28
|
-
_init_engine(:SUB, linger: linger)
|
|
27
|
+
def initialize(endpoints = nil, linger: 0, subscribe: nil, backend: nil)
|
|
28
|
+
_init_engine(:SUB, linger: linger, backend: backend)
|
|
29
29
|
_attach(endpoints, default: :connect)
|
|
30
|
-
subscribe(
|
|
30
|
+
self.subscribe(subscribe) unless subscribe.nil?
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Subscribes to a topic prefix.
|
|
@@ -50,28 +50,28 @@ module OMQ
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
class XPUB < Socket
|
|
53
|
-
include
|
|
54
|
-
include
|
|
53
|
+
include Readable
|
|
54
|
+
include Writable
|
|
55
55
|
|
|
56
|
-
def initialize(endpoints = nil, linger: 0)
|
|
57
|
-
_init_engine(:XPUB, linger: linger)
|
|
56
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
57
|
+
_init_engine(:XPUB, linger: linger, backend: backend)
|
|
58
58
|
_attach(endpoints, default: :bind)
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
class XSUB < Socket
|
|
63
|
-
include
|
|
64
|
-
include
|
|
63
|
+
include Readable
|
|
64
|
+
include Writable
|
|
65
65
|
|
|
66
66
|
# @param endpoints [String, nil]
|
|
67
67
|
# @param linger [Integer]
|
|
68
|
-
# @param
|
|
68
|
+
# @param subscribe [String, nil] subscription prefix; +nil+ (default)
|
|
69
69
|
# means no subscription — send a subscribe frame explicitly.
|
|
70
70
|
#
|
|
71
|
-
def initialize(endpoints = nil, linger: 0,
|
|
72
|
-
_init_engine(:XSUB, linger: linger)
|
|
71
|
+
def initialize(endpoints = nil, linger: 0, subscribe: nil, backend: nil)
|
|
72
|
+
_init_engine(:XSUB, linger: linger, backend: backend)
|
|
73
73
|
_attach(endpoints, default: :connect)
|
|
74
|
-
send("\x01#{
|
|
74
|
+
send("\x01#{subscribe}".b) unless subscribe.nil?
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
end
|
data/lib/omq/push_pull.rb
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
4
|
class PUSH < Socket
|
|
5
|
-
include
|
|
5
|
+
include Writable
|
|
6
6
|
|
|
7
|
-
def initialize(endpoints = nil, linger: 0, send_hwm: nil, send_timeout: nil)
|
|
8
|
-
_init_engine(:PUSH, linger: linger, send_hwm: send_hwm, send_timeout: send_timeout)
|
|
7
|
+
def initialize(endpoints = nil, linger: 0, send_hwm: nil, send_timeout: nil, backend: nil)
|
|
8
|
+
_init_engine(:PUSH, linger: linger, send_hwm: send_hwm, send_timeout: send_timeout, backend: backend)
|
|
9
9
|
_attach(endpoints, default: :connect)
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
class PULL < Socket
|
|
14
|
-
include
|
|
14
|
+
include Readable
|
|
15
15
|
|
|
16
|
-
def initialize(endpoints = nil, linger: 0, recv_hwm: nil, recv_timeout: nil)
|
|
17
|
-
_init_engine(:PULL, linger: linger, recv_hwm: recv_hwm, recv_timeout: recv_timeout)
|
|
16
|
+
def initialize(endpoints = nil, linger: 0, recv_hwm: nil, recv_timeout: nil, backend: nil)
|
|
17
|
+
_init_engine(:PULL, linger: linger, recv_hwm: recv_hwm, recv_timeout: recv_timeout, backend: backend)
|
|
18
18
|
_attach(endpoints, default: :bind)
|
|
19
19
|
end
|
|
20
20
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
# Async::Queue-compatible read interface.
|
|
5
|
+
#
|
|
6
|
+
# Automatically included by {Readable}. Provides #dequeue, #pop,
|
|
7
|
+
# #wait, and #each so sockets can be used where an Async::Queue
|
|
8
|
+
# is expected.
|
|
9
|
+
#
|
|
10
|
+
module QueueReadable
|
|
11
|
+
# Dequeues the next message.
|
|
12
|
+
#
|
|
13
|
+
# @param timeout [Numeric, nil] timeout in seconds (overrides
|
|
14
|
+
# the socket's +read_timeout+ for this call)
|
|
15
|
+
# @return [Array<String>] message parts
|
|
16
|
+
# @raise [IO::TimeoutError] if timeout exceeded
|
|
17
|
+
#
|
|
18
|
+
def dequeue(timeout: @options.read_timeout)
|
|
19
|
+
msg = @recv_mutex.synchronize { @recv_buffer.shift }
|
|
20
|
+
return msg if msg
|
|
21
|
+
|
|
22
|
+
batch = Reactor.run { with_timeout(timeout) { @engine.dequeue_recv_batch(Readable::RECV_BATCH_SIZE) } }
|
|
23
|
+
msg = batch.shift
|
|
24
|
+
@recv_mutex.synchronize { @recv_buffer.concat(batch) } unless batch.empty?
|
|
25
|
+
msg
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
alias_method :pop, :dequeue
|
|
29
|
+
|
|
30
|
+
# Waits for the next message indefinitely (ignores read_timeout).
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<String>] message parts
|
|
33
|
+
#
|
|
34
|
+
def wait
|
|
35
|
+
dequeue(timeout: nil)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Yields each received message until the socket is closed or
|
|
39
|
+
# a receive timeout expires.
|
|
40
|
+
#
|
|
41
|
+
# @yield [Array<String>] message parts
|
|
42
|
+
# @return [void]
|
|
43
|
+
#
|
|
44
|
+
def each
|
|
45
|
+
while (msg = receive)
|
|
46
|
+
yield msg
|
|
47
|
+
end
|
|
48
|
+
rescue IO::TimeoutError
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Async::Queue-compatible write interface.
|
|
55
|
+
#
|
|
56
|
+
# Automatically included by {Writable}. Provides #enqueue, #push,
|
|
57
|
+
# and #signal so sockets can be used where an Async::Queue is
|
|
58
|
+
# expected.
|
|
59
|
+
#
|
|
60
|
+
module QueueWritable
|
|
61
|
+
# Enqueues one or more messages for sending.
|
|
62
|
+
#
|
|
63
|
+
# @param messages [String, Array<String>]
|
|
64
|
+
# @return [self]
|
|
65
|
+
#
|
|
66
|
+
def enqueue(*messages)
|
|
67
|
+
messages.each { |msg| send(msg) }
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
alias_method :push, :enqueue
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/omq/radio_dish.rb
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
4
|
class RADIO < Socket
|
|
5
|
-
include
|
|
5
|
+
include Writable
|
|
6
6
|
|
|
7
|
-
def initialize(endpoints = nil, linger: 0, conflate: false)
|
|
8
|
-
_init_engine(:RADIO, linger: linger, conflate: conflate)
|
|
7
|
+
def initialize(endpoints = nil, linger: 0, conflate: false, backend: nil)
|
|
8
|
+
_init_engine(:RADIO, linger: linger, conflate: conflate, backend: backend)
|
|
9
9
|
_attach(endpoints, default: :bind)
|
|
10
10
|
end
|
|
11
11
|
|
|
@@ -45,10 +45,10 @@ module OMQ
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
class DISH < Socket
|
|
48
|
-
include
|
|
48
|
+
include Readable
|
|
49
49
|
|
|
50
|
-
def initialize(endpoints = nil, linger: 0, group: nil)
|
|
51
|
-
_init_engine(:DISH, linger: linger)
|
|
50
|
+
def initialize(endpoints = nil, linger: 0, group: nil, backend: nil)
|
|
51
|
+
_init_engine(:DISH, linger: linger, backend: backend)
|
|
52
52
|
_attach(endpoints, default: :connect)
|
|
53
53
|
join(group) if group
|
|
54
54
|
end
|
data/lib/omq/reactor.rb
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "async"
|
|
4
|
+
|
|
5
|
+
module OMQ
|
|
6
|
+
# Shared IO reactor for the Ruby backend.
|
|
7
|
+
#
|
|
8
|
+
# When user code runs inside an Async reactor, engine tasks are
|
|
9
|
+
# spawned directly under the caller's Async task. When no reactor
|
|
10
|
+
# is available (e.g. bare Thread.new), a single shared IO thread
|
|
11
|
+
# hosts all engine tasks — mirroring libzmq's IO thread.
|
|
12
|
+
#
|
|
13
|
+
# Engines obtain the IO thread's root task via {.root_task} and
|
|
14
|
+
# use it as their @parent_task. Blocking operations from the main
|
|
15
|
+
# thread are dispatched to the IO thread via {.run}.
|
|
16
|
+
#
|
|
17
|
+
module Reactor
|
|
18
|
+
@mutex = Mutex.new
|
|
19
|
+
@thread = nil
|
|
20
|
+
@root_task = nil
|
|
21
|
+
@work_queue = nil
|
|
22
|
+
@lingers = Hash.new(0) # linger value → count of active sockets
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
# Returns the root Async task inside the shared IO thread.
|
|
26
|
+
# Starts the thread exactly once (double-checked lock).
|
|
27
|
+
#
|
|
28
|
+
# @return [Async::Task]
|
|
29
|
+
#
|
|
30
|
+
def root_task
|
|
31
|
+
return @root_task if @root_task
|
|
32
|
+
@mutex.synchronize do
|
|
33
|
+
return @root_task if @root_task
|
|
34
|
+
ready = Thread::Queue.new
|
|
35
|
+
@work_queue = Async::Queue.new
|
|
36
|
+
@thread = Thread.new { run_reactor(ready) }
|
|
37
|
+
@thread.name = "omq-io"
|
|
38
|
+
@root_task = ready.pop
|
|
39
|
+
at_exit { stop! }
|
|
40
|
+
end
|
|
41
|
+
@root_task
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Runs a block inside the Async reactor.
|
|
46
|
+
#
|
|
47
|
+
# Inside an Async reactor: runs directly.
|
|
48
|
+
# Outside: dispatches to the shared IO thread and blocks
|
|
49
|
+
# the calling thread until the result is available.
|
|
50
|
+
#
|
|
51
|
+
# @return [Object] the block's return value
|
|
52
|
+
#
|
|
53
|
+
def run(&block)
|
|
54
|
+
if Async::Task.current?
|
|
55
|
+
yield
|
|
56
|
+
else
|
|
57
|
+
result = Thread::Queue.new
|
|
58
|
+
root_task # ensure started
|
|
59
|
+
@work_queue.push([block, result])
|
|
60
|
+
status, value = result.pop
|
|
61
|
+
raise value if status == :error
|
|
62
|
+
value
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Registers a socket's linger value.
|
|
68
|
+
#
|
|
69
|
+
# @param seconds [Numeric, nil] linger value
|
|
70
|
+
#
|
|
71
|
+
def track_linger(seconds)
|
|
72
|
+
@lingers[seconds || 0] += 1
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Unregisters a socket's linger value.
|
|
77
|
+
#
|
|
78
|
+
# @param seconds [Numeric, nil] linger value
|
|
79
|
+
#
|
|
80
|
+
def untrack_linger(seconds)
|
|
81
|
+
key = seconds || 0
|
|
82
|
+
@lingers[key] -= 1
|
|
83
|
+
@lingers.delete(key) if @lingers[key] <= 0
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# Stops the shared IO thread.
|
|
88
|
+
#
|
|
89
|
+
# @return [void]
|
|
90
|
+
#
|
|
91
|
+
def stop!
|
|
92
|
+
return unless @thread&.alive?
|
|
93
|
+
max_linger = @lingers.empty? ? 0 : @lingers.keys.max
|
|
94
|
+
@work_queue&.push(nil)
|
|
95
|
+
@thread&.join(max_linger + 1)
|
|
96
|
+
@thread = nil
|
|
97
|
+
@root_task = nil
|
|
98
|
+
@work_queue = nil
|
|
99
|
+
@lingers = Hash.new(0)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# Runs the shared Async reactor.
|
|
105
|
+
#
|
|
106
|
+
# Processes work items dispatched via {.run} while engine
|
|
107
|
+
# tasks (accept loops, pumps, etc.) run as transient children.
|
|
108
|
+
#
|
|
109
|
+
# @param ready [Thread::Queue] receives the root task once started
|
|
110
|
+
#
|
|
111
|
+
def run_reactor(ready)
|
|
112
|
+
Async do |task|
|
|
113
|
+
ready.push(task)
|
|
114
|
+
loop do
|
|
115
|
+
item = @work_queue.dequeue
|
|
116
|
+
break if item.nil?
|
|
117
|
+
block, result = item
|
|
118
|
+
task.async do
|
|
119
|
+
result.push([:ok, block.call])
|
|
120
|
+
rescue => e
|
|
121
|
+
result.push([:error, e])
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
data/lib/omq/readable.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "timeout"
|
|
4
|
+
|
|
5
|
+
module OMQ
|
|
6
|
+
# Pure Ruby Readable mixin. Dequeues messages from the engine's recv queue.
|
|
7
|
+
#
|
|
8
|
+
module Readable
|
|
9
|
+
include QueueReadable
|
|
10
|
+
|
|
11
|
+
# Maximum messages to prefetch from the recv queue per drain.
|
|
12
|
+
RECV_BATCH_SIZE = 64
|
|
13
|
+
|
|
14
|
+
# Receives the next message. Returns from a local prefetch
|
|
15
|
+
# buffer when available, otherwise drains up to
|
|
16
|
+
# {RECV_BATCH_SIZE} messages from the recv queue in one
|
|
17
|
+
# synchronized dequeue.
|
|
18
|
+
#
|
|
19
|
+
# @return [Array<String>] message parts
|
|
20
|
+
# @raise [IO::TimeoutError] if read_timeout exceeded
|
|
21
|
+
#
|
|
22
|
+
def receive
|
|
23
|
+
@recv_mutex.synchronize { @recv_buffer.shift } || fill_recv_buffer
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Waits until the socket is readable.
|
|
27
|
+
#
|
|
28
|
+
# @param timeout [Numeric, nil] timeout in seconds
|
|
29
|
+
# @return [true]
|
|
30
|
+
#
|
|
31
|
+
def wait_readable(timeout = @options.read_timeout)
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def fill_recv_buffer
|
|
38
|
+
batch = Reactor.run { with_timeout(@options.read_timeout) { @engine.dequeue_recv_batch(RECV_BATCH_SIZE) } }
|
|
39
|
+
msg = batch.shift
|
|
40
|
+
@recv_mutex.synchronize { @recv_buffer.concat(batch) } unless batch.empty?
|
|
41
|
+
msg
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/omq/req_rep.rb
CHANGED
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
4
|
class REQ < Socket
|
|
5
|
-
include
|
|
6
|
-
include
|
|
5
|
+
include Readable
|
|
6
|
+
include Writable
|
|
7
7
|
|
|
8
|
-
def initialize(endpoints = nil, linger: 0)
|
|
9
|
-
_init_engine(:REQ, linger: linger)
|
|
8
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
9
|
+
_init_engine(:REQ, linger: linger, backend: backend)
|
|
10
10
|
_attach(endpoints, default: :connect)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
class REP < Socket
|
|
15
|
-
include
|
|
16
|
-
include
|
|
15
|
+
include Readable
|
|
16
|
+
include Writable
|
|
17
17
|
|
|
18
|
-
def initialize(endpoints = nil, linger: 0)
|
|
19
|
-
_init_engine(:REP, linger: linger)
|
|
18
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
19
|
+
_init_engine(:REP, linger: linger, backend: backend)
|
|
20
20
|
_attach(endpoints, default: :bind)
|
|
21
21
|
end
|
|
22
22
|
end
|
data/lib/omq/router_dealer.rb
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
4
|
class DEALER < Socket
|
|
5
|
-
include
|
|
6
|
-
include
|
|
5
|
+
include Readable
|
|
6
|
+
include Writable
|
|
7
7
|
|
|
8
|
-
def initialize(endpoints = nil, linger: 0)
|
|
9
|
-
_init_engine(:DEALER, linger: linger)
|
|
8
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
9
|
+
_init_engine(:DEALER, linger: linger, backend: backend)
|
|
10
10
|
_attach(endpoints, default: :connect)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -14,11 +14,11 @@ module OMQ
|
|
|
14
14
|
# ROUTER socket.
|
|
15
15
|
#
|
|
16
16
|
class ROUTER < Socket
|
|
17
|
-
include
|
|
18
|
-
include
|
|
17
|
+
include Readable
|
|
18
|
+
include Writable
|
|
19
19
|
|
|
20
|
-
def initialize(endpoints = nil, linger: 0)
|
|
21
|
-
_init_engine(:ROUTER, linger: linger)
|
|
20
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
21
|
+
_init_engine(:ROUTER, linger: linger, backend: backend)
|
|
22
22
|
_attach(endpoints, default: :bind)
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# CHANNEL socket routing: exclusive 1-to-1 bidirectional.
|
|
6
|
+
#
|
|
7
|
+
class Channel
|
|
8
|
+
|
|
9
|
+
# @param engine [Engine]
|
|
10
|
+
#
|
|
11
|
+
def initialize(engine)
|
|
12
|
+
@engine = engine
|
|
13
|
+
@connection = nil
|
|
14
|
+
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
15
|
+
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
16
|
+
@tasks = []
|
|
17
|
+
@send_pump_idle = true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Async::LimitedQueue]
|
|
21
|
+
#
|
|
22
|
+
attr_reader :recv_queue, :send_queue
|
|
23
|
+
|
|
24
|
+
# @param connection [Connection]
|
|
25
|
+
# @raise [RuntimeError] if a connection already exists
|
|
26
|
+
#
|
|
27
|
+
def connection_added(connection)
|
|
28
|
+
raise "CHANNEL allows only one peer" if @connection
|
|
29
|
+
@connection = connection
|
|
30
|
+
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
31
|
+
@tasks << task if task
|
|
32
|
+
start_send_pump(connection) unless connection.is_a?(Transport::Inproc::DirectPipe)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param connection [Connection]
|
|
36
|
+
#
|
|
37
|
+
def connection_removed(connection)
|
|
38
|
+
if @connection == connection
|
|
39
|
+
@connection = nil
|
|
40
|
+
@send_pump&.stop
|
|
41
|
+
@send_pump = nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param parts [Array<String>]
|
|
46
|
+
#
|
|
47
|
+
def enqueue(parts)
|
|
48
|
+
conn = @connection
|
|
49
|
+
if conn.is_a?(Transport::Inproc::DirectPipe) && conn.direct_recv_queue
|
|
50
|
+
conn.send_message(parts)
|
|
51
|
+
else
|
|
52
|
+
@send_queue.enqueue(parts)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
def stop
|
|
58
|
+
@tasks.each(&:stop)
|
|
59
|
+
@tasks.clear
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def send_pump_idle? = @send_pump_idle
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def start_send_pump(conn)
|
|
67
|
+
@send_pump = @engine.spawn_pump_task(annotation: "send pump") do
|
|
68
|
+
loop do
|
|
69
|
+
@send_pump_idle = true
|
|
70
|
+
batch = [@send_queue.dequeue]
|
|
71
|
+
@send_pump_idle = false
|
|
72
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
73
|
+
batch.each { |parts| conn.write_message(parts) }
|
|
74
|
+
conn.flush
|
|
75
|
+
end
|
|
76
|
+
rescue *CONNECTION_LOST
|
|
77
|
+
@engine.connection_lost(conn)
|
|
78
|
+
end
|
|
79
|
+
@tasks << @send_pump
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# CLIENT socket routing: round-robin send, fair-queue receive.
|
|
6
|
+
#
|
|
7
|
+
# Same as DEALER — no envelope manipulation.
|
|
8
|
+
#
|
|
9
|
+
class Client
|
|
10
|
+
include RoundRobin
|
|
11
|
+
|
|
12
|
+
# @param engine [Engine]
|
|
13
|
+
#
|
|
14
|
+
def initialize(engine)
|
|
15
|
+
@engine = engine
|
|
16
|
+
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
17
|
+
@tasks = []
|
|
18
|
+
init_round_robin(engine)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Async::LimitedQueue]
|
|
22
|
+
#
|
|
23
|
+
attr_reader :recv_queue, :send_queue
|
|
24
|
+
|
|
25
|
+
# @param connection [Connection]
|
|
26
|
+
#
|
|
27
|
+
def connection_added(connection)
|
|
28
|
+
@connections << connection
|
|
29
|
+
signal_connection_available
|
|
30
|
+
update_direct_pipe
|
|
31
|
+
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
32
|
+
@tasks << task if task
|
|
33
|
+
start_send_pump unless @send_pump_started
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @param connection [Connection]
|
|
37
|
+
#
|
|
38
|
+
def connection_removed(connection)
|
|
39
|
+
@connections.delete(connection)
|
|
40
|
+
update_direct_pipe
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param parts [Array<String>]
|
|
44
|
+
#
|
|
45
|
+
def enqueue(parts)
|
|
46
|
+
enqueue_round_robin(parts)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
#
|
|
50
|
+
def stop
|
|
51
|
+
@tasks.each(&:stop)
|
|
52
|
+
@tasks.clear
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|