omq 0.22.1 → 0.24.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +162 -0
  3. data/README.md +17 -21
  4. data/lib/omq/channel.rb +35 -0
  5. data/lib/omq/client_server.rb +72 -0
  6. data/lib/omq/constants.rb +68 -0
  7. data/lib/omq/engine/connection_lifecycle.rb +22 -8
  8. data/lib/omq/engine/heartbeat.rb +3 -4
  9. data/lib/omq/engine/maintenance.rb +4 -5
  10. data/lib/omq/engine/reconnect.rb +12 -11
  11. data/lib/omq/engine/recv_pump.rb +10 -10
  12. data/lib/omq/engine/socket_lifecycle.rb +26 -9
  13. data/lib/omq/engine.rb +202 -90
  14. data/lib/omq/peer.rb +49 -0
  15. data/lib/omq/pub_sub.rb +2 -2
  16. data/lib/omq/radio_dish.rb +122 -0
  17. data/lib/omq/reactor.rb +14 -5
  18. data/lib/omq/readable.rb +5 -1
  19. data/lib/omq/routing/channel.rb +110 -0
  20. data/lib/omq/routing/client.rb +70 -0
  21. data/lib/omq/routing/conn_send_pump.rb +5 -8
  22. data/lib/omq/routing/dealer.rb +3 -15
  23. data/lib/omq/routing/dish.rb +94 -0
  24. data/lib/omq/routing/fan_out.rb +12 -16
  25. data/lib/omq/routing/gather.rb +60 -0
  26. data/lib/omq/routing/pair.rb +7 -26
  27. data/lib/omq/routing/peer.rb +95 -0
  28. data/lib/omq/routing/pub.rb +2 -13
  29. data/lib/omq/routing/pull.rb +3 -15
  30. data/lib/omq/routing/push.rb +4 -13
  31. data/lib/omq/routing/radio.rb +187 -0
  32. data/lib/omq/routing/rep.rb +5 -19
  33. data/lib/omq/routing/req.rb +6 -18
  34. data/lib/omq/routing/round_robin.rb +15 -19
  35. data/lib/omq/routing/router.rb +5 -19
  36. data/lib/omq/routing/scatter.rb +76 -0
  37. data/lib/omq/routing/server.rb +90 -0
  38. data/lib/omq/routing/sub.rb +3 -15
  39. data/lib/omq/routing/xpub.rb +2 -13
  40. data/lib/omq/routing/xsub.rb +8 -25
  41. data/lib/omq/scatter_gather.rb +56 -0
  42. data/lib/omq/socket.rb +8 -23
  43. data/lib/omq/transport/inproc/{direct_pipe.rb → pipe.rb} +26 -24
  44. data/lib/omq/transport/inproc.rb +22 -14
  45. data/lib/omq/transport/ipc.rb +41 -13
  46. data/lib/omq/transport/tcp.rb +59 -23
  47. data/lib/omq/transport/udp.rb +281 -0
  48. data/lib/omq/version.rb +1 -1
  49. data/lib/omq/writable.rb +11 -42
  50. data/lib/omq.rb +9 -64
  51. metadata +17 -3
  52. data/lib/omq/monitor_event.rb +0 -16
@@ -12,7 +12,7 @@ module OMQ
12
12
  # which is strictly better than libzmq's strict per-pipe round-robin
13
13
  # for PUSH-style patterns.
14
14
  #
15
- # See DESIGN.md "Per-socket HWM (not per-connection)" for the
15
+ # See doc/DESIGN.md "Per-socket HWM (not per-connection)" for the
16
16
  # full reasoning.
17
17
  #
18
18
  # Including classes must call `init_round_robin(engine)` from
@@ -38,18 +38,17 @@ module OMQ
38
38
  # @param engine [Engine]
39
39
  #
40
40
  def init_round_robin(engine)
41
- @connections = []
42
- @send_queue = Routing.build_queue(engine.options.send_hwm, :block)
43
- @direct_pipe = nil
44
- @conn_send_tasks = {} # conn => send pump task
45
- @in_flight = 0 # messages dequeued but not yet written
41
+ @connections = []
42
+ @send_queue = Routing.build_queue(engine.options.send_hwm, :block)
43
+ @direct_pipe = nil
44
+ @in_flight = 0 # messages dequeued but not yet written
46
45
  end
47
46
 
48
47
 
49
48
  # Registers a connection and starts its send pump.
50
49
  # Call from #connection_added.
51
50
  #
52
- # @param conn [Connection]
51
+ # @param conn [Protocol::ZMTP::Connection]
53
52
  #
54
53
  def add_round_robin_send_connection(conn)
55
54
  @connections << conn
@@ -58,23 +57,23 @@ module OMQ
58
57
  end
59
58
 
60
59
 
61
- # Removes the connection and stops its send pump. Any message
62
- # the pump had already dequeued but not yet written is dropped --
63
- # matching libzmq's behavior on `pipe_terminated`. PUSH has no
64
- # cross-peer ordering guarantee, so this is safe.
60
+ # Removes the connection. Any message the pump had already
61
+ # dequeued but not yet written is dropped — matching libzmq's
62
+ # behavior on `pipe_terminated`. PUSH has no cross-peer ordering
63
+ # guarantee, so this is safe. The pump itself is torn down by
64
+ # the per-connection lifecycle barrier.
65
65
  #
66
- # @param conn [Connection]
66
+ # @param conn [Protocol::ZMTP::Connection]
67
67
  #
68
68
  def remove_round_robin_send_connection(conn)
69
69
  update_direct_pipe
70
- @conn_send_tasks.delete(conn)
71
70
  end
72
71
 
73
72
 
74
73
  # Updates the direct-pipe shortcut for inproc single-peer bypass.
75
74
  #
76
75
  def update_direct_pipe
77
- if @connections.size == 1 && @connections.first.is_a?(Transport::Inproc::DirectPipe)
76
+ if @connections.size == 1 && @connections.first.is_a?(Transport::Inproc::Pipe)
78
77
  @direct_pipe = @connections.first
79
78
  else
80
79
  @direct_pipe = nil
@@ -129,10 +128,10 @@ module OMQ
129
128
  # run. The yield is effectively free when the scheduler has no
130
129
  # other work.
131
130
  #
132
- # @param conn [Connection]
131
+ # @param conn [Protocol::ZMTP::Connection]
133
132
  #
134
133
  def start_conn_send_pump(conn)
135
- task = @engine.spawn_conn_pump_task(conn, annotation: "send pump") do
134
+ @engine.spawn_conn_pump_task(conn, annotation: "send pump") do
136
135
  batch = []
137
136
 
138
137
  loop do
@@ -153,9 +152,6 @@ module OMQ
153
152
  Async::Task.current.yield
154
153
  end
155
154
  end
156
-
157
- @conn_send_tasks[conn] = task
158
- @tasks << task
159
155
  end
160
156
 
161
157
 
@@ -23,9 +23,7 @@ module OMQ
23
23
  @recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
24
24
  @connections_by_identity = {}
25
25
  @identity_by_connection = {}
26
- @conn_queues = {} # connection => per-connection send queue
27
- @conn_send_tasks = {} # connection => send pump task
28
- @tasks = []
26
+ @conn_queues = {}
29
27
  end
30
28
 
31
29
 
@@ -47,7 +45,7 @@ module OMQ
47
45
  end
48
46
 
49
47
 
50
- # @param connection [Connection]
48
+ # @param connection [Protocol::ZMTP::Connection]
51
49
  #
52
50
  def connection_added(connection)
53
51
  identity = connection.peer_identity
@@ -55,22 +53,20 @@ module OMQ
55
53
  @connections_by_identity[identity] = connection
56
54
  @identity_by_connection[connection] = identity
57
55
 
58
- task = @engine.start_recv_pump(connection, @recv_queue) { |msg| [identity, *msg] }
59
- @tasks << task if task
56
+ @engine.start_recv_pump(connection, @recv_queue) { |msg| [identity, *msg] }
60
57
 
61
58
  q = Routing.build_queue(@engine.options.send_hwm, :block)
62
59
  @conn_queues[connection] = q
63
- @conn_send_tasks[connection] = ConnSendPump.start(@engine, connection, q, @tasks)
60
+ ConnSendPump.start(@engine, connection, q)
64
61
  end
65
62
 
66
63
 
67
- # @param connection [Connection]
64
+ # @param connection [Protocol::ZMTP::Connection]
68
65
  #
69
66
  def connection_removed(connection)
70
67
  identity = @identity_by_connection.delete(connection)
71
68
  @connections_by_identity.delete(identity) if identity
72
69
  @conn_queues.delete(connection)
73
- @conn_send_tasks.delete(connection)&.stop
74
70
  end
75
71
 
76
72
 
@@ -91,16 +87,6 @@ module OMQ
91
87
  end
92
88
 
93
89
 
94
- # Stops all background tasks.
95
- #
96
- # @return [void]
97
- #
98
- def stop
99
- @tasks.each(&:stop)
100
- @tasks.clear
101
- end
102
-
103
-
104
90
  # @return [Boolean] true when all per-connection send queues are empty
105
91
  #
106
92
  def send_queues_drained?
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMQ
4
+ module Routing
5
+ # SCATTER socket routing: round-robin send to GATHER peers.
6
+ #
7
+ class Scatter
8
+ include RoundRobin
9
+
10
+ # @param engine [Engine]
11
+ #
12
+ def initialize(engine)
13
+ @engine = engine
14
+ init_round_robin(engine)
15
+ end
16
+
17
+
18
+ # SCATTER is write-only.
19
+ #
20
+ def recv_queue
21
+ raise "SCATTER sockets cannot receive"
22
+ end
23
+
24
+
25
+ def dequeue_recv
26
+ raise "SCATTER sockets cannot receive"
27
+ end
28
+
29
+
30
+ # No-op; SCATTER has no recv queue to unblock.
31
+ #
32
+ def unblock_recv
33
+ end
34
+
35
+
36
+ # @param connection [Protocol::ZMTP::Connection]
37
+ #
38
+ def connection_added(connection)
39
+ add_round_robin_send_connection(connection)
40
+ start_reaper(connection)
41
+ end
42
+
43
+
44
+ # @param connection [Protocol::ZMTP::Connection]
45
+ #
46
+ def connection_removed(connection)
47
+ @connections.delete(connection)
48
+ remove_round_robin_send_connection(connection)
49
+ end
50
+
51
+
52
+ # @param parts [Array<String>]
53
+ #
54
+ def enqueue(parts)
55
+ enqueue_round_robin(parts)
56
+ end
57
+
58
+
59
+ private
60
+
61
+
62
+ # Detects peer disconnection on write-only sockets by
63
+ # blocking on a receive that only returns on disconnect.
64
+ #
65
+ # @param conn [Protocol::ZMTP::Connection]
66
+ #
67
+ def start_reaper(conn)
68
+ return if conn.is_a?(Transport::Inproc::Pipe)
69
+ @engine.spawn_conn_pump_task(conn, annotation: "reaper") do
70
+ conn.receive_message # blocks until peer disconnects; then exits
71
+ end
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module OMQ
6
+ module Routing
7
+ # SERVER socket routing: identity-based routing with auto-generated
8
+ # 4-byte routing IDs.
9
+ #
10
+ # Prepends routing ID on receive. Strips routing ID on send and
11
+ # routes to the identified connection.
12
+ #
13
+ class Server
14
+ # @return [Async::LimitedQueue]
15
+ #
16
+ attr_reader :recv_queue
17
+
18
+
19
+ # @param engine [Engine]
20
+ #
21
+ def initialize(engine)
22
+ @engine = engine
23
+ @recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
24
+ @connections_by_routing_id = {}
25
+ @routing_id_by_connection = {}
26
+ @conn_queues = {}
27
+ end
28
+
29
+
30
+ # Dequeues the next received message. Blocks until one is available.
31
+ #
32
+ # @return [Array<String>, nil]
33
+ #
34
+ def dequeue_recv
35
+ @recv_queue.dequeue
36
+ end
37
+
38
+
39
+ # Wakes a blocked {#dequeue_recv} with a nil sentinel.
40
+ #
41
+ # @return [void]
42
+ #
43
+ def unblock_recv
44
+ @recv_queue.enqueue(nil)
45
+ end
46
+
47
+
48
+ # @param connection [Protocol::ZMTP::Connection]
49
+ #
50
+ def connection_added(connection)
51
+ routing_id = SecureRandom.bytes(4)
52
+ @connections_by_routing_id[routing_id] = connection
53
+ @routing_id_by_connection[connection] = routing_id
54
+
55
+ @engine.start_recv_pump(connection, @recv_queue) { |msg| [routing_id, *msg] }
56
+
57
+ q = Routing.build_queue(@engine.options.send_hwm, :block)
58
+ @conn_queues[connection] = q
59
+ ConnSendPump.start(@engine, connection, q)
60
+ end
61
+
62
+
63
+ # @param connection [Protocol::ZMTP::Connection]
64
+ #
65
+ def connection_removed(connection)
66
+ routing_id = @routing_id_by_connection.delete(connection)
67
+ @connections_by_routing_id.delete(routing_id) if routing_id
68
+ @conn_queues.delete(connection)
69
+ end
70
+
71
+
72
+ # @param parts [Array<String>]
73
+ #
74
+ def enqueue(parts)
75
+ routing_id = parts.first
76
+ conn = @connections_by_routing_id[routing_id]
77
+ return unless conn
78
+ @conn_queues[conn]&.enqueue(parts[1..])
79
+ end
80
+
81
+
82
+ # True when all per-connection send queues are empty.
83
+ #
84
+ def send_queues_drained?
85
+ @conn_queues.values.all?(&:empty?)
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -20,7 +20,6 @@ module OMQ
20
20
  @connections = Set.new
21
21
  @recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
22
22
  @subscriptions = Set.new
23
- @tasks = []
24
23
  end
25
24
 
26
25
 
@@ -42,7 +41,7 @@ module OMQ
42
41
  end
43
42
 
44
43
 
45
- # @param connection [Connection]
44
+ # @param connection [Protocol::ZMTP::Connection]
46
45
  #
47
46
  def connection_added(connection)
48
47
  @connections << connection
@@ -51,12 +50,11 @@ module OMQ
51
50
  send_subscribe(connection, prefix)
52
51
  end
53
52
 
54
- task = @engine.start_recv_pump(connection, @recv_queue)
55
- @tasks << task if task
53
+ @engine.start_recv_pump(connection, @recv_queue)
56
54
  end
57
55
 
58
56
 
59
- # @param connection [Connection]
57
+ # @param connection [Protocol::ZMTP::Connection]
60
58
  #
61
59
  def connection_removed(connection)
62
60
  @connections.delete(connection)
@@ -90,16 +88,6 @@ module OMQ
90
88
  end
91
89
 
92
90
 
93
- # Stops all background tasks.
94
- #
95
- # @return [void]
96
- #
97
- def stop
98
- @tasks.each(&:stop)
99
- @tasks.clear
100
- end
101
-
102
-
103
91
  private
104
92
 
105
93
 
@@ -24,7 +24,6 @@ module OMQ
24
24
  def initialize(engine)
25
25
  @engine = engine
26
26
  @recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
27
- @tasks = []
28
27
 
29
28
  init_fan_out(engine)
30
29
  end
@@ -42,7 +41,7 @@ module OMQ
42
41
  end
43
42
 
44
43
 
45
- # @param connection [Connection]
44
+ # @param connection [Protocol::ZMTP::Connection]
46
45
  #
47
46
  def connection_added(connection)
48
47
  @connections << connection
@@ -52,7 +51,7 @@ module OMQ
52
51
  end
53
52
 
54
53
 
55
- # @param connection [Connection]
54
+ # @param connection [Protocol::ZMTP::Connection]
56
55
  #
57
56
  def connection_removed(connection)
58
57
  @connections.delete(connection)
@@ -68,16 +67,6 @@ module OMQ
68
67
  end
69
68
 
70
69
 
71
- # Stops all background tasks.
72
- #
73
- # @return [void]
74
- #
75
- def stop
76
- @tasks.each(&:stop)
77
- @tasks.clear
78
- end
79
-
80
-
81
70
  private
82
71
 
83
72
 
@@ -18,12 +18,10 @@ module OMQ
18
18
  # @param engine [Engine]
19
19
  #
20
20
  def initialize(engine)
21
- @engine = engine
22
- @connections = Set.new
23
- @recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
24
- @conn_queues = {} # connection => per-connection send queue
25
- @conn_send_tasks = {} # connection => send pump task
26
- @tasks = []
21
+ @engine = engine
22
+ @connections = Set.new
23
+ @recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
24
+ @conn_queues = {}
27
25
  end
28
26
 
29
27
 
@@ -45,13 +43,12 @@ module OMQ
45
43
  end
46
44
 
47
45
 
48
- # @param connection [Connection]
46
+ # @param connection [Protocol::ZMTP::Connection]
49
47
  #
50
48
  def connection_added(connection)
51
49
  @connections << connection
52
50
 
53
- task = @engine.start_recv_pump(connection, @recv_queue)
54
- @tasks << task if task
51
+ @engine.start_recv_pump(connection, @recv_queue)
55
52
 
56
53
  q = Routing.build_queue(@engine.options.send_hwm, :block)
57
54
  @conn_queues[connection] = q
@@ -59,12 +56,11 @@ module OMQ
59
56
  end
60
57
 
61
58
 
62
- # @param connection [Connection]
59
+ # @param connection [Protocol::ZMTP::Connection]
63
60
  #
64
61
  def connection_removed(connection)
65
62
  @connections.delete(connection)
66
63
  @conn_queues.delete(connection)
67
- @conn_send_tasks.delete(connection)&.stop
68
64
  end
69
65
 
70
66
 
@@ -79,16 +75,6 @@ module OMQ
79
75
  end
80
76
 
81
77
 
82
- # Stops all background tasks.
83
- #
84
- # @return [void]
85
- #
86
- def stop
87
- @tasks.each(&:stop)
88
- @tasks.clear
89
- end
90
-
91
-
92
78
  # @return [Boolean] true when all per-connection send queues are empty
93
79
  #
94
80
  def send_queues_drained?
@@ -100,7 +86,7 @@ module OMQ
100
86
 
101
87
 
102
88
  def start_conn_send_pump(conn, q)
103
- task = @engine.spawn_conn_pump_task(conn, annotation: "send pump") do
89
+ @engine.spawn_conn_pump_task(conn, annotation: "send pump") do
104
90
  loop do
105
91
  parts = q.dequeue
106
92
  frame = parts.first&.b
@@ -128,9 +114,6 @@ module OMQ
128
114
  end
129
115
  end
130
116
  end
131
-
132
- @conn_send_tasks[conn] = task
133
- @tasks << task
134
117
  end
135
118
 
136
119
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # OMQ SCATTER/GATHER socket types (ZeroMQ RFC 49).
4
+ #
5
+ # Not loaded by +require "omq"+; opt in with:
6
+ #
7
+ # require "omq/scatter_gather"
8
+
9
+ require "omq"
10
+ require_relative "routing/scatter"
11
+ require_relative "routing/gather"
12
+
13
+ module OMQ
14
+ # Pipeline sender socket that round-robins to GATHER peers (ZeroMQ RFC 49).
15
+ class SCATTER < Socket
16
+ include Writable
17
+ include SingleFrame
18
+
19
+ # Creates a new SCATTER socket.
20
+ #
21
+ # @param endpoints [String, Array<String>, nil] endpoint(s) to connect to
22
+ # @param linger [Numeric] linger period in seconds (Float::INFINITY = wait forever, 0 = drop)
23
+ # @param send_hwm [Integer, nil] send high-water mark
24
+ # @param send_timeout [Integer, nil] send timeout in seconds
25
+ # @param backend [Object, nil] optional transport backend
26
+ def initialize(endpoints = nil, linger: Float::INFINITY, send_hwm: nil, send_timeout: nil, backend: nil)
27
+ init_engine(:SCATTER, send_hwm: send_hwm, send_timeout: send_timeout, backend: backend)
28
+ @options.linger = linger
29
+ attach_endpoints(endpoints, default: :connect)
30
+ end
31
+ end
32
+
33
+
34
+ # Pipeline receiver socket that fair-queues from SCATTER peers (ZeroMQ RFC 49).
35
+ class GATHER < Socket
36
+ include Readable
37
+ include SingleFrame
38
+
39
+ # Creates a new GATHER socket.
40
+ #
41
+ # @param endpoints [String, Array<String>, nil] endpoint(s) to bind to
42
+ # @param linger [Numeric] linger period in seconds (Float::INFINITY = wait forever, 0 = drop)
43
+ # @param recv_hwm [Integer, nil] receive high-water mark
44
+ # @param recv_timeout [Integer, nil] receive timeout in seconds
45
+ # @param backend [Object, nil] optional transport backend
46
+ def initialize(endpoints = nil, linger: Float::INFINITY, recv_hwm: nil, recv_timeout: nil, backend: nil)
47
+ init_engine(:GATHER, recv_hwm: recv_hwm, recv_timeout: recv_timeout, backend: backend)
48
+ @options.linger = linger
49
+ attach_endpoints(endpoints, default: :bind)
50
+ end
51
+ end
52
+
53
+
54
+ Routing.register(:SCATTER, Routing::Scatter)
55
+ Routing.register(:GATHER, Routing::Gather)
56
+ end
data/lib/omq/socket.rb CHANGED
@@ -35,11 +35,6 @@ module OMQ
35
35
  attr_reader :options
36
36
 
37
37
 
38
- # @return [Integer, nil] last auto-selected TCP port
39
- #
40
- attr_reader :last_tcp_port
41
-
42
-
43
38
  # @return [Engine] the socket's engine. Exposed for peer tooling
44
39
  # (omq-cli, omq-ffi, omq-ractor) that needs to reach into the
45
40
  # socket's internals — not part of the stable user API.
@@ -109,14 +104,11 @@ module OMQ
109
104
  # can coordinate teardown with their own Async tree. Only the
110
105
  # *first* bind/connect call captures the parent — subsequent
111
106
  # calls ignore the kwarg.
112
- # @return [void]
107
+ # @return [URI::Generic] resolved endpoint URI (with auto-selected port for "tcp://host:0")
113
108
  #
114
- def bind(endpoint, parent: nil)
109
+ def bind(endpoint, parent: nil, **opts)
115
110
  ensure_parent_task(parent: parent)
116
- Reactor.run do
117
- @engine.bind(endpoint) # TODO: use timeout?
118
- @last_tcp_port = @engine.last_tcp_port
119
- end
111
+ Reactor.run { @engine.bind(endpoint, **opts) } # TODO: use timeout?
120
112
  end
121
113
 
122
114
 
@@ -124,11 +116,11 @@ module OMQ
124
116
  #
125
117
  # @param endpoint [String]
126
118
  # @param parent [#async, nil] see {#bind}.
127
- # @return [void]
119
+ # @return [URI::Generic] parsed endpoint URI
128
120
  #
129
- def connect(endpoint, parent: nil)
121
+ def connect(endpoint, parent: nil, **opts)
130
122
  ensure_parent_task(parent: parent)
131
- Reactor.run { @engine.connect(endpoint) } # TODO: use timeout?
123
+ Reactor.run { @engine.connect(endpoint, **opts) } # TODO: use timeout?
132
124
  end
133
125
 
134
126
 
@@ -152,13 +144,6 @@ module OMQ
152
144
  end
153
145
 
154
146
 
155
- # @return [String, nil] last bound endpoint
156
- #
157
- def last_endpoint
158
- @engine.last_endpoint
159
- end
160
-
161
-
162
147
  # @return [Async::Promise] resolves when first peer completes handshake
163
148
  def peer_connected
164
149
  @engine.peer_connected
@@ -167,7 +152,7 @@ module OMQ
167
152
 
168
153
  # @return [Async::Promise] resolves when first subscriber joins (PUB/XPUB only)
169
154
  def subscriber_joined
170
- @engine.routing.subscriber_joined
155
+ @engine.subscriber_joined
171
156
  end
172
157
 
173
158
 
@@ -285,7 +270,7 @@ module OMQ
285
270
  # @return [String]
286
271
  #
287
272
  def inspect
288
- format("#<%s last_endpoint=%p>", self.class, last_endpoint)
273
+ format("#<%s bound=%p>", self.class, @engine.listeners.keys)
289
274
  end
290
275
 
291
276