nnq 0.6.1 → 0.8.1
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 +90 -0
- data/lib/nnq/bus.rb +1 -1
- data/lib/nnq/constants.rb +58 -0
- data/lib/nnq/engine/connection_lifecycle.rb +28 -4
- data/lib/nnq/engine/reconnect.rb +1 -26
- data/lib/nnq/engine.rb +53 -18
- data/lib/nnq/pair.rb +1 -1
- data/lib/nnq/pub_sub.rb +1 -1
- data/lib/nnq/push_pull.rb +1 -1
- data/lib/nnq/req_rep.rb +4 -4
- data/lib/nnq/routing/backtrace.rb +1 -1
- data/lib/nnq/routing/bus.rb +9 -10
- data/lib/nnq/routing/pair.rb +7 -0
- data/lib/nnq/routing/pub.rb +4 -13
- data/lib/nnq/routing/pull.rb +8 -0
- data/lib/nnq/routing/rep.rb +11 -0
- data/lib/nnq/routing/rep_raw.rb +10 -0
- data/lib/nnq/routing/req.rb +1 -1
- data/lib/nnq/routing/req_raw.rb +10 -0
- data/lib/nnq/routing/respondent.rb +10 -0
- data/lib/nnq/routing/respondent_raw.rb +10 -0
- data/lib/nnq/routing/sub.rb +7 -0
- data/lib/nnq/routing/surveyor.rb +19 -14
- data/lib/nnq/routing/surveyor_raw.rb +13 -13
- data/lib/nnq/socket.rb +18 -18
- data/lib/nnq/surveyor_respondent.rb +4 -4
- data/lib/nnq/transport/inproc/pipe.rb +142 -0
- data/lib/nnq/transport/inproc.rb +27 -25
- data/lib/nnq/transport/ipc.rb +5 -2
- data/lib/nnq/transport/tcp.rb +5 -2
- data/lib/nnq/version.rb +1 -1
- data/lib/nnq.rb +12 -11
- metadata +7 -6
- data/lib/nnq/monitor_event.rb +0 -18
data/lib/nnq/routing/req_raw.rb
CHANGED
|
@@ -48,6 +48,16 @@ module NNQ
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
# Inproc fast-path hook.
|
|
52
|
+
def direct_recv_for(conn)
|
|
53
|
+
transform = lambda do |wire_bytes|
|
|
54
|
+
header, payload = parse_backtrace(wire_bytes)
|
|
55
|
+
header ? [conn, header, payload] : nil
|
|
56
|
+
end
|
|
57
|
+
[@recv_queue, transform]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
|
|
51
61
|
def close
|
|
52
62
|
@recv_queue.enqueue(nil)
|
|
53
63
|
end
|
|
@@ -71,6 +71,16 @@ module NNQ
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
# Inproc fast-path hook.
|
|
75
|
+
def direct_recv_for(conn)
|
|
76
|
+
transform = lambda do |body|
|
|
77
|
+
btrace, payload = parse_backtrace(body)
|
|
78
|
+
btrace ? [conn, btrace, payload] : nil
|
|
79
|
+
end
|
|
80
|
+
[@recv_queue, transform]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
|
|
74
84
|
def connection_removed(conn)
|
|
75
85
|
@mutex.synchronize do
|
|
76
86
|
@pending = nil if @pending && @pending[0] == conn
|
|
@@ -47,6 +47,16 @@ module NNQ
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
# Inproc fast-path hook.
|
|
51
|
+
def direct_recv_for(conn)
|
|
52
|
+
transform = lambda do |wire_bytes|
|
|
53
|
+
header, payload = parse_backtrace(wire_bytes)
|
|
54
|
+
header ? [conn, header, payload] : nil
|
|
55
|
+
end
|
|
56
|
+
[@recv_queue, transform]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
50
60
|
def close
|
|
51
61
|
@recv_queue.enqueue(nil)
|
|
52
62
|
end
|
data/lib/nnq/routing/sub.rb
CHANGED
|
@@ -38,6 +38,13 @@ module NNQ
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
# Inproc fast-path hook: filter via the subscription list in the
|
|
42
|
+
# transform, then enqueue only matching bodies.
|
|
43
|
+
def direct_recv_for(_conn)
|
|
44
|
+
[@queue, ->(body) { matches?(body) ? body : nil }]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
|
|
41
48
|
# @return [String, nil]
|
|
42
49
|
def receive
|
|
43
50
|
@queue.dequeue
|
data/lib/nnq/routing/surveyor.rb
CHANGED
|
@@ -24,7 +24,6 @@ module NNQ
|
|
|
24
24
|
def initialize(engine)
|
|
25
25
|
@engine = engine
|
|
26
26
|
@queues = {} # conn => Async::LimitedQueue
|
|
27
|
-
@pump_tasks = {} # conn => Async::Task
|
|
28
27
|
@recv_queue = Async::Queue.new
|
|
29
28
|
@current_id = nil
|
|
30
29
|
@mutex = Mutex.new
|
|
@@ -68,7 +67,7 @@ module NNQ
|
|
|
68
67
|
return if body.bytesize < 4
|
|
69
68
|
|
|
70
69
|
id = body.unpack1("N")
|
|
71
|
-
payload = body.byteslice(4..)
|
|
70
|
+
payload = body.byteslice(4..).freeze
|
|
72
71
|
|
|
73
72
|
@mutex.synchronize do
|
|
74
73
|
return unless @current_id == id
|
|
@@ -78,6 +77,21 @@ module NNQ
|
|
|
78
77
|
end
|
|
79
78
|
|
|
80
79
|
|
|
80
|
+
# Inproc fast-path hook. Transform filters replies by current
|
|
81
|
+
# survey id and strips the 4-byte header, mirroring #enqueue.
|
|
82
|
+
def direct_recv_for(_conn)
|
|
83
|
+
mutex = @mutex
|
|
84
|
+
transform = lambda do |body|
|
|
85
|
+
next nil if body.bytesize < 4
|
|
86
|
+
id = body.unpack1("N")
|
|
87
|
+
payload = body.byteslice(4..).freeze
|
|
88
|
+
match = mutex.synchronize { @current_id == id }
|
|
89
|
+
match ? payload : nil
|
|
90
|
+
end
|
|
91
|
+
[@recv_queue, transform]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
|
|
81
95
|
# Strips the 4-byte survey id for verbose trace previews.
|
|
82
96
|
def preview_body(wire)
|
|
83
97
|
wire.byteslice(4..) || wire
|
|
@@ -85,21 +99,14 @@ module NNQ
|
|
|
85
99
|
|
|
86
100
|
|
|
87
101
|
def connection_added(conn)
|
|
88
|
-
queue
|
|
89
|
-
@queues[conn]
|
|
90
|
-
|
|
102
|
+
queue = Async::LimitedQueue.new(@engine.options.send_hwm)
|
|
103
|
+
@queues[conn] = queue
|
|
104
|
+
spawn_pump(conn, queue)
|
|
91
105
|
end
|
|
92
106
|
|
|
93
107
|
|
|
94
108
|
def connection_removed(conn)
|
|
95
109
|
@queues.delete(conn)
|
|
96
|
-
task = @pump_tasks.delete(conn)
|
|
97
|
-
|
|
98
|
-
return unless task
|
|
99
|
-
return if task == Async::Task.current
|
|
100
|
-
|
|
101
|
-
task.stop
|
|
102
|
-
rescue IOError, Errno::EPIPE
|
|
103
110
|
end
|
|
104
111
|
|
|
105
112
|
|
|
@@ -109,8 +116,6 @@ module NNQ
|
|
|
109
116
|
|
|
110
117
|
|
|
111
118
|
def close
|
|
112
|
-
@pump_tasks.each_value(&:stop)
|
|
113
|
-
@pump_tasks.clear
|
|
114
119
|
@queues.clear
|
|
115
120
|
@recv_queue.enqueue(nil)
|
|
116
121
|
end
|
|
@@ -23,7 +23,6 @@ module NNQ
|
|
|
23
23
|
def initialize(engine)
|
|
24
24
|
@engine = engine
|
|
25
25
|
@queues = {} # conn => Async::LimitedQueue
|
|
26
|
-
@pump_tasks = {} # conn => Async::Task
|
|
27
26
|
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
28
27
|
end
|
|
29
28
|
|
|
@@ -47,6 +46,16 @@ module NNQ
|
|
|
47
46
|
end
|
|
48
47
|
|
|
49
48
|
|
|
49
|
+
# Inproc fast-path hook.
|
|
50
|
+
def direct_recv_for(conn)
|
|
51
|
+
transform = lambda do |wire_bytes|
|
|
52
|
+
header, payload = parse_backtrace(wire_bytes)
|
|
53
|
+
header ? [conn, header, payload] : nil
|
|
54
|
+
end
|
|
55
|
+
[@recv_queue, transform]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
|
|
50
59
|
def preview_body(wire)
|
|
51
60
|
_, payload = parse_backtrace(wire)
|
|
52
61
|
payload || wire
|
|
@@ -54,21 +63,14 @@ module NNQ
|
|
|
54
63
|
|
|
55
64
|
|
|
56
65
|
def connection_added(conn)
|
|
57
|
-
queue
|
|
58
|
-
@queues[conn]
|
|
59
|
-
|
|
66
|
+
queue = Async::LimitedQueue.new(@engine.options.send_hwm)
|
|
67
|
+
@queues[conn] = queue
|
|
68
|
+
spawn_pump(conn, queue)
|
|
60
69
|
end
|
|
61
70
|
|
|
62
71
|
|
|
63
72
|
def connection_removed(conn)
|
|
64
73
|
@queues.delete(conn)
|
|
65
|
-
task = @pump_tasks.delete(conn)
|
|
66
|
-
|
|
67
|
-
return unless task
|
|
68
|
-
return if task == Async::Task.current
|
|
69
|
-
|
|
70
|
-
task.stop
|
|
71
|
-
rescue IOError, Errno::EPIPE
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
|
|
@@ -78,8 +80,6 @@ module NNQ
|
|
|
78
80
|
|
|
79
81
|
|
|
80
82
|
def close
|
|
81
|
-
@pump_tasks.each_value(&:stop)
|
|
82
|
-
@pump_tasks.clear
|
|
83
83
|
@queues.clear
|
|
84
84
|
@recv_queue.enqueue(nil)
|
|
85
85
|
end
|
data/lib/nnq/socket.rb
CHANGED
|
@@ -2,11 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "async/queue"
|
|
4
4
|
|
|
5
|
-
require_relative "options"
|
|
6
|
-
require_relative "engine"
|
|
7
|
-
require_relative "monitor_event"
|
|
8
|
-
require_relative "reactor"
|
|
9
|
-
|
|
10
5
|
module NNQ
|
|
11
6
|
# Socket base class. Subclasses (PUSH, PULL, ...) wire up a routing
|
|
12
7
|
# strategy and the SP protocol id.
|
|
@@ -50,15 +45,15 @@ module NNQ
|
|
|
50
45
|
end
|
|
51
46
|
|
|
52
47
|
|
|
53
|
-
def bind(endpoint)
|
|
48
|
+
def bind(endpoint, **opts)
|
|
54
49
|
ensure_parent_task
|
|
55
|
-
Reactor.run { @engine.bind(endpoint) }
|
|
50
|
+
Reactor.run { @engine.bind(endpoint, **opts) }
|
|
56
51
|
end
|
|
57
52
|
|
|
58
53
|
|
|
59
|
-
def connect(endpoint)
|
|
54
|
+
def connect(endpoint, **opts)
|
|
60
55
|
ensure_parent_task
|
|
61
|
-
Reactor.run { @engine.connect(endpoint) }
|
|
56
|
+
Reactor.run { @engine.connect(endpoint, **opts) }
|
|
62
57
|
end
|
|
63
58
|
|
|
64
59
|
|
|
@@ -140,16 +135,21 @@ module NNQ
|
|
|
140
135
|
end
|
|
141
136
|
|
|
142
137
|
|
|
143
|
-
# Coerces +body+ to a frozen
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
138
|
+
# Coerces +body+ to a frozen `Encoding::BINARY`-tagged String and
|
|
139
|
+
# returns it. Every send method runs its body through this so the
|
|
140
|
+
# receiver sees a uniform frozen+BINARY contract across transports
|
|
141
|
+
# (mutation bugs raise `FrozenError` instead of silently corrupting
|
|
142
|
+
# a shared reference on the inproc fast path).
|
|
148
143
|
#
|
|
149
|
-
# Fast-path:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
144
|
+
# Fast-path: unfrozen non-BINARY strings are re-tagged in place
|
|
145
|
+
# (force_encoding is a flag flip, no copy). The pathological case
|
|
146
|
+
# of a frozen non-BINARY body (e.g. a `# frozen_string_literal: true`
|
|
147
|
+
# literal) can't be re-tagged in place — the inproc {Pipe} handles
|
|
148
|
+
# that with a copy so the receive contract stays uniform.
|
|
149
|
+
def coerce_binary(body)
|
|
150
|
+
body = body.to_str unless body.is_a?(String)
|
|
151
|
+
body.force_encoding(Encoding::BINARY) unless body.frozen? || body.encoding == Encoding::BINARY
|
|
152
|
+
body.freeze
|
|
153
153
|
end
|
|
154
154
|
|
|
155
155
|
|
|
@@ -16,7 +16,7 @@ module NNQ
|
|
|
16
16
|
# Cooked: broadcasts +body+ as a survey to all connected respondents.
|
|
17
17
|
def send_survey(body)
|
|
18
18
|
raise Error, "SURVEYOR#send_survey not available in raw mode" if raw?
|
|
19
|
-
body =
|
|
19
|
+
body = coerce_binary(body)
|
|
20
20
|
Reactor.run { @engine.routing.send_survey(body) }
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -26,7 +26,7 @@ module NNQ
|
|
|
26
26
|
# protocol-sp header kwarg — no concat). Raises in cooked mode.
|
|
27
27
|
def send(body, header:)
|
|
28
28
|
raise Error, "SURVEYOR#send not available in cooked mode" unless raw?
|
|
29
|
-
body =
|
|
29
|
+
body = coerce_binary(body)
|
|
30
30
|
Reactor.run { @engine.routing.send(body, header: header) }
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -70,7 +70,7 @@ module NNQ
|
|
|
70
70
|
# recent survey. Raises in raw mode.
|
|
71
71
|
def send_reply(body)
|
|
72
72
|
raise Error, "RESPONDENT#send_reply not available in raw mode" if raw?
|
|
73
|
-
body =
|
|
73
|
+
body = coerce_binary(body)
|
|
74
74
|
Reactor.run { @engine.routing.send_reply(body) }
|
|
75
75
|
end
|
|
76
76
|
|
|
@@ -78,7 +78,7 @@ module NNQ
|
|
|
78
78
|
# Raw: writes +body+ with +header+ back to +to+. Raises in cooked mode.
|
|
79
79
|
def send(body, to:, header:)
|
|
80
80
|
raise Error, "RESPONDENT#send not available in cooked mode" unless raw?
|
|
81
|
-
body =
|
|
81
|
+
body = coerce_binary(body)
|
|
82
82
|
Reactor.run { @engine.routing.send(body, to: to, header: header) }
|
|
83
83
|
end
|
|
84
84
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NNQ
|
|
4
|
+
module Transport
|
|
5
|
+
module Inproc
|
|
6
|
+
# Queue-based in-process pipe. Duck-types {NNQ::Connection} so
|
|
7
|
+
# routing strategies, the recv loop, and the send pump work
|
|
8
|
+
# against it unchanged.
|
|
9
|
+
#
|
|
10
|
+
# No wire framing: bodies are transferred as frozen Strings
|
|
11
|
+
# through a pair of {Async::Queue} (one per direction). When an
|
|
12
|
+
# SP backtrace header is supplied (REQ/REP/SURVEYOR paths), it's
|
|
13
|
+
# prepended before enqueue so {#receive_message} returns an
|
|
14
|
+
# already-prefixed body — matching the TCP/IPC framing semantic
|
|
15
|
+
# so routing's `parse_backtrace` parses the same layout either
|
|
16
|
+
# way.
|
|
17
|
+
#
|
|
18
|
+
# Direct-recv fast path: when a routing strategy calls
|
|
19
|
+
# {#wire_direct_recv} on the peer side of a pipe pair, subsequent
|
|
20
|
+
# {#send_message} calls enqueue straight into the consumer's
|
|
21
|
+
# recv queue — the intermediate pipe queue and the recv pump
|
|
22
|
+
# fiber are both skipped. Cuts three fiber hops to one and is
|
|
23
|
+
# what lets inproc PUSH/PULL clear 1M msg/s on YJIT.
|
|
24
|
+
#
|
|
25
|
+
# Wiring happens synchronously inside {Transport::Inproc.connect}
|
|
26
|
+
# (before the call returns to the caller), so there's no window
|
|
27
|
+
# in which a send can precede a wire — no pending buffer needed.
|
|
28
|
+
#
|
|
29
|
+
# Close protocol: {#close} enqueues a `nil` sentinel onto the
|
|
30
|
+
# send side (or the direct queue if wired). The peer's recv loop
|
|
31
|
+
# sees `nil`, raises `EOFError`, and unwinds via its connection
|
|
32
|
+
# supervisor.
|
|
33
|
+
class Pipe
|
|
34
|
+
# @return [String, nil] endpoint URI this pipe was established on
|
|
35
|
+
attr_reader :endpoint
|
|
36
|
+
|
|
37
|
+
# @return [Pipe, nil] the other end of the pair
|
|
38
|
+
attr_accessor :peer
|
|
39
|
+
|
|
40
|
+
# @return [Async::Queue, nil] when non-nil, {#send_message}
|
|
41
|
+
# enqueues here instead of into @send_queue.
|
|
42
|
+
attr_reader :direct_recv_queue
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def initialize(send_queue:, recv_queue:, endpoint:)
|
|
46
|
+
@send_queue = send_queue
|
|
47
|
+
@recv_queue = recv_queue
|
|
48
|
+
@endpoint = endpoint
|
|
49
|
+
@closed = false
|
|
50
|
+
@peer = nil
|
|
51
|
+
@direct_recv_queue = nil
|
|
52
|
+
@direct_recv_transform = nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Wires the direct-recv fast path. After this call, messages
|
|
57
|
+
# sent on this pipe bypass the intermediate pipe queue and
|
|
58
|
+
# land directly in +queue+.
|
|
59
|
+
#
|
|
60
|
+
# @param queue [Async::Queue]
|
|
61
|
+
# @param transform [Proc, nil] optional per-message transform;
|
|
62
|
+
# return nil to drop the message (used by filter/parse
|
|
63
|
+
# strategies like SUB or REP).
|
|
64
|
+
def wire_direct_recv(queue, transform)
|
|
65
|
+
@direct_recv_transform = transform
|
|
66
|
+
@direct_recv_queue = queue
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def send_message(body, header: nil)
|
|
71
|
+
raise ClosedError, "connection closed" if @closed
|
|
72
|
+
|
|
73
|
+
# Socket#coerce_binary tags mutable bodies BINARY in place;
|
|
74
|
+
# the pathological case of a frozen non-BINARY body (e.g. a
|
|
75
|
+
# `# frozen_string_literal: true` literal) can't be re-tagged
|
|
76
|
+
# in place, so copy it here to keep the receiver contract
|
|
77
|
+
# uniform with TCP/IPC.
|
|
78
|
+
body = body.b.freeze if body.encoding != Encoding::BINARY
|
|
79
|
+
wire = header ? (header + body).freeze : body
|
|
80
|
+
|
|
81
|
+
if (q = @direct_recv_queue)
|
|
82
|
+
item = @direct_recv_transform ? @direct_recv_transform.call(wire) : wire
|
|
83
|
+
q.enqueue(item) unless item.nil?
|
|
84
|
+
else
|
|
85
|
+
@send_queue.enqueue(wire)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
alias write_message send_message
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def write_messages(bodies)
|
|
94
|
+
raise ClosedError, "connection closed" if @closed
|
|
95
|
+
|
|
96
|
+
if bodies.any? { |b| b.encoding != Encoding::BINARY }
|
|
97
|
+
bodies = bodies.map { |b| b.encoding == Encoding::BINARY ? b : b.b.freeze }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if (q = @direct_recv_queue)
|
|
101
|
+
transform = @direct_recv_transform
|
|
102
|
+
bodies.each do |body|
|
|
103
|
+
item = transform ? transform.call(body) : body
|
|
104
|
+
q.enqueue(item) unless item.nil?
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
bodies.each { |body| @send_queue.enqueue(body) }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# No-op — Async::Queue has no IO buffer to flush.
|
|
113
|
+
def flush
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def receive_message
|
|
119
|
+
item = @recv_queue.dequeue
|
|
120
|
+
raise EOFError, "connection closed" if item.nil?
|
|
121
|
+
item
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def closed?
|
|
126
|
+
@closed
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def close
|
|
131
|
+
return if @closed
|
|
132
|
+
@closed = true
|
|
133
|
+
# Close sentinel goes on whichever queue the peer is reading.
|
|
134
|
+
# When direct-wired, @send_queue is unused; hit the direct
|
|
135
|
+
# queue so the consumer unblocks.
|
|
136
|
+
(@direct_recv_queue || @send_queue).enqueue(nil)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
data/lib/nnq/transport/inproc.rb
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
4
|
-
|
|
3
|
+
require "async/queue"
|
|
4
|
+
|
|
5
|
+
require_relative "inproc/pipe"
|
|
5
6
|
|
|
6
7
|
module NNQ
|
|
7
8
|
module Transport
|
|
8
9
|
# In-process transport. Both peers live in the same process and
|
|
9
|
-
# exchange
|
|
10
|
+
# exchange frozen Strings through a pair of {Async::Queue}s — no
|
|
11
|
+
# wire framing, no socketpair, no SP handshake.
|
|
10
12
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# implementation.
|
|
13
|
+
# The historical implementation ran through a Unix `socketpair(2)`
|
|
14
|
+
# and the full SP protocol, making inproc roughly as expensive as
|
|
15
|
+
# IPC. Swapping to {Inproc::Pipe} (duck-types {NNQ::Connection})
|
|
16
|
+
# drops the kernel buffer copy, the framing encode/decode, and the
|
|
17
|
+
# handshake — inproc becomes a pure in-process queue transfer.
|
|
17
18
|
#
|
|
18
19
|
module Inproc
|
|
20
|
+
Engine.transports["inproc"] = self
|
|
21
|
+
|
|
19
22
|
@registry = {}
|
|
20
23
|
@mutex = Mutex.new
|
|
21
24
|
|
|
@@ -26,7 +29,7 @@ module NNQ
|
|
|
26
29
|
# @param endpoint [String] e.g. "inproc://my-endpoint"
|
|
27
30
|
# @param engine [Engine]
|
|
28
31
|
# @return [Listener]
|
|
29
|
-
def bind(endpoint, engine)
|
|
32
|
+
def bind(endpoint, engine, **)
|
|
30
33
|
@mutex.synchronize do
|
|
31
34
|
raise Error, "inproc endpoint already bound: #{endpoint}" if @registry.key?(endpoint)
|
|
32
35
|
@registry[endpoint] = engine
|
|
@@ -36,28 +39,27 @@ module NNQ
|
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
|
|
39
|
-
# Connects +engine+ to a bound inproc endpoint. Creates a
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
42
|
+
# Connects +engine+ to a bound inproc endpoint. Creates a Pipe
|
|
43
|
+
# pair — one queue per direction — and registers each side with
|
|
44
|
+
# its owning engine via {Engine#connection_ready}. No handshake
|
|
45
|
+
# runs; both ends are live as soon as the pipes are wired.
|
|
43
46
|
#
|
|
44
47
|
# @param endpoint [String]
|
|
45
48
|
# @param engine [Engine]
|
|
46
49
|
# @return [void]
|
|
47
|
-
def connect(endpoint, engine)
|
|
50
|
+
def connect(endpoint, engine, **)
|
|
48
51
|
bound = @mutex.synchronize { @registry[endpoint] }
|
|
49
52
|
raise Error, "inproc endpoint not bound: #{endpoint}" unless bound
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
a_to_b = Async::Queue.new
|
|
55
|
+
b_to_a = Async::Queue.new
|
|
56
|
+
client = Pipe.new(send_queue: a_to_b, recv_queue: b_to_a, endpoint: endpoint)
|
|
57
|
+
server = Pipe.new(send_queue: b_to_a, recv_queue: a_to_b, endpoint: endpoint)
|
|
58
|
+
client.peer = server
|
|
59
|
+
server.peer = client
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# it would block on reading our greeting before we've had
|
|
56
|
-
# a chance to write it.
|
|
57
|
-
bound.spawn_task(annotation: "nnq inproc accept #{endpoint}") do
|
|
58
|
-
bound.handle_accepted(IO::Stream::Buffered.wrap(b), endpoint: endpoint)
|
|
59
|
-
end
|
|
60
|
-
engine.handle_connected(IO::Stream::Buffered.wrap(a), endpoint: endpoint)
|
|
61
|
+
bound.connection_ready(server, endpoint: endpoint)
|
|
62
|
+
engine.connection_ready(client, endpoint: endpoint)
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
|
|
@@ -84,7 +86,7 @@ module NNQ
|
|
|
84
86
|
end
|
|
85
87
|
|
|
86
88
|
|
|
87
|
-
# No accept loop: inproc connects
|
|
89
|
+
# No accept loop: inproc connects are fully synchronous.
|
|
88
90
|
def start_accept_loop(_parent_task, &_on_accepted)
|
|
89
91
|
end
|
|
90
92
|
|
data/lib/nnq/transport/ipc.rb
CHANGED
|
@@ -13,13 +13,16 @@ module NNQ
|
|
|
13
13
|
# verbatim.
|
|
14
14
|
#
|
|
15
15
|
module IPC
|
|
16
|
+
Engine.transports["ipc"] = self
|
|
17
|
+
|
|
18
|
+
|
|
16
19
|
class << self
|
|
17
20
|
# Binds an IPC server.
|
|
18
21
|
#
|
|
19
22
|
# @param endpoint [String] e.g. "ipc:///tmp/nnq.sock" or "ipc://@abstract"
|
|
20
23
|
# @param engine [Engine]
|
|
21
24
|
# @return [Listener]
|
|
22
|
-
def bind(endpoint, engine)
|
|
25
|
+
def bind(endpoint, engine, **)
|
|
23
26
|
path = parse_path(endpoint)
|
|
24
27
|
sock_path = to_socket_path(path)
|
|
25
28
|
|
|
@@ -35,7 +38,7 @@ module NNQ
|
|
|
35
38
|
# @param endpoint [String]
|
|
36
39
|
# @param engine [Engine]
|
|
37
40
|
# @return [void]
|
|
38
|
-
def connect(endpoint, engine)
|
|
41
|
+
def connect(endpoint, engine, **)
|
|
39
42
|
path = parse_path(endpoint)
|
|
40
43
|
sock_path = to_socket_path(path)
|
|
41
44
|
sock = UNIXSocket.new(sock_path)
|
data/lib/nnq/transport/tcp.rb
CHANGED
|
@@ -11,13 +11,16 @@ module NNQ
|
|
|
11
11
|
# accept inside an Async fiber.
|
|
12
12
|
#
|
|
13
13
|
module TCP
|
|
14
|
+
Engine.transports["tcp"] = self
|
|
15
|
+
|
|
16
|
+
|
|
14
17
|
class << self
|
|
15
18
|
# Binds a TCP server to +endpoint+.
|
|
16
19
|
#
|
|
17
20
|
# @param endpoint [String] e.g. "tcp://127.0.0.1:5570" or "tcp://127.0.0.1:0"
|
|
18
21
|
# @param engine [Engine]
|
|
19
22
|
# @return [Listener]
|
|
20
|
-
def bind(endpoint, engine)
|
|
23
|
+
def bind(endpoint, engine, **)
|
|
21
24
|
host, port = parse_endpoint(endpoint)
|
|
22
25
|
host = "0.0.0.0" if host == "*"
|
|
23
26
|
server = TCPServer.new(host, port)
|
|
@@ -34,7 +37,7 @@ module NNQ
|
|
|
34
37
|
# @param endpoint [String]
|
|
35
38
|
# @param engine [Engine]
|
|
36
39
|
# @return [void]
|
|
37
|
-
def connect(endpoint, engine)
|
|
40
|
+
def connect(endpoint, engine, **)
|
|
38
41
|
host, port = parse_endpoint(endpoint)
|
|
39
42
|
sock = ::Socket.tcp(host, port, connect_timeout: connect_timeout(engine.options))
|
|
40
43
|
|
data/lib/nnq/version.rb
CHANGED
data/lib/nnq.rb
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "protocol/sp"
|
|
4
|
+
require "io/stream"
|
|
4
5
|
|
|
5
|
-
module NNQ
|
|
6
|
-
# Freezes module-level state so NNQ sockets can be used inside Ractors.
|
|
7
|
-
# Call this once before spawning any Ractors that create NNQ sockets.
|
|
8
|
-
#
|
|
9
|
-
def self.freeze_for_ractors!
|
|
10
|
-
Engine::CONNECTION_FAILED.freeze
|
|
11
|
-
Engine::CONNECTION_LOST.freeze
|
|
12
|
-
Engine::TRANSPORTS.freeze
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
6
|
|
|
7
|
+
# Core
|
|
16
8
|
require_relative "nnq/version"
|
|
17
|
-
require_relative "nnq/
|
|
9
|
+
require_relative "nnq/constants"
|
|
10
|
+
require_relative "nnq/reactor"
|
|
18
11
|
require_relative "nnq/options"
|
|
12
|
+
require_relative "nnq/error"
|
|
19
13
|
require_relative "nnq/connection"
|
|
20
14
|
require_relative "nnq/engine"
|
|
15
|
+
|
|
16
|
+
# Transport
|
|
17
|
+
require_relative "nnq/transport/inproc"
|
|
18
|
+
require_relative "nnq/transport/tcp"
|
|
19
|
+
require_relative "nnq/transport/ipc"
|
|
20
|
+
|
|
21
|
+
# Socket types
|
|
21
22
|
require_relative "nnq/socket"
|
|
22
23
|
require_relative "nnq/push_pull"
|
|
23
24
|
require_relative "nnq/pair"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nnq
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -41,16 +41,16 @@ dependencies:
|
|
|
41
41
|
name: protocol-sp
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0.
|
|
46
|
+
version: '0.4'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
|
-
- - "
|
|
51
|
+
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0.
|
|
53
|
+
version: '0.4'
|
|
54
54
|
description: Pure Ruby implementation of nanomsg's Scalability Protocols (SP) on top
|
|
55
55
|
of async + io-stream. Per-socket HWM, opportunistic batching, wire-compatible with
|
|
56
56
|
libnng over inproc/ipc/tcp.
|
|
@@ -66,12 +66,12 @@ files:
|
|
|
66
66
|
- lib/nnq.rb
|
|
67
67
|
- lib/nnq/bus.rb
|
|
68
68
|
- lib/nnq/connection.rb
|
|
69
|
+
- lib/nnq/constants.rb
|
|
69
70
|
- lib/nnq/engine.rb
|
|
70
71
|
- lib/nnq/engine/connection_lifecycle.rb
|
|
71
72
|
- lib/nnq/engine/reconnect.rb
|
|
72
73
|
- lib/nnq/engine/socket_lifecycle.rb
|
|
73
74
|
- lib/nnq/error.rb
|
|
74
|
-
- lib/nnq/monitor_event.rb
|
|
75
75
|
- lib/nnq/options.rb
|
|
76
76
|
- lib/nnq/pair.rb
|
|
77
77
|
- lib/nnq/pub_sub.rb
|
|
@@ -97,6 +97,7 @@ files:
|
|
|
97
97
|
- lib/nnq/socket.rb
|
|
98
98
|
- lib/nnq/surveyor_respondent.rb
|
|
99
99
|
- lib/nnq/transport/inproc.rb
|
|
100
|
+
- lib/nnq/transport/inproc/pipe.rb
|
|
100
101
|
- lib/nnq/transport/ipc.rb
|
|
101
102
|
- lib/nnq/transport/tcp.rb
|
|
102
103
|
- lib/nnq/version.rb
|