omq 0.4.2 → 0.5.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 +24 -0
- data/README.md +6 -1
- data/lib/omq/channel.rb +14 -0
- data/lib/omq/client_server.rb +37 -0
- data/lib/omq/peer.rb +26 -0
- data/lib/omq/radio_dish.rb +74 -0
- data/lib/omq/scatter_gather.rb +23 -0
- data/lib/omq/version.rb +1 -1
- data/lib/omq/zmtp/codec/command.rb +18 -0
- data/lib/omq/zmtp/routing/channel.rb +70 -0
- data/lib/omq/zmtp/routing/client.rb +56 -0
- data/lib/omq/zmtp/routing/dish.rb +80 -0
- data/lib/omq/zmtp/routing/gather.rb +48 -0
- data/lib/omq/zmtp/routing/pair.rb +7 -2
- data/lib/omq/zmtp/routing/peer.rb +77 -0
- data/lib/omq/zmtp/routing/push.rb +16 -0
- data/lib/omq/zmtp/routing/radio.rb +106 -0
- data/lib/omq/zmtp/routing/scatter.rb +68 -0
- data/lib/omq/zmtp/routing/server.rb +77 -0
- data/lib/omq/zmtp/routing.rb +10 -2
- data/lib/omq/zmtp/single_frame.rb +20 -0
- data/lib/omq/zmtp/transport/inproc.rb +1 -1
- data/lib/omq/zmtp/valid_peers.rb +10 -2
- data/lib/omq/zmtp.rb +9 -0
- data/lib/omq.rb +5 -0
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 37d1cbdb4834bd8c3171e5317403066d6cc84f454ab4f3c076655019480634ad
|
|
4
|
+
data.tar.gz: 95e1712a3c993aad845d499c5b2462c42ab6720aa4c7f4134aab05fd4f972ea5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4e8412637f3eb2adce4451866217018e2947ab0e1ff1324664ff9946d658115ab042d3c429811d0d7c269704c54bb7c6ba2902570a8e882a2e5349b9504665c7
|
|
7
|
+
data.tar.gz: fc54cbabda462a9a50a9135f9c7f4da202229a768c74f60fff8cfe56fccd6ad7bdfd68a90e4e4525e091b8d0b71640692997596978b696596a33d572227bdbd3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 — 2026-03-28
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Draft socket types** (RFCs 41, 48, 49, 51, 52):
|
|
8
|
+
- `CLIENT`/`SERVER` — thread-safe REQ/REP without envelope, 4-byte routing IDs
|
|
9
|
+
- `RADIO`/`DISH` — group-based pub/sub with exact match, JOIN/LEAVE commands.
|
|
10
|
+
`radio.publish(group, body)`, `radio.send(body, group:)`, `radio << [group, body]`
|
|
11
|
+
- `SCATTER`/`GATHER` — thread-safe PUSH/PULL
|
|
12
|
+
- `PEER` — bidirectional multi-peer with 4-byte routing IDs
|
|
13
|
+
- `CHANNEL` — thread-safe PAIR
|
|
14
|
+
- All draft types enforce single-frame messages (no multipart)
|
|
15
|
+
- Reconnect-after-restart tests for all 10 socket type pairings
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **PUSH/SCATTER silently wrote to dead peers** — write-only sockets had
|
|
20
|
+
no recv pump to detect peer disconnection. Writes succeeded because the
|
|
21
|
+
kernel send buffer absorbed the data, preventing reconnect from
|
|
22
|
+
triggering. Added background monitor task per connection.
|
|
23
|
+
- **PAIR/CHANNEL stale send pump after reconnect** — old send pump kept
|
|
24
|
+
its captured connection reference and raced with the new send pump,
|
|
25
|
+
sending to the dead connection. Now stopped in `connection_removed`.
|
|
26
|
+
|
|
3
27
|
## 0.4.2 — 2026-03-27
|
|
4
28
|
|
|
5
29
|
### Fixed
|
data/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Pure Ruby implementation of the [ZMTP 3.1](https://rfc.zeromq.org/spec/23/) wire
|
|
|
18
18
|
## Highlights
|
|
19
19
|
|
|
20
20
|
- **Pure Ruby** — no C extensions, no FFI, no libzmq/libczmq dependency
|
|
21
|
-
- **All socket types** — req/rep, pub/sub, push/pull, dealer/router, xpub/xsub, pair
|
|
21
|
+
- **All socket types** — req/rep, pub/sub, push/pull, dealer/router, xpub/xsub, pair + draft types (client/server, radio/dish, scatter/gather, peer, channel)
|
|
22
22
|
- **Async-native** — built on [Async](https://github.com/socketry/async) fibers, also works with plain threads
|
|
23
23
|
- **Ruby-idiomatic API** — messages as `Array<String>`, errors as exceptions, timeouts as `IO::TimeoutError`
|
|
24
24
|
- **All transports** — tcp, ipc, inproc
|
|
@@ -112,6 +112,11 @@ end
|
|
|
112
112
|
| Pipeline | `PUSH`, `PULL` | unidirectional |
|
|
113
113
|
| Routing | `DEALER`, `ROUTER` | bidirectional |
|
|
114
114
|
| Exclusive pair | `PAIR` | bidirectional |
|
|
115
|
+
| Client/Server | `CLIENT`, `SERVER` | bidirectional |
|
|
116
|
+
| Group messaging | `RADIO`, `DISH` | unidirectional |
|
|
117
|
+
| Pipeline (draft) | `SCATTER`, `GATHER` | unidirectional |
|
|
118
|
+
| Peer-to-peer | `PEER` | bidirectional |
|
|
119
|
+
| Channel (draft) | `CHANNEL` | bidirectional |
|
|
115
120
|
|
|
116
121
|
All classes live under `OMQ::`. For the purists, `ØMQ` is an alias:
|
|
117
122
|
|
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 ZMTP::Readable
|
|
6
|
+
include ZMTP::Writable
|
|
7
|
+
include ZMTP::SingleFrame
|
|
8
|
+
|
|
9
|
+
def initialize(endpoints = nil, linger: 0)
|
|
10
|
+
_init_engine(:CHANNEL, linger: linger)
|
|
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 ZMTP::Readable
|
|
6
|
+
include ZMTP::Writable
|
|
7
|
+
include ZMTP::SingleFrame
|
|
8
|
+
|
|
9
|
+
def initialize(endpoints = nil, linger: 0)
|
|
10
|
+
_init_engine(:CLIENT, linger: linger)
|
|
11
|
+
_attach(endpoints, default: :connect)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class SERVER < Socket
|
|
16
|
+
include ZMTP::Readable
|
|
17
|
+
include ZMTP::Writable
|
|
18
|
+
include ZMTP::SingleFrame
|
|
19
|
+
|
|
20
|
+
def initialize(endpoints = nil, linger: 0)
|
|
21
|
+
_init_engine(:SERVER, linger: linger)
|
|
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
|
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 ZMTP::Readable
|
|
6
|
+
include ZMTP::Writable
|
|
7
|
+
include ZMTP::SingleFrame
|
|
8
|
+
|
|
9
|
+
def initialize(endpoints = nil, linger: 0)
|
|
10
|
+
_init_engine(:PEER, linger: linger)
|
|
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 ZMTP::Writable
|
|
6
|
+
|
|
7
|
+
def initialize(endpoints = nil, linger: 0)
|
|
8
|
+
_init_engine(:RADIO, linger: linger)
|
|
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 ZMTP::Readable
|
|
49
|
+
|
|
50
|
+
def initialize(endpoints = nil, linger: 0, group: nil)
|
|
51
|
+
_init_engine(:DISH, linger: linger)
|
|
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,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class SCATTER < Socket
|
|
5
|
+
include ZMTP::Writable
|
|
6
|
+
include ZMTP::SingleFrame
|
|
7
|
+
|
|
8
|
+
def initialize(endpoints = nil, linger: 0, send_hwm: nil, send_timeout: nil)
|
|
9
|
+
_init_engine(:SCATTER, linger: linger, send_hwm: send_hwm, send_timeout: send_timeout)
|
|
10
|
+
_attach(endpoints, default: :connect)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class GATHER < Socket
|
|
15
|
+
include ZMTP::Readable
|
|
16
|
+
include ZMTP::SingleFrame
|
|
17
|
+
|
|
18
|
+
def initialize(endpoints = nil, linger: 0, recv_hwm: nil, recv_timeout: nil)
|
|
19
|
+
_init_engine(:GATHER, linger: linger, recv_hwm: recv_hwm, recv_timeout: recv_timeout)
|
|
20
|
+
_attach(endpoints, default: :bind)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/omq/version.rb
CHANGED
|
@@ -101,6 +101,24 @@ module OMQ
|
|
|
101
101
|
new("CANCEL", prefix.b)
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# Builds a JOIN command (RADIO/DISH group subscription).
|
|
105
|
+
#
|
|
106
|
+
# @param group [String] group name
|
|
107
|
+
# @return [Command]
|
|
108
|
+
#
|
|
109
|
+
def self.join(group)
|
|
110
|
+
new("JOIN", group.b)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Builds a LEAVE command (RADIO/DISH group unsubscription).
|
|
114
|
+
#
|
|
115
|
+
# @param group [String] group name
|
|
116
|
+
# @return [Command]
|
|
117
|
+
#
|
|
118
|
+
def self.leave(group)
|
|
119
|
+
new("LEAVE", group.b)
|
|
120
|
+
end
|
|
121
|
+
|
|
104
122
|
# Builds a PING command.
|
|
105
123
|
#
|
|
106
124
|
# @param ttl [Numeric] time-to-live in seconds (sent as deciseconds)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module ZMTP
|
|
5
|
+
module Routing
|
|
6
|
+
# CHANNEL socket routing: exclusive 1-to-1 bidirectional.
|
|
7
|
+
#
|
|
8
|
+
class Channel
|
|
9
|
+
|
|
10
|
+
# @param engine [Engine]
|
|
11
|
+
#
|
|
12
|
+
def initialize(engine)
|
|
13
|
+
@engine = engine
|
|
14
|
+
@connection = nil
|
|
15
|
+
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
16
|
+
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
17
|
+
@tasks = []
|
|
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)
|
|
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
|
+
@send_queue.enqueue(parts)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
#
|
|
52
|
+
def stop
|
|
53
|
+
@tasks.each(&:stop)
|
|
54
|
+
@tasks.clear
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def start_send_pump(conn)
|
|
60
|
+
@send_pump = Reactor.spawn_pump do
|
|
61
|
+
loop { conn.send_message(@send_queue.dequeue) }
|
|
62
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
63
|
+
@engine.connection_lost(conn)
|
|
64
|
+
end
|
|
65
|
+
@tasks << @send_pump
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module ZMTP
|
|
5
|
+
module Routing
|
|
6
|
+
# CLIENT socket routing: round-robin send, fair-queue receive.
|
|
7
|
+
#
|
|
8
|
+
# Same as DEALER — no envelope manipulation.
|
|
9
|
+
#
|
|
10
|
+
class Client
|
|
11
|
+
include RoundRobin
|
|
12
|
+
|
|
13
|
+
# @param engine [Engine]
|
|
14
|
+
#
|
|
15
|
+
def initialize(engine)
|
|
16
|
+
@engine = engine
|
|
17
|
+
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
18
|
+
@tasks = []
|
|
19
|
+
init_round_robin(engine)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [Async::LimitedQueue]
|
|
23
|
+
#
|
|
24
|
+
attr_reader :recv_queue, :send_queue
|
|
25
|
+
|
|
26
|
+
# @param connection [Connection]
|
|
27
|
+
#
|
|
28
|
+
def connection_added(connection)
|
|
29
|
+
@connections << connection
|
|
30
|
+
signal_connection_available
|
|
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
|
+
end
|
|
41
|
+
|
|
42
|
+
# @param parts [Array<String>]
|
|
43
|
+
#
|
|
44
|
+
def enqueue(parts)
|
|
45
|
+
@send_queue.enqueue(parts)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
#
|
|
49
|
+
def stop
|
|
50
|
+
@tasks.each(&:stop)
|
|
51
|
+
@tasks.clear
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module ZMTP
|
|
5
|
+
module Routing
|
|
6
|
+
# DISH socket routing: group-based receive from RADIO peers.
|
|
7
|
+
#
|
|
8
|
+
# Sends JOIN/LEAVE commands to connected RADIO peers.
|
|
9
|
+
# Receives two-frame messages (group + body) from RADIO.
|
|
10
|
+
#
|
|
11
|
+
class Dish
|
|
12
|
+
|
|
13
|
+
# @param engine [Engine]
|
|
14
|
+
#
|
|
15
|
+
def initialize(engine)
|
|
16
|
+
@engine = engine
|
|
17
|
+
@connections = []
|
|
18
|
+
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
19
|
+
@groups = Set.new
|
|
20
|
+
@tasks = []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Async::LimitedQueue]
|
|
24
|
+
#
|
|
25
|
+
attr_reader :recv_queue
|
|
26
|
+
|
|
27
|
+
# @param connection [Connection]
|
|
28
|
+
#
|
|
29
|
+
def connection_added(connection)
|
|
30
|
+
@connections << connection
|
|
31
|
+
# Send existing group memberships to new peer
|
|
32
|
+
@groups.each do |group|
|
|
33
|
+
connection.send_command(Codec::Command.join(group))
|
|
34
|
+
end
|
|
35
|
+
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
36
|
+
@tasks << task if task
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @param connection [Connection]
|
|
40
|
+
#
|
|
41
|
+
def connection_removed(connection)
|
|
42
|
+
@connections.delete(connection)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# DISH is read-only.
|
|
46
|
+
#
|
|
47
|
+
def enqueue(_parts)
|
|
48
|
+
raise "DISH sockets cannot send"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Joins a group.
|
|
52
|
+
#
|
|
53
|
+
# @param group [String]
|
|
54
|
+
#
|
|
55
|
+
def join(group)
|
|
56
|
+
@groups << group
|
|
57
|
+
@connections.each do |conn|
|
|
58
|
+
conn.send_command(Codec::Command.join(group))
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Leaves a group.
|
|
63
|
+
#
|
|
64
|
+
# @param group [String]
|
|
65
|
+
#
|
|
66
|
+
def leave(group)
|
|
67
|
+
@groups.delete(group)
|
|
68
|
+
@connections.each do |conn|
|
|
69
|
+
conn.send_command(Codec::Command.leave(group))
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def stop
|
|
74
|
+
@tasks.each(&:stop)
|
|
75
|
+
@tasks.clear
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module ZMTP
|
|
5
|
+
module Routing
|
|
6
|
+
# GATHER socket routing: fair-queue receive from SCATTER peers.
|
|
7
|
+
#
|
|
8
|
+
class Gather
|
|
9
|
+
# @param engine [Engine]
|
|
10
|
+
#
|
|
11
|
+
def initialize(engine)
|
|
12
|
+
@engine = engine
|
|
13
|
+
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
14
|
+
@tasks = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [Async::LimitedQueue]
|
|
18
|
+
#
|
|
19
|
+
attr_reader :recv_queue
|
|
20
|
+
|
|
21
|
+
# @param connection [Connection]
|
|
22
|
+
#
|
|
23
|
+
def connection_added(connection)
|
|
24
|
+
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
25
|
+
@tasks << task if task
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @param connection [Connection]
|
|
29
|
+
#
|
|
30
|
+
def connection_removed(connection)
|
|
31
|
+
# recv pump stops on CONNECTION_LOST
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# GATHER is read-only.
|
|
35
|
+
#
|
|
36
|
+
def enqueue(_parts)
|
|
37
|
+
raise "GATHER sockets cannot send"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
def stop
|
|
42
|
+
@tasks.each(&:stop)
|
|
43
|
+
@tasks.clear
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -38,7 +38,11 @@ module OMQ
|
|
|
38
38
|
# @param connection [Connection]
|
|
39
39
|
#
|
|
40
40
|
def connection_removed(connection)
|
|
41
|
-
|
|
41
|
+
if @connection == connection
|
|
42
|
+
@connection = nil
|
|
43
|
+
@send_pump&.stop
|
|
44
|
+
@send_pump = nil
|
|
45
|
+
end
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
# @param parts [Array<String>]
|
|
@@ -56,11 +60,12 @@ module OMQ
|
|
|
56
60
|
private
|
|
57
61
|
|
|
58
62
|
def start_send_pump(conn)
|
|
59
|
-
@
|
|
63
|
+
@send_pump = Reactor.spawn_pump do
|
|
60
64
|
loop { conn.send_message(@send_queue.dequeue) }
|
|
61
65
|
rescue *ZMTP::CONNECTION_LOST
|
|
62
66
|
@engine.connection_lost(conn)
|
|
63
67
|
end
|
|
68
|
+
@tasks << @send_pump
|
|
64
69
|
end
|
|
65
70
|
end
|
|
66
71
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module OMQ
|
|
6
|
+
module ZMTP
|
|
7
|
+
module Routing
|
|
8
|
+
# PEER socket routing: bidirectional multi-peer with auto-generated
|
|
9
|
+
# 4-byte routing IDs.
|
|
10
|
+
#
|
|
11
|
+
# Prepends routing ID on receive. Strips routing ID on send and
|
|
12
|
+
# routes to the identified connection.
|
|
13
|
+
#
|
|
14
|
+
class Peer
|
|
15
|
+
# @param engine [Engine]
|
|
16
|
+
#
|
|
17
|
+
def initialize(engine)
|
|
18
|
+
@engine = engine
|
|
19
|
+
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
20
|
+
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
21
|
+
@connections_by_routing_id = {}
|
|
22
|
+
@tasks = []
|
|
23
|
+
@send_pump_started = false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Async::LimitedQueue]
|
|
27
|
+
#
|
|
28
|
+
attr_reader :recv_queue, :send_queue
|
|
29
|
+
|
|
30
|
+
# @param connection [Connection]
|
|
31
|
+
#
|
|
32
|
+
def connection_added(connection)
|
|
33
|
+
routing_id = SecureRandom.bytes(4)
|
|
34
|
+
@connections_by_routing_id[routing_id] = connection
|
|
35
|
+
|
|
36
|
+
task = @engine.start_recv_pump(connection, @recv_queue,
|
|
37
|
+
transform: ->(msg) { [routing_id, *msg] })
|
|
38
|
+
@tasks << task if task
|
|
39
|
+
|
|
40
|
+
start_send_pump unless @send_pump_started
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param connection [Connection]
|
|
44
|
+
#
|
|
45
|
+
def connection_removed(connection)
|
|
46
|
+
@connections_by_routing_id.reject! { |_, c| c == connection }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param parts [Array<String>]
|
|
50
|
+
#
|
|
51
|
+
def enqueue(parts)
|
|
52
|
+
@send_queue.enqueue(parts)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def stop
|
|
56
|
+
@tasks.each(&:stop)
|
|
57
|
+
@tasks.clear
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def start_send_pump
|
|
63
|
+
@send_pump_started = true
|
|
64
|
+
@tasks << Reactor.spawn_pump do
|
|
65
|
+
loop do
|
|
66
|
+
parts = @send_queue.dequeue
|
|
67
|
+
routing_id = parts.first
|
|
68
|
+
conn = @connections_by_routing_id[routing_id]
|
|
69
|
+
next unless conn # silently drop if peer gone
|
|
70
|
+
conn.send_message(parts[1..])
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -32,6 +32,7 @@ module OMQ
|
|
|
32
32
|
@connections << connection
|
|
33
33
|
signal_connection_available
|
|
34
34
|
start_send_pump unless @send_pump_started
|
|
35
|
+
start_monitor(connection)
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
# @param connection [Connection]
|
|
@@ -51,6 +52,21 @@ module OMQ
|
|
|
51
52
|
@tasks.each(&:stop)
|
|
52
53
|
@tasks.clear
|
|
53
54
|
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# Monitors a connection for disconnection.
|
|
59
|
+
# Write-only sockets have no recv pump, so without this monitor
|
|
60
|
+
# a dead peer is only detected on the next send — which may
|
|
61
|
+
# succeed if the kernel send buffer absorbs the data.
|
|
62
|
+
#
|
|
63
|
+
def start_monitor(conn)
|
|
64
|
+
@tasks << Reactor.spawn_pump do
|
|
65
|
+
conn.receive_message # blocks until peer disconnects
|
|
66
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
67
|
+
@engine.connection_lost(conn)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
54
70
|
end
|
|
55
71
|
end
|
|
56
72
|
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module ZMTP
|
|
5
|
+
module Routing
|
|
6
|
+
# RADIO socket routing: group-based fan-out to DISH peers.
|
|
7
|
+
#
|
|
8
|
+
# Like PUB/FanOut but with exact group matching and JOIN/LEAVE
|
|
9
|
+
# commands instead of SUBSCRIBE/CANCEL.
|
|
10
|
+
#
|
|
11
|
+
# Messages are sent as two frames on the wire:
|
|
12
|
+
# group (MORE=1) + body (MORE=0)
|
|
13
|
+
#
|
|
14
|
+
class Radio
|
|
15
|
+
|
|
16
|
+
# @param engine [Engine]
|
|
17
|
+
#
|
|
18
|
+
def initialize(engine)
|
|
19
|
+
@engine = engine
|
|
20
|
+
@connections = []
|
|
21
|
+
@groups = {} # connection => Set of joined groups
|
|
22
|
+
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
23
|
+
@send_pump_started = false
|
|
24
|
+
@tasks = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Async::LimitedQueue]
|
|
28
|
+
#
|
|
29
|
+
attr_reader :send_queue
|
|
30
|
+
|
|
31
|
+
# RADIO is write-only.
|
|
32
|
+
#
|
|
33
|
+
def recv_queue
|
|
34
|
+
raise "RADIO sockets cannot receive"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @param connection [Connection]
|
|
38
|
+
#
|
|
39
|
+
def connection_added(connection)
|
|
40
|
+
@connections << connection
|
|
41
|
+
@groups[connection] = Set.new
|
|
42
|
+
start_group_listener(connection)
|
|
43
|
+
start_send_pump unless @send_pump_started
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param connection [Connection]
|
|
47
|
+
#
|
|
48
|
+
def connection_removed(connection)
|
|
49
|
+
@connections.delete(connection)
|
|
50
|
+
@groups.delete(connection)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Enqueues a message for sending.
|
|
54
|
+
#
|
|
55
|
+
# @param parts [Array<String>] [group, body]
|
|
56
|
+
#
|
|
57
|
+
def enqueue(parts)
|
|
58
|
+
@send_queue.enqueue(parts)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def stop
|
|
62
|
+
@tasks.each(&:stop)
|
|
63
|
+
@tasks.clear
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def start_send_pump
|
|
69
|
+
@send_pump_started = true
|
|
70
|
+
@tasks << Reactor.spawn_pump do
|
|
71
|
+
loop do
|
|
72
|
+
parts = @send_queue.dequeue
|
|
73
|
+
group = parts[0]
|
|
74
|
+
body = parts[1] || "".b
|
|
75
|
+
@connections.each do |conn|
|
|
76
|
+
next unless @groups[conn]&.include?(group)
|
|
77
|
+
begin
|
|
78
|
+
# Wire format: group frame (MORE) + body frame
|
|
79
|
+
conn.send_message([group, body])
|
|
80
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
81
|
+
# connection dead — will be cleaned up
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def start_group_listener(conn)
|
|
89
|
+
@tasks << Reactor.spawn_pump do
|
|
90
|
+
loop do
|
|
91
|
+
frame = conn.read_frame
|
|
92
|
+
next unless frame.command?
|
|
93
|
+
cmd = Codec::Command.from_body(frame.body)
|
|
94
|
+
case cmd.name
|
|
95
|
+
when "JOIN" then @groups[conn]&.add(cmd.data)
|
|
96
|
+
when "LEAVE" then @groups[conn]&.delete(cmd.data)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
100
|
+
@engine.connection_lost(conn)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module ZMTP
|
|
5
|
+
module Routing
|
|
6
|
+
# SCATTER socket routing: round-robin send to GATHER peers.
|
|
7
|
+
#
|
|
8
|
+
class Scatter
|
|
9
|
+
include RoundRobin
|
|
10
|
+
|
|
11
|
+
# @param engine [Engine]
|
|
12
|
+
#
|
|
13
|
+
def initialize(engine)
|
|
14
|
+
@engine = engine
|
|
15
|
+
@tasks = []
|
|
16
|
+
init_round_robin(engine)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Async::LimitedQueue]
|
|
20
|
+
#
|
|
21
|
+
attr_reader :send_queue
|
|
22
|
+
|
|
23
|
+
# SCATTER is write-only.
|
|
24
|
+
#
|
|
25
|
+
def recv_queue
|
|
26
|
+
raise "SCATTER sockets cannot receive"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param connection [Connection]
|
|
30
|
+
#
|
|
31
|
+
def connection_added(connection)
|
|
32
|
+
@connections << connection
|
|
33
|
+
signal_connection_available
|
|
34
|
+
start_send_pump unless @send_pump_started
|
|
35
|
+
start_monitor(connection)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param connection [Connection]
|
|
39
|
+
#
|
|
40
|
+
def connection_removed(connection)
|
|
41
|
+
@connections.delete(connection)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @param parts [Array<String>]
|
|
45
|
+
#
|
|
46
|
+
def enqueue(parts)
|
|
47
|
+
@send_queue.enqueue(parts)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
def stop
|
|
52
|
+
@tasks.each(&:stop)
|
|
53
|
+
@tasks.clear
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def start_monitor(conn)
|
|
59
|
+
@tasks << Reactor.spawn_pump do
|
|
60
|
+
conn.receive_message
|
|
61
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
62
|
+
@engine.connection_lost(conn)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module OMQ
|
|
6
|
+
module ZMTP
|
|
7
|
+
module Routing
|
|
8
|
+
# SERVER socket routing: identity-based routing with auto-generated
|
|
9
|
+
# 4-byte routing IDs.
|
|
10
|
+
#
|
|
11
|
+
# Prepends routing ID on receive. Strips routing ID on send and
|
|
12
|
+
# routes to the identified connection.
|
|
13
|
+
#
|
|
14
|
+
class Server
|
|
15
|
+
# @param engine [Engine]
|
|
16
|
+
#
|
|
17
|
+
def initialize(engine)
|
|
18
|
+
@engine = engine
|
|
19
|
+
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
20
|
+
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
21
|
+
@connections_by_routing_id = {}
|
|
22
|
+
@tasks = []
|
|
23
|
+
@send_pump_started = false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Async::LimitedQueue]
|
|
27
|
+
#
|
|
28
|
+
attr_reader :recv_queue, :send_queue
|
|
29
|
+
|
|
30
|
+
# @param connection [Connection]
|
|
31
|
+
#
|
|
32
|
+
def connection_added(connection)
|
|
33
|
+
routing_id = SecureRandom.bytes(4)
|
|
34
|
+
@connections_by_routing_id[routing_id] = connection
|
|
35
|
+
|
|
36
|
+
task = @engine.start_recv_pump(connection, @recv_queue,
|
|
37
|
+
transform: ->(msg) { [routing_id, *msg] })
|
|
38
|
+
@tasks << task if task
|
|
39
|
+
|
|
40
|
+
start_send_pump unless @send_pump_started
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param connection [Connection]
|
|
44
|
+
#
|
|
45
|
+
def connection_removed(connection)
|
|
46
|
+
@connections_by_routing_id.reject! { |_, c| c == connection }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param parts [Array<String>]
|
|
50
|
+
#
|
|
51
|
+
def enqueue(parts)
|
|
52
|
+
@send_queue.enqueue(parts)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def stop
|
|
56
|
+
@tasks.each(&:stop)
|
|
57
|
+
@tasks.clear
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def start_send_pump
|
|
63
|
+
@send_pump_started = true
|
|
64
|
+
@tasks << Reactor.spawn_pump do
|
|
65
|
+
loop do
|
|
66
|
+
parts = @send_queue.dequeue
|
|
67
|
+
routing_id = parts.first
|
|
68
|
+
conn = @connections_by_routing_id[routing_id]
|
|
69
|
+
next unless conn # silently drop if peer gone
|
|
70
|
+
conn.send_message(parts[1..])
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/omq/zmtp/routing.rb
CHANGED
|
@@ -28,8 +28,16 @@ module OMQ
|
|
|
28
28
|
when :SUB then Sub
|
|
29
29
|
when :XPUB then XPub
|
|
30
30
|
when :XSUB then XSub
|
|
31
|
-
when :PUSH
|
|
32
|
-
when :PULL
|
|
31
|
+
when :PUSH then Push
|
|
32
|
+
when :PULL then Pull
|
|
33
|
+
when :CLIENT then Client
|
|
34
|
+
when :SERVER then Server
|
|
35
|
+
when :RADIO then Radio
|
|
36
|
+
when :DISH then Dish
|
|
37
|
+
when :SCATTER then Scatter
|
|
38
|
+
when :GATHER then Gather
|
|
39
|
+
when :PEER then Peer
|
|
40
|
+
when :CHANNEL then Channel
|
|
33
41
|
else raise ArgumentError, "unknown socket type: #{socket_type}"
|
|
34
42
|
end
|
|
35
43
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module ZMTP
|
|
5
|
+
# Mixin that rejects multipart messages.
|
|
6
|
+
#
|
|
7
|
+
# All draft socket types (CLIENT, SERVER, RADIO, DISH, SCATTER,
|
|
8
|
+
# GATHER, PEER, CHANNEL) require single-frame messages for
|
|
9
|
+
# thread-safe atomic operations.
|
|
10
|
+
#
|
|
11
|
+
module SingleFrame
|
|
12
|
+
def send(message)
|
|
13
|
+
if message.is_a?(Array) && message.size > 1
|
|
14
|
+
raise ArgumentError, "#{self.class} does not support multipart messages"
|
|
15
|
+
end
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -17,7 +17,7 @@ module OMQ
|
|
|
17
17
|
module Inproc
|
|
18
18
|
# Socket types that exchange commands (SUBSCRIBE/CANCEL) over inproc.
|
|
19
19
|
#
|
|
20
|
-
COMMAND_TYPES = %i[PUB SUB XPUB XSUB].freeze
|
|
20
|
+
COMMAND_TYPES = %i[PUB SUB XPUB XSUB RADIO DISH].freeze
|
|
21
21
|
|
|
22
22
|
# Global registry of bound inproc endpoints.
|
|
23
23
|
#
|
data/lib/omq/zmtp/valid_peers.rb
CHANGED
|
@@ -14,8 +14,16 @@ module OMQ
|
|
|
14
14
|
SUB: %i[PUB XPUB].freeze,
|
|
15
15
|
XPUB: %i[SUB XSUB].freeze,
|
|
16
16
|
XSUB: %i[PUB XPUB].freeze,
|
|
17
|
-
PUSH:
|
|
18
|
-
PULL:
|
|
17
|
+
PUSH: %i[PULL].freeze,
|
|
18
|
+
PULL: %i[PUSH].freeze,
|
|
19
|
+
CLIENT: %i[SERVER].freeze,
|
|
20
|
+
SERVER: %i[CLIENT].freeze,
|
|
21
|
+
RADIO: %i[DISH].freeze,
|
|
22
|
+
DISH: %i[RADIO].freeze,
|
|
23
|
+
SCATTER: %i[GATHER].freeze,
|
|
24
|
+
GATHER: %i[SCATTER].freeze,
|
|
25
|
+
PEER: %i[PEER].freeze,
|
|
26
|
+
CHANNEL: %i[CHANNEL].freeze,
|
|
19
27
|
}.freeze
|
|
20
28
|
end
|
|
21
29
|
end
|
data/lib/omq/zmtp.rb
CHANGED
|
@@ -63,6 +63,15 @@ require_relative "zmtp/routing/xpub"
|
|
|
63
63
|
require_relative "zmtp/routing/xsub"
|
|
64
64
|
require_relative "zmtp/routing/push"
|
|
65
65
|
require_relative "zmtp/routing/pull"
|
|
66
|
+
require_relative "zmtp/routing/scatter"
|
|
67
|
+
require_relative "zmtp/routing/gather"
|
|
68
|
+
require_relative "zmtp/routing/channel"
|
|
69
|
+
require_relative "zmtp/routing/client"
|
|
70
|
+
require_relative "zmtp/routing/server"
|
|
71
|
+
require_relative "zmtp/routing/radio"
|
|
72
|
+
require_relative "zmtp/routing/dish"
|
|
73
|
+
require_relative "zmtp/routing/peer"
|
|
74
|
+
require_relative "zmtp/single_frame"
|
|
66
75
|
require_relative "zmtp/engine"
|
|
67
76
|
require_relative "zmtp/readable"
|
|
68
77
|
require_relative "zmtp/writable"
|
data/lib/omq.rb
CHANGED
|
@@ -17,6 +17,11 @@ require_relative "omq/router_dealer"
|
|
|
17
17
|
require_relative "omq/pub_sub"
|
|
18
18
|
require_relative "omq/push_pull"
|
|
19
19
|
require_relative "omq/pair"
|
|
20
|
+
require_relative "omq/scatter_gather"
|
|
21
|
+
require_relative "omq/channel"
|
|
22
|
+
require_relative "omq/client_server"
|
|
23
|
+
require_relative "omq/radio_dish"
|
|
24
|
+
require_relative "omq/peer"
|
|
20
25
|
|
|
21
26
|
# For the purists.
|
|
22
27
|
ØMQ = OMQ
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omq
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -51,11 +51,16 @@ files:
|
|
|
51
51
|
- README.md
|
|
52
52
|
- exe/omqcat
|
|
53
53
|
- lib/omq.rb
|
|
54
|
+
- lib/omq/channel.rb
|
|
55
|
+
- lib/omq/client_server.rb
|
|
54
56
|
- lib/omq/pair.rb
|
|
57
|
+
- lib/omq/peer.rb
|
|
55
58
|
- lib/omq/pub_sub.rb
|
|
56
59
|
- lib/omq/push_pull.rb
|
|
60
|
+
- lib/omq/radio_dish.rb
|
|
57
61
|
- lib/omq/req_rep.rb
|
|
58
62
|
- lib/omq/router_dealer.rb
|
|
63
|
+
- lib/omq/scatter_gather.rb
|
|
59
64
|
- lib/omq/socket.rb
|
|
60
65
|
- lib/omq/version.rb
|
|
61
66
|
- lib/omq/zmtp.rb
|
|
@@ -70,19 +75,28 @@ files:
|
|
|
70
75
|
- lib/omq/zmtp/reactor.rb
|
|
71
76
|
- lib/omq/zmtp/readable.rb
|
|
72
77
|
- lib/omq/zmtp/routing.rb
|
|
78
|
+
- lib/omq/zmtp/routing/channel.rb
|
|
79
|
+
- lib/omq/zmtp/routing/client.rb
|
|
73
80
|
- lib/omq/zmtp/routing/dealer.rb
|
|
81
|
+
- lib/omq/zmtp/routing/dish.rb
|
|
74
82
|
- lib/omq/zmtp/routing/fan_out.rb
|
|
83
|
+
- lib/omq/zmtp/routing/gather.rb
|
|
75
84
|
- lib/omq/zmtp/routing/pair.rb
|
|
85
|
+
- lib/omq/zmtp/routing/peer.rb
|
|
76
86
|
- lib/omq/zmtp/routing/pub.rb
|
|
77
87
|
- lib/omq/zmtp/routing/pull.rb
|
|
78
88
|
- lib/omq/zmtp/routing/push.rb
|
|
89
|
+
- lib/omq/zmtp/routing/radio.rb
|
|
79
90
|
- lib/omq/zmtp/routing/rep.rb
|
|
80
91
|
- lib/omq/zmtp/routing/req.rb
|
|
81
92
|
- lib/omq/zmtp/routing/round_robin.rb
|
|
82
93
|
- lib/omq/zmtp/routing/router.rb
|
|
94
|
+
- lib/omq/zmtp/routing/scatter.rb
|
|
95
|
+
- lib/omq/zmtp/routing/server.rb
|
|
83
96
|
- lib/omq/zmtp/routing/sub.rb
|
|
84
97
|
- lib/omq/zmtp/routing/xpub.rb
|
|
85
98
|
- lib/omq/zmtp/routing/xsub.rb
|
|
99
|
+
- lib/omq/zmtp/single_frame.rb
|
|
86
100
|
- lib/omq/zmtp/transport/inproc.rb
|
|
87
101
|
- lib/omq/zmtp/transport/ipc.rb
|
|
88
102
|
- lib/omq/zmtp/transport/tcp.rb
|