omq-qos 0.3.2
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 +101 -0
- data/lib/omq/qos/connection_ext.rb +36 -0
- data/lib/omq/qos/dead_letter.rb +36 -0
- data/lib/omq/qos/dedup_set.rb +69 -0
- data/lib/omq/qos/error_codes.rb +102 -0
- data/lib/omq/qos/hasher.rb +64 -0
- data/lib/omq/qos/lifecycle_ext.rb +106 -0
- data/lib/omq/qos/options_ext.rb +35 -0
- data/lib/omq/qos/peer_registry.rb +146 -0
- data/lib/omq/qos/pending_store.rb +89 -0
- data/lib/omq/qos/retry_scheduler.rb +22 -0
- data/lib/omq/qos/routing_ext.rb +481 -0
- data/lib/omq/qos/socket_ext.rb +173 -0
- data/lib/omq/qos/version.rb +8 -0
- data/lib/omq/qos/zmtp/command_ext.rb +114 -0
- data/lib/omq/qos.rb +307 -0
- metadata +88 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
class QoS
|
|
5
|
+
# Prepended onto Protocol::ZMTP::Codec::Command's singleton class
|
|
6
|
+
# to add ACK/NACK/CLR/COMP command builders.
|
|
7
|
+
#
|
|
8
|
+
module CommandClassExt
|
|
9
|
+
# Builds an ACK command.
|
|
10
|
+
#
|
|
11
|
+
# @param hash_bytes [String] binary hash digest
|
|
12
|
+
# @param algorithm [String] "x" (XXH64) or "s" (SHA-1 truncated)
|
|
13
|
+
def ack(hash_bytes, algorithm: "x")
|
|
14
|
+
new("ACK", "#{algorithm}#{hash_bytes}".b)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Builds a NACK command. The on-wire +error_info+ payload is
|
|
19
|
+
# +[1 byte code] [2 bytes big-endian message length] [message]+
|
|
20
|
+
# per the omq-qos RFC §NACK command.
|
|
21
|
+
#
|
|
22
|
+
# @param hash_bytes [String] binary hash digest
|
|
23
|
+
# @param code [Integer] error code byte (bit 7 = retryable)
|
|
24
|
+
# @param message [String] UTF-8 error description (≤ 65535 bytes)
|
|
25
|
+
# @param algorithm [String] "x" (XXH64) or "s" (SHA-1 truncated)
|
|
26
|
+
def nack(hash_bytes, code: 0, message: "", algorithm: "x")
|
|
27
|
+
msg_bytes = message.b
|
|
28
|
+
error_info = [code, msg_bytes.bytesize].pack("Cn") + msg_bytes
|
|
29
|
+
new("NACK", "#{algorithm}#{hash_bytes}#{error_info}".b)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Builds a CLR ("clear") command. Sent by the sender after
|
|
34
|
+
# receiving an ACK (QoS 2) or COMP (QoS 3) so the receiver may
|
|
35
|
+
# evict the digest from its dedup set immediately instead of
|
|
36
|
+
# waiting for TTL expiry.
|
|
37
|
+
#
|
|
38
|
+
# @param hash_bytes [String] binary hash digest
|
|
39
|
+
# @param algorithm [String] "x" (XXH64) or "s" (SHA-1 truncated)
|
|
40
|
+
def clr(hash_bytes, algorithm: "x")
|
|
41
|
+
new("CLR", "#{algorithm}#{hash_bytes}".b)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Builds a COMP ("complete") command. Sent by the QoS 3 receiver
|
|
46
|
+
# after its application handler returns successfully.
|
|
47
|
+
#
|
|
48
|
+
# @param hash_bytes [String] binary hash digest
|
|
49
|
+
# @param algorithm [String] "x" (XXH64) or "s" (SHA-1 truncated)
|
|
50
|
+
def comp(hash_bytes, algorithm: "x")
|
|
51
|
+
new("COMP", "#{algorithm}#{hash_bytes}".b)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Prepended onto Protocol::ZMTP::Codec::Command to add
|
|
57
|
+
# ACK/NACK/CLR/COMP data extraction methods.
|
|
58
|
+
#
|
|
59
|
+
module CommandExt
|
|
60
|
+
# Extracts algorithm prefix and hash bytes from an ACK command's data.
|
|
61
|
+
#
|
|
62
|
+
# @return [Array(String, String)] [algorithm, hash_bytes]
|
|
63
|
+
def ack_data
|
|
64
|
+
algo = @data.byteslice(0, 1)
|
|
65
|
+
hash_size = Protocol::ZMTP::Codec::Command::ACK_HASH_SIZES.fetch(algo, 8)
|
|
66
|
+
[algo, @data.byteslice(1, hash_size)]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Same layout as ACK.
|
|
71
|
+
#
|
|
72
|
+
# @return [Array(String, String)] [algorithm, hash_bytes]
|
|
73
|
+
def clr_data
|
|
74
|
+
ack_data
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Same layout as ACK — COMP carries only the digest.
|
|
79
|
+
#
|
|
80
|
+
# @return [Array(String, String)] [algorithm, hash_bytes]
|
|
81
|
+
def comp_data
|
|
82
|
+
ack_data
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Extracts algorithm, hash bytes, NACK code, and error message
|
|
87
|
+
# from a NACK command's data. See {CommandClassExt.nack} for the
|
|
88
|
+
# wire layout.
|
|
89
|
+
#
|
|
90
|
+
# @return [Array(String, String, Integer, String)]
|
|
91
|
+
# [algorithm, hash_bytes, code, message]
|
|
92
|
+
def nack_data
|
|
93
|
+
algo = @data.byteslice(0, 1)
|
|
94
|
+
hash_size = Protocol::ZMTP::Codec::Command::ACK_HASH_SIZES.fetch(algo, 8)
|
|
95
|
+
hash = @data.byteslice(1, hash_size)
|
|
96
|
+
off = 1 + hash_size
|
|
97
|
+
header = @data.byteslice(off, 3)
|
|
98
|
+
|
|
99
|
+
if header && header.bytesize == 3
|
|
100
|
+
code, msg_len = header.unpack("Cn")
|
|
101
|
+
message = @data.byteslice(off + 3, msg_len) || "".b
|
|
102
|
+
else
|
|
103
|
+
code, message = 0, "".b
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
[algo, hash, code, message]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# Known hash digest sizes by algorithm prefix.
|
|
114
|
+
Protocol::ZMTP::Codec::Command::ACK_HASH_SIZES = { "x" => 8, "s" => 8 }.freeze
|
data/lib/omq/qos.rb
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# OMQ QoS — per-hop delivery guarantees for OMQ sockets.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# require "omq"
|
|
7
|
+
# require "omq/qos"
|
|
8
|
+
#
|
|
9
|
+
# push = OMQ::PUSH.new
|
|
10
|
+
# push.qos = OMQ::QoS.exactly_once(dead_letter_timeout: 30)
|
|
11
|
+
# push.connect("tcp://127.0.0.1:5555")
|
|
12
|
+
# promise = push.send("guaranteed delivery")
|
|
13
|
+
# case promise.wait
|
|
14
|
+
# in :delivered then :ok
|
|
15
|
+
# in OMQ::QoS::DeadLetter => dl then retry_queue << dl.parts
|
|
16
|
+
# end
|
|
17
|
+
|
|
18
|
+
require "async/semaphore"
|
|
19
|
+
|
|
20
|
+
require "omq"
|
|
21
|
+
|
|
22
|
+
require_relative "qos/version"
|
|
23
|
+
require_relative "qos/zmtp/command_ext"
|
|
24
|
+
require_relative "qos/hasher"
|
|
25
|
+
require_relative "qos/pending_store"
|
|
26
|
+
require_relative "qos/peer_registry"
|
|
27
|
+
require_relative "qos/dedup_set"
|
|
28
|
+
require_relative "qos/dead_letter"
|
|
29
|
+
require_relative "qos/error_codes"
|
|
30
|
+
require_relative "qos/retry_scheduler"
|
|
31
|
+
require_relative "qos/connection_ext"
|
|
32
|
+
require_relative "qos/lifecycle_ext"
|
|
33
|
+
require_relative "qos/routing_ext"
|
|
34
|
+
require_relative "qos/options_ext"
|
|
35
|
+
require_relative "qos/socket_ext"
|
|
36
|
+
|
|
37
|
+
module OMQ
|
|
38
|
+
# Per-hop delivery guarantee handle for a single socket.
|
|
39
|
+
#
|
|
40
|
+
# A {QoS} instance bundles both the user-visible configuration (level,
|
|
41
|
+
# hash-algorithm preference, timeouts, retry caps) and the socket-wide
|
|
42
|
+
# runtime state needed for levels >= 2 ({PeerRegistry}, a bounded-slot
|
|
43
|
+
# {Async::Semaphore} for backpressure, per-connection {DedupSet}s, and
|
|
44
|
+
# the dead-letter sweep task).
|
|
45
|
+
#
|
|
46
|
+
# Construct via the class-method builders — never via `.new(level:)`
|
|
47
|
+
# directly.
|
|
48
|
+
#
|
|
49
|
+
# socket.qos = nil # QoS 0 (default)
|
|
50
|
+
# socket.qos = OMQ::QoS.at_least_once # QoS 1
|
|
51
|
+
# socket.qos = OMQ::QoS.exactly_once(dead_letter_timeout: 30) # QoS 2
|
|
52
|
+
# socket.qos = OMQ::QoS.exactly_once_and_processed( # QoS 3
|
|
53
|
+
# max_retries: 5, processing_timeout: 1.0,
|
|
54
|
+
# )
|
|
55
|
+
#
|
|
56
|
+
# Each socket gets its own instance. The same {QoS} cannot be shared
|
|
57
|
+
# across sockets — {#attach!} rejects a second engine.
|
|
58
|
+
#
|
|
59
|
+
class QoS
|
|
60
|
+
DEFAULT_DEAD_LETTER_TIMEOUT = 60.0
|
|
61
|
+
DEFAULT_DEDUP_TTL = 60.0
|
|
62
|
+
DEFAULT_MAX_RETRIES = 3
|
|
63
|
+
DEFAULT_RETRY_BACKOFF = (0.1..10.0).freeze
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Data outcome pushed through `#send`'s Promise when a message
|
|
67
|
+
# cannot be delivered under the requested guarantee.
|
|
68
|
+
#
|
|
69
|
+
# @!attribute [r] parts
|
|
70
|
+
# @return [Array<String>] the original message frames
|
|
71
|
+
# @!attribute [r] reason
|
|
72
|
+
# @return [Symbol] +:peer_timeout+, +:terminal_nack+, +:retry_exhausted+, +:socket_closed+
|
|
73
|
+
# @!attribute [r] peer_info
|
|
74
|
+
# @return [Protocol::ZMTP::PeerInfo, nil] peer it was pinned to, if any
|
|
75
|
+
# @!attribute [r] error
|
|
76
|
+
# @return [NackInfo, nil] NACK details at QoS 3; nil at QoS 2
|
|
77
|
+
DeadLetter = Data.define(:parts, :reason, :peer_info, :error)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Builds a QoS 1 (at-least-once) handle.
|
|
81
|
+
#
|
|
82
|
+
# @param hash_algos [String] preference list, e.g. +"xs"+
|
|
83
|
+
# @return [QoS]
|
|
84
|
+
def self.at_least_once(hash_algos: SUPPORTED_HASH_ALGOS)
|
|
85
|
+
new(level: 1, hash_algos: hash_algos)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Builds a QoS 2 (exactly-once with peer pinning) handle.
|
|
90
|
+
#
|
|
91
|
+
# @param hash_algos [String]
|
|
92
|
+
# @param dead_letter_timeout [Numeric] seconds to wait for a disconnected
|
|
93
|
+
# peer to return before dead-lettering its pending messages
|
|
94
|
+
# @param dedup_ttl [Numeric] seconds a receiver keeps a digest in its
|
|
95
|
+
# dedup set before it may evict it
|
|
96
|
+
# @return [QoS]
|
|
97
|
+
def self.exactly_once(
|
|
98
|
+
hash_algos: SUPPORTED_HASH_ALGOS,
|
|
99
|
+
dead_letter_timeout: DEFAULT_DEAD_LETTER_TIMEOUT,
|
|
100
|
+
dedup_ttl: DEFAULT_DEDUP_TTL
|
|
101
|
+
)
|
|
102
|
+
new(
|
|
103
|
+
level: 2,
|
|
104
|
+
hash_algos: hash_algos,
|
|
105
|
+
dead_letter_timeout: dead_letter_timeout,
|
|
106
|
+
dedup_ttl: dedup_ttl,
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Builds a QoS 3 (exactly-once + application-level COMP/NACK) handle.
|
|
112
|
+
#
|
|
113
|
+
# @param hash_algos [String]
|
|
114
|
+
# @param dead_letter_timeout [Numeric]
|
|
115
|
+
# @param dedup_ttl [Numeric]
|
|
116
|
+
# @param max_retries [Integer] retry cap on retryable NACKs
|
|
117
|
+
# @param processing_timeout [Numeric, nil] per-message receiver handler deadline
|
|
118
|
+
# @param retry_backoff [Range] exponential backoff bounds (min..max, seconds)
|
|
119
|
+
# @return [QoS]
|
|
120
|
+
def self.exactly_once_and_processed(
|
|
121
|
+
hash_algos: SUPPORTED_HASH_ALGOS,
|
|
122
|
+
dead_letter_timeout: DEFAULT_DEAD_LETTER_TIMEOUT,
|
|
123
|
+
dedup_ttl: DEFAULT_DEDUP_TTL,
|
|
124
|
+
max_retries: DEFAULT_MAX_RETRIES,
|
|
125
|
+
processing_timeout: nil,
|
|
126
|
+
retry_backoff: DEFAULT_RETRY_BACKOFF
|
|
127
|
+
)
|
|
128
|
+
new(
|
|
129
|
+
level: 3,
|
|
130
|
+
hash_algos: hash_algos,
|
|
131
|
+
dead_letter_timeout: dead_letter_timeout,
|
|
132
|
+
dedup_ttl: dedup_ttl,
|
|
133
|
+
max_retries: max_retries,
|
|
134
|
+
processing_timeout: processing_timeout,
|
|
135
|
+
retry_backoff: retry_backoff,
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
attr_reader :level, :hash_algos,
|
|
141
|
+
:dead_letter_timeout, :dedup_ttl,
|
|
142
|
+
:max_retries, :processing_timeout, :retry_backoff
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Constructed only via the class-method builders.
|
|
146
|
+
def initialize(level:,
|
|
147
|
+
hash_algos: SUPPORTED_HASH_ALGOS,
|
|
148
|
+
dead_letter_timeout: nil,
|
|
149
|
+
dedup_ttl: nil,
|
|
150
|
+
max_retries: nil,
|
|
151
|
+
processing_timeout: nil,
|
|
152
|
+
retry_backoff: nil)
|
|
153
|
+
@level = level
|
|
154
|
+
@hash_algos = hash_algos
|
|
155
|
+
@dead_letter_timeout = dead_letter_timeout
|
|
156
|
+
@dedup_ttl = dedup_ttl
|
|
157
|
+
@max_retries = max_retries
|
|
158
|
+
@processing_timeout = processing_timeout
|
|
159
|
+
@retry_backoff = retry_backoff
|
|
160
|
+
|
|
161
|
+
@engine = nil
|
|
162
|
+
@peer_registry = nil
|
|
163
|
+
@send_semaphore = nil
|
|
164
|
+
@dedup_sets = nil
|
|
165
|
+
@sweep_task = nil
|
|
166
|
+
@enqueue_promises = nil
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# Registers the {Async::Promise} associated with an outgoing
|
|
171
|
+
# +parts+ array keyed by +parts.object_id+. The routing-layer
|
|
172
|
+
# {RoundRobinExt#write_batch} looks it up (via
|
|
173
|
+
# {#take_enqueue_promise}) right before handing the entry to the
|
|
174
|
+
# {PeerRegistry}. Using object_id on a freshly-frozen Array that
|
|
175
|
+
# lives in the send queue avoids reshaping the send queue itself
|
|
176
|
+
# (which would otherwise need a Data wrapper every consumer has
|
|
177
|
+
# to know about).
|
|
178
|
+
#
|
|
179
|
+
# @param parts [Array<String>]
|
|
180
|
+
# @param promise [Async::Promise]
|
|
181
|
+
def enqueue_promise(parts, promise)
|
|
182
|
+
(@enqueue_promises ||= {})[parts.object_id] = promise
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# Removes and returns the Promise previously registered for +parts+.
|
|
187
|
+
# nil if none (e.g. message was enqueued before QoS upgrade — should
|
|
188
|
+
# not happen under the rules enforced by {SocketExt#qos=}).
|
|
189
|
+
def take_enqueue_promise(parts)
|
|
190
|
+
@enqueue_promises&.delete(parts.object_id)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# Binds this QoS handle to a socket's engine. Called by
|
|
195
|
+
# {SocketExt#qos=} once the instance is assigned.
|
|
196
|
+
#
|
|
197
|
+
# @param engine [OMQ::Engine]
|
|
198
|
+
# @return [void]
|
|
199
|
+
# @raise [ArgumentError] if already attached to a (possibly different) engine
|
|
200
|
+
def attach!(engine)
|
|
201
|
+
if @engine && !@engine.equal?(engine)
|
|
202
|
+
raise ArgumentError, "OMQ::QoS instance is already attached to a different socket"
|
|
203
|
+
end
|
|
204
|
+
@engine = engine
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# @return [Boolean]
|
|
209
|
+
def attached?
|
|
210
|
+
!@engine.nil?
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# Lazily built pending-message registry (QoS >= 2). Keyed by
|
|
215
|
+
# {Protocol::ZMTP::PeerInfo} so entries survive reconnects.
|
|
216
|
+
#
|
|
217
|
+
# @return [PeerRegistry]
|
|
218
|
+
def peer_registry
|
|
219
|
+
@peer_registry ||= PeerRegistry.new(capacity: @engine.options.send_hwm)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# Lazily built bounded-slot semaphore. Bounds the number of
|
|
224
|
+
# in-flight (un-ACK'd / un-COMP'd) messages at +send_hwm+; senders
|
|
225
|
+
# acquire a slot before tracking and release on ACK/COMP/dead-letter.
|
|
226
|
+
#
|
|
227
|
+
# @return [Async::Semaphore]
|
|
228
|
+
def send_semaphore
|
|
229
|
+
@send_semaphore ||= Async::Semaphore.new(@engine.options.send_hwm)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# Lazily built per-connection dedup set for the receive side (QoS >= 2).
|
|
234
|
+
#
|
|
235
|
+
# @param conn [Protocol::ZMTP::Connection]
|
|
236
|
+
# @return [DedupSet]
|
|
237
|
+
def dedup_set_for(conn)
|
|
238
|
+
(@dedup_sets ||= {})[conn] ||= DedupSet.new(capacity: @engine.options.recv_hwm)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# Drops the dedup set for a connection (called on disconnect).
|
|
243
|
+
def forget_dedup_set(conn)
|
|
244
|
+
@dedup_sets&.delete(conn)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# Tears down socket-scoped state. Called from {OMQ::Socket#close}.
|
|
249
|
+
# Resolves any pending Promises with +DeadLetter(reason: :socket_closed)+
|
|
250
|
+
# so no fiber hangs on +#wait+.
|
|
251
|
+
#
|
|
252
|
+
# @return [void]
|
|
253
|
+
def shutdown
|
|
254
|
+
@sweep_task&.stop
|
|
255
|
+
@sweep_task = nil
|
|
256
|
+
@peer_registry&.drain_with_dead_letter(:socket_closed)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# Spawns a task on the engine's task tree. Used by the QoS 3 retry
|
|
261
|
+
# scheduler and internal sweep fiber.
|
|
262
|
+
#
|
|
263
|
+
# @param annotation [String]
|
|
264
|
+
# @yield the task body
|
|
265
|
+
# @return [Async::Task]
|
|
266
|
+
def spawn_task(annotation:, &block)
|
|
267
|
+
@engine.spawn_pump_task(annotation: annotation, &block)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# Starts the per-socket dead-letter sweep fiber (idempotent).
|
|
272
|
+
# Called from the routing layer once the first connection reaches
|
|
273
|
+
# the handshake-ready point — that's when +Async::Task.current+ is
|
|
274
|
+
# guaranteed to be inside the engine's task tree.
|
|
275
|
+
#
|
|
276
|
+
# @return [void]
|
|
277
|
+
def ensure_sweep_task_started
|
|
278
|
+
return if @sweep_task
|
|
279
|
+
return unless @level >= 2 && @dead_letter_timeout
|
|
280
|
+
|
|
281
|
+
interval = @dead_letter_timeout / 4.0
|
|
282
|
+
@sweep_task = @engine.spawn_pump_task(annotation: "qos dead-letter sweep") do
|
|
283
|
+
loop do
|
|
284
|
+
sleep interval
|
|
285
|
+
next unless @peer_registry
|
|
286
|
+
|
|
287
|
+
expired = @peer_registry.sweep_dead_letters(Async::Clock.now, @dead_letter_timeout)
|
|
288
|
+
expired.each do |entry, peer_info|
|
|
289
|
+
QoS.dead_letter(entry, peer_info: peer_info, reason: :peer_timeout, semaphore: @send_semaphore)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Wire up prepends.
|
|
298
|
+
Protocol::ZMTP::Codec::Command.singleton_class.prepend(OMQ::QoS::CommandClassExt)
|
|
299
|
+
Protocol::ZMTP::Codec::Command.prepend(OMQ::QoS::CommandExt)
|
|
300
|
+
|
|
301
|
+
OMQ::Routing::RoundRobin.prepend(OMQ::QoS::RoundRobinExt)
|
|
302
|
+
OMQ::Routing::Push.prepend(OMQ::QoS::PushExt)
|
|
303
|
+
OMQ::Routing::Pull.prepend(OMQ::QoS::PullExt)
|
|
304
|
+
|
|
305
|
+
# Draft socket types from optional extension gems.
|
|
306
|
+
OMQ::Routing::Scatter.prepend(OMQ::QoS::ScatterExt) if defined?(OMQ::Routing::Scatter)
|
|
307
|
+
OMQ::Routing::Gather.prepend(OMQ::QoS::GatherExt) if defined?(OMQ::Routing::Gather)
|
metadata
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: omq-qos
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.3.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Patrik Wenger
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: omq
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.28'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.28'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: xxhash
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
description: 'Experimental Quality of Service plugin for the OMQ pure-Ruby ZeroMQ
|
|
41
|
+
library. Adds per-hop delivery guarantees: QoS 1 (at-least-once), QoS 2 (exactly-once
|
|
42
|
+
with peer pinning and dedup), and QoS 3 (exactly-once plus application-level COMP/NACK),
|
|
43
|
+
using ACK/NACK command frames and xxHash message identification.'
|
|
44
|
+
email:
|
|
45
|
+
- paddor@gmail.com
|
|
46
|
+
executables: []
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- LICENSE
|
|
51
|
+
- README.md
|
|
52
|
+
- lib/omq/qos.rb
|
|
53
|
+
- lib/omq/qos/connection_ext.rb
|
|
54
|
+
- lib/omq/qos/dead_letter.rb
|
|
55
|
+
- lib/omq/qos/dedup_set.rb
|
|
56
|
+
- lib/omq/qos/error_codes.rb
|
|
57
|
+
- lib/omq/qos/hasher.rb
|
|
58
|
+
- lib/omq/qos/lifecycle_ext.rb
|
|
59
|
+
- lib/omq/qos/options_ext.rb
|
|
60
|
+
- lib/omq/qos/peer_registry.rb
|
|
61
|
+
- lib/omq/qos/pending_store.rb
|
|
62
|
+
- lib/omq/qos/retry_scheduler.rb
|
|
63
|
+
- lib/omq/qos/routing_ext.rb
|
|
64
|
+
- lib/omq/qos/socket_ext.rb
|
|
65
|
+
- lib/omq/qos/version.rb
|
|
66
|
+
- lib/omq/qos/zmtp/command_ext.rb
|
|
67
|
+
homepage: https://github.com/zeromq/omq.rb/tree/main/gems/omq-qos
|
|
68
|
+
licenses:
|
|
69
|
+
- ISC
|
|
70
|
+
metadata: {}
|
|
71
|
+
rdoc_options: []
|
|
72
|
+
require_paths:
|
|
73
|
+
- lib
|
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '3.3'
|
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '0'
|
|
84
|
+
requirements: []
|
|
85
|
+
rubygems_version: 4.0.16
|
|
86
|
+
specification_version: 4
|
|
87
|
+
summary: Experimental QoS delivery guarantees for OMQ
|
|
88
|
+
test_files: []
|