omq-draft 0.1.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 +7 -0
- data/LICENSE +15 -0
- data/README.md +58 -0
- data/lib/omq/channel.rb +14 -0
- data/lib/omq/client_server.rb +37 -0
- data/lib/omq/draft/channel.rb +17 -0
- data/lib/omq/draft/clientserver.rb +19 -0
- data/lib/omq/draft/p2p.rb +17 -0
- data/lib/omq/draft/radiodish.rb +23 -0
- data/lib/omq/draft/scattergather.rb +19 -0
- data/lib/omq/draft/version.rb +7 -0
- data/lib/omq/peer.rb +26 -0
- data/lib/omq/radio_dish.rb +74 -0
- data/lib/omq/routing/channel.rb +83 -0
- data/lib/omq/routing/client.rb +56 -0
- data/lib/omq/routing/dish.rb +77 -0
- data/lib/omq/routing/gather.rb +46 -0
- data/lib/omq/routing/peer.rb +101 -0
- data/lib/omq/routing/radio.rb +167 -0
- data/lib/omq/routing/scatter.rb +84 -0
- data/lib/omq/routing/server.rb +101 -0
- data/lib/omq/scatter_gather.rb +23 -0
- data/lib/omq/single_frame.rb +18 -0
- data/lib/omq/transport/udp.rb +214 -0
- metadata +80 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 227c70413e95a09337fe74eff2c92393a218456aa16c75cb45bd2312cedf08af
|
|
4
|
+
data.tar.gz: 14345eedca4ea644f478fb3c2538a06f93eb8bf387f7a9f3a2664aace0022aad
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 75cea8ecb3cd08f469897a0ceeef2bac1bc6c087b0cf3b5f60b77bc2ce53891338f62fcddb3cf024077565b30383badfe8b8ecfb1950fb8d9d740a39c4040b7f
|
|
7
|
+
data.tar.gz: 5f691e089cdaa0010955948bd722f844768a1e678dce56d5fd75da7b795a6b6fdadd782b84c751647979a68f0b7b1969fff39aad7e2fc9c8dd88bbded1314ff9
|
data/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026, Patrik Wenger
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# omq-draft
|
|
2
|
+
|
|
3
|
+
Draft ZMQ socket types for [OMQ](https://github.com/zeromq/omq) — the pure Ruby ZeroMQ library.
|
|
4
|
+
|
|
5
|
+
Implements the ZeroMQ draft RFCs as opt-in, separately loadable modules:
|
|
6
|
+
|
|
7
|
+
| Require | Socket types | Transport |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `omq/draft/radiodish` | `OMQ::RADIO`, `OMQ::DISH` | TCP, IPC, inproc, **UDP** |
|
|
10
|
+
| `omq/draft/clientserver` | `OMQ::CLIENT`, `OMQ::SERVER` | TCP, IPC, inproc |
|
|
11
|
+
| `omq/draft/scattergather` | `OMQ::SCATTER`, `OMQ::GATHER` | TCP, IPC, inproc |
|
|
12
|
+
| `omq/draft/channel` | `OMQ::CHANNEL` | TCP, IPC, inproc |
|
|
13
|
+
| `omq/draft/p2p` | `OMQ::PEER` | TCP, IPC, inproc |
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem "omq-draft"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Load only the socket types you need:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
require "omq/draft/radiodish"
|
|
27
|
+
|
|
28
|
+
radio = OMQ::RADIO.connect("udp://127.0.0.1:5555")
|
|
29
|
+
dish = OMQ::DISH.bind("udp://127.0.0.1:5555")
|
|
30
|
+
dish.join("weather")
|
|
31
|
+
|
|
32
|
+
radio.publish("weather", "sunny")
|
|
33
|
+
puts dish.receive.inspect # => ["weather", "sunny"]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
require "omq/draft/clientserver"
|
|
38
|
+
|
|
39
|
+
server = OMQ::SERVER.bind("tcp://127.0.0.1:5555")
|
|
40
|
+
client = OMQ::CLIENT.connect("tcp://127.0.0.1:5555")
|
|
41
|
+
|
|
42
|
+
client.send("hello")
|
|
43
|
+
routing_id, body = server.receive
|
|
44
|
+
server.send_to(routing_id, "world")
|
|
45
|
+
puts client.receive.inspect # => ["world"]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Development
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
export OMQ_DEV=1 # use ../omq from source
|
|
52
|
+
bundle install
|
|
53
|
+
bundle exec rake
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
ISC
|
data/lib/omq/channel.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class CHANNEL < Socket
|
|
5
|
+
include Readable
|
|
6
|
+
include Writable
|
|
7
|
+
include SingleFrame
|
|
8
|
+
|
|
9
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
10
|
+
_init_engine(:CHANNEL, linger: linger, backend: backend)
|
|
11
|
+
_attach(endpoints, default: :connect)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class CLIENT < Socket
|
|
5
|
+
include Readable
|
|
6
|
+
include Writable
|
|
7
|
+
include SingleFrame
|
|
8
|
+
|
|
9
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
10
|
+
_init_engine(:CLIENT, linger: linger, backend: backend)
|
|
11
|
+
_attach(endpoints, default: :connect)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class SERVER < Socket
|
|
16
|
+
include Readable
|
|
17
|
+
include Writable
|
|
18
|
+
include SingleFrame
|
|
19
|
+
|
|
20
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
21
|
+
_init_engine(:SERVER, linger: linger, backend: backend)
|
|
22
|
+
_attach(endpoints, default: :bind)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Sends a message to a specific peer by routing ID.
|
|
26
|
+
#
|
|
27
|
+
# @param routing_id [String] 4-byte routing ID
|
|
28
|
+
# @param message [String] message body
|
|
29
|
+
# @return [self]
|
|
30
|
+
#
|
|
31
|
+
def send_to(routing_id, message)
|
|
32
|
+
parts = [routing_id.b.freeze, message.b.freeze]
|
|
33
|
+
with_timeout(@options.write_timeout) { @engine.enqueue_send(parts) }
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# OMQ draft CHANNEL socket type.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# require "omq/draft/channel"
|
|
7
|
+
#
|
|
8
|
+
# a = OMQ::CHANNEL.bind("tcp://127.0.0.1:5555")
|
|
9
|
+
# b = OMQ::CHANNEL.connect("tcp://127.0.0.1:5555")
|
|
10
|
+
|
|
11
|
+
require "omq"
|
|
12
|
+
|
|
13
|
+
require_relative "../single_frame"
|
|
14
|
+
require_relative "../routing/channel"
|
|
15
|
+
require_relative "../channel"
|
|
16
|
+
|
|
17
|
+
OMQ::Routing.register(:CHANNEL, OMQ::Routing::Channel)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# OMQ draft CLIENT/SERVER socket types.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# require "omq/draft/clientserver"
|
|
7
|
+
#
|
|
8
|
+
# server = OMQ::SERVER.bind("tcp://127.0.0.1:5555")
|
|
9
|
+
# client = OMQ::CLIENT.connect("tcp://127.0.0.1:5555")
|
|
10
|
+
|
|
11
|
+
require "omq"
|
|
12
|
+
|
|
13
|
+
require_relative "../single_frame"
|
|
14
|
+
require_relative "../routing/client"
|
|
15
|
+
require_relative "../routing/server"
|
|
16
|
+
require_relative "../client_server"
|
|
17
|
+
|
|
18
|
+
OMQ::Routing.register(:CLIENT, OMQ::Routing::Client)
|
|
19
|
+
OMQ::Routing.register(:SERVER, OMQ::Routing::Server)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# OMQ draft PEER socket type.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# require "omq/draft/p2p"
|
|
7
|
+
#
|
|
8
|
+
# a = OMQ::PEER.bind("tcp://127.0.0.1:5555")
|
|
9
|
+
# b = OMQ::PEER.connect("tcp://127.0.0.1:5555")
|
|
10
|
+
|
|
11
|
+
require "omq"
|
|
12
|
+
|
|
13
|
+
require_relative "../single_frame"
|
|
14
|
+
require_relative "../routing/peer"
|
|
15
|
+
require_relative "../peer"
|
|
16
|
+
|
|
17
|
+
OMQ::Routing.register(:PEER, OMQ::Routing::Peer)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# OMQ draft RADIO/DISH socket types with UDP transport.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# require "omq/draft/radiodish"
|
|
7
|
+
#
|
|
8
|
+
# radio = OMQ::RADIO.connect("udp://127.0.0.1:5555")
|
|
9
|
+
# dish = OMQ::DISH.bind("udp://127.0.0.1:5555")
|
|
10
|
+
# dish.join("weather")
|
|
11
|
+
# radio.publish("weather", "sunny")
|
|
12
|
+
|
|
13
|
+
require "omq"
|
|
14
|
+
|
|
15
|
+
require_relative "../single_frame"
|
|
16
|
+
require_relative "../routing/radio"
|
|
17
|
+
require_relative "../routing/dish"
|
|
18
|
+
require_relative "../transport/udp"
|
|
19
|
+
require_relative "../radio_dish"
|
|
20
|
+
|
|
21
|
+
OMQ::Routing.register(:RADIO, OMQ::Routing::Radio)
|
|
22
|
+
OMQ::Routing.register(:DISH, OMQ::Routing::Dish)
|
|
23
|
+
OMQ::Engine.transports["udp"] = OMQ::Transport::UDP
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# OMQ draft SCATTER/GATHER socket types.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# require "omq/draft/scattergather"
|
|
7
|
+
#
|
|
8
|
+
# gather = OMQ::GATHER.bind("tcp://127.0.0.1:5555")
|
|
9
|
+
# scatter = OMQ::SCATTER.connect("tcp://127.0.0.1:5555")
|
|
10
|
+
|
|
11
|
+
require "omq"
|
|
12
|
+
|
|
13
|
+
require_relative "../single_frame"
|
|
14
|
+
require_relative "../routing/scatter"
|
|
15
|
+
require_relative "../routing/gather"
|
|
16
|
+
require_relative "../scatter_gather"
|
|
17
|
+
|
|
18
|
+
OMQ::Routing.register(:SCATTER, OMQ::Routing::Scatter)
|
|
19
|
+
OMQ::Routing.register(:GATHER, OMQ::Routing::Gather)
|
data/lib/omq/peer.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class PEER < Socket
|
|
5
|
+
include Readable
|
|
6
|
+
include Writable
|
|
7
|
+
include SingleFrame
|
|
8
|
+
|
|
9
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
10
|
+
_init_engine(:PEER, linger: linger, backend: backend)
|
|
11
|
+
_attach(endpoints, default: :connect)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Sends a message to a specific peer by routing ID.
|
|
15
|
+
#
|
|
16
|
+
# @param routing_id [String] 4-byte routing ID
|
|
17
|
+
# @param message [String] message body
|
|
18
|
+
# @return [self]
|
|
19
|
+
#
|
|
20
|
+
def send_to(routing_id, message)
|
|
21
|
+
parts = [routing_id.b.freeze, message.b.freeze]
|
|
22
|
+
with_timeout(@options.write_timeout) { @engine.enqueue_send(parts) }
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class RADIO < Socket
|
|
5
|
+
include Writable
|
|
6
|
+
|
|
7
|
+
def initialize(endpoints = nil, linger: 0, on_mute: :drop_newest, conflate: false, backend: nil)
|
|
8
|
+
_init_engine(:RADIO, linger: linger, on_mute: on_mute, conflate: conflate, backend: backend)
|
|
9
|
+
_attach(endpoints, default: :bind)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Publishes a message to a group.
|
|
13
|
+
#
|
|
14
|
+
# @param group [String] group name
|
|
15
|
+
# @param body [String] message body
|
|
16
|
+
# @return [self]
|
|
17
|
+
#
|
|
18
|
+
def publish(group, body)
|
|
19
|
+
with_timeout(@options.write_timeout) do
|
|
20
|
+
@engine.enqueue_send([group.b.freeze, body.b.freeze])
|
|
21
|
+
end
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Sends a message to a group.
|
|
26
|
+
#
|
|
27
|
+
# @param message [String] message body (requires group: kwarg)
|
|
28
|
+
# @param group [String] group name
|
|
29
|
+
# @return [self]
|
|
30
|
+
#
|
|
31
|
+
def send(message, group: nil)
|
|
32
|
+
raise ArgumentError, "RADIO requires a group (use group: kwarg, publish, or << [group, body])" unless group
|
|
33
|
+
publish(group, message)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Sends a message to a group via [group, body] array.
|
|
37
|
+
#
|
|
38
|
+
# @param message [Array<String>] [group, body]
|
|
39
|
+
# @return [self]
|
|
40
|
+
#
|
|
41
|
+
def <<(message)
|
|
42
|
+
raise ArgumentError, "RADIO requires [group, body] array" unless message.is_a?(Array) && message.size == 2
|
|
43
|
+
publish(message[0], message[1])
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class DISH < Socket
|
|
48
|
+
include Readable
|
|
49
|
+
|
|
50
|
+
def initialize(endpoints = nil, linger: 0, group: nil, on_mute: :block, backend: nil)
|
|
51
|
+
_init_engine(:DISH, linger: linger, on_mute: on_mute, backend: backend)
|
|
52
|
+
_attach(endpoints, default: :connect)
|
|
53
|
+
join(group) if group
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Joins a group.
|
|
57
|
+
#
|
|
58
|
+
# @param group [String]
|
|
59
|
+
# @return [void]
|
|
60
|
+
#
|
|
61
|
+
def join(group)
|
|
62
|
+
@engine.routing.join(group)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Leaves a group.
|
|
66
|
+
#
|
|
67
|
+
# @param group [String]
|
|
68
|
+
# @return [void]
|
|
69
|
+
#
|
|
70
|
+
def leave(group)
|
|
71
|
+
@engine.routing.leave(group)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -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 = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
15
|
+
@send_queue = Routing.build_queue(engine.options.send_hwm, :block)
|
|
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 = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
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
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# DISH socket routing: group-based receive from RADIO peers.
|
|
6
|
+
#
|
|
7
|
+
# Sends JOIN/LEAVE commands to connected RADIO peers.
|
|
8
|
+
# Receives two-frame messages (group + body) from RADIO.
|
|
9
|
+
#
|
|
10
|
+
class Dish
|
|
11
|
+
|
|
12
|
+
# @param engine [Engine]
|
|
13
|
+
#
|
|
14
|
+
def initialize(engine)
|
|
15
|
+
@engine = engine
|
|
16
|
+
@connections = []
|
|
17
|
+
@recv_queue = Routing.build_queue(engine.options.recv_hwm, engine.options.on_mute)
|
|
18
|
+
@groups = Set.new
|
|
19
|
+
@tasks = []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [Async::LimitedQueue]
|
|
23
|
+
#
|
|
24
|
+
attr_reader :recv_queue
|
|
25
|
+
|
|
26
|
+
# @param connection [Connection]
|
|
27
|
+
#
|
|
28
|
+
def connection_added(connection)
|
|
29
|
+
@connections << connection
|
|
30
|
+
@groups.each do |group|
|
|
31
|
+
connection.send_command(Protocol::ZMTP::Codec::Command.join(group))
|
|
32
|
+
end
|
|
33
|
+
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
34
|
+
@tasks << task if task
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @param connection [Connection]
|
|
38
|
+
#
|
|
39
|
+
def connection_removed(connection)
|
|
40
|
+
@connections.delete(connection)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# DISH is read-only.
|
|
44
|
+
#
|
|
45
|
+
def enqueue(_parts)
|
|
46
|
+
raise "DISH sockets cannot send"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Joins a group.
|
|
50
|
+
#
|
|
51
|
+
# @param group [String]
|
|
52
|
+
#
|
|
53
|
+
def join(group)
|
|
54
|
+
@groups << group
|
|
55
|
+
@connections.each do |conn|
|
|
56
|
+
conn.send_command(Protocol::ZMTP::Codec::Command.join(group))
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Leaves a group.
|
|
61
|
+
#
|
|
62
|
+
# @param group [String]
|
|
63
|
+
#
|
|
64
|
+
def leave(group)
|
|
65
|
+
@groups.delete(group)
|
|
66
|
+
@connections.each do |conn|
|
|
67
|
+
conn.send_command(Protocol::ZMTP::Codec::Command.leave(group))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def stop
|
|
72
|
+
@tasks.each(&:stop)
|
|
73
|
+
@tasks.clear
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# GATHER socket routing: fair-queue receive from SCATTER peers.
|
|
6
|
+
#
|
|
7
|
+
class Gather
|
|
8
|
+
# @param engine [Engine]
|
|
9
|
+
#
|
|
10
|
+
def initialize(engine)
|
|
11
|
+
@engine = engine
|
|
12
|
+
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
13
|
+
@tasks = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @return [Async::LimitedQueue]
|
|
17
|
+
#
|
|
18
|
+
attr_reader :recv_queue
|
|
19
|
+
|
|
20
|
+
# @param connection [Connection]
|
|
21
|
+
#
|
|
22
|
+
def connection_added(connection)
|
|
23
|
+
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
24
|
+
@tasks << task if task
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param connection [Connection]
|
|
28
|
+
#
|
|
29
|
+
def connection_removed(connection)
|
|
30
|
+
# recv pump stops on CONNECTION_LOST
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# GATHER is read-only.
|
|
34
|
+
#
|
|
35
|
+
def enqueue(_parts)
|
|
36
|
+
raise "GATHER sockets cannot send"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#
|
|
40
|
+
def stop
|
|
41
|
+
@tasks.each(&:stop)
|
|
42
|
+
@tasks.clear
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|