omq 0.17.6 → 0.17.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b990bcb03502bd0cc09d92cf0c402dfeca1602faec96ecfce85500f0c1d2aa7b
4
- data.tar.gz: 129e85fa71560045d6bba7d1fbb3c21c8c12cba112c840e70802fc81606fe23b
3
+ metadata.gz: 684f878ff9a1731c4820ec6a6ed52393b727fa046b3d8ff74dfbb4fd50e20e48
4
+ data.tar.gz: f5ffad6b1c827949a3c17088536af9a7b7e3f329231767b299cb5bd5a0f73628
5
5
  SHA512:
6
- metadata.gz: e729dc6c1ff1db46cba68feeb0bd017cf09049a1a65b7e908b54cbcf398851ecb04c4ee5dae8ee35244061638e85c1314dcee9cfa458026c30c7fe27543689ee
7
- data.tar.gz: ea9d7aa704da50ff2cb00ecd254888dfb613e27d65a3c2e72bf5f6330f458060417da18f7c84f56902b3d53ea77c89d98a71837d33dafe2cbc0b6b03fb2d9cc5
6
+ metadata.gz: ec98f8caf95091396b6a7f4e3ed01480700c4ba7fb14d3c85dc20572e6fd2cf3160f1f081f5390d79834d8b9db8271a6f8104c34400562c874fbf61c2763cfab
7
+ data.tar.gz: 15a546c0717ab0440aeb010bd9fca87f01f63bffe4eba91fa852b2a48bb99864994c366778114e116463c715dbab11e218256602c662d394dc6a17b5657e683f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.8 — 2026-04-10
4
+
5
+ ### Fixed
6
+
7
+ - **Linger drain missed in-flight messages.** `RoundRobin#send_queues_drained?`
8
+ now tracks an `@in_flight` counter for messages dequeued by pump fibers but
9
+ not yet written. Previously, linger could tear down connections while pumps
10
+ still held unwritten batches, dropping messages silently.
11
+
12
+ ## 0.17.7 — 2026-04-10
13
+
14
+ ### Changed
15
+
16
+ - **Message parts coerced via `#to_s`.** `#frozen_binary` now calls
17
+ `#to_s` instead of `#to_str`, so `nil` becomes an empty frame and
18
+ integers/symbols are converted automatically. A cached `EMPTY_PART`
19
+ avoids allocations for nil parts.
20
+
21
+ - **Reduced allocations on hot paths.** `freeze_message` short-circuits
22
+ when all parts are already frozen binary (zero-alloc fast path).
23
+ `write_batch` passes the batch directly instead of `.map`-ing through
24
+ `transform_send` — only REQ overrides the transform and it never
25
+ batches. Up to +55% throughput on small messages (PUSH/PULL IPC 64B).
26
+
3
27
  ## 0.17.6 — 2026-04-10
4
28
 
5
29
  ### Fixed
@@ -20,9 +20,10 @@ module OMQ
20
20
  #
21
21
  module RoundRobin
22
22
  # @return [Boolean] true when the shared send queue is empty
23
+ # and no pump fiber is mid-write with a dequeued batch.
23
24
  #
24
25
  def send_queues_drained?
25
- @send_queue.empty?
26
+ @send_queue.empty? && @in_flight == 0
26
27
  end
27
28
 
28
29
  private
@@ -36,6 +37,7 @@ module OMQ
36
37
  @send_queue = Routing.build_queue(engine.options.send_hwm, :block)
37
38
  @direct_pipe = nil
38
39
  @conn_send_tasks = {} # conn => send pump task
40
+ @in_flight = 0 # messages dequeued but not yet written
39
41
  end
40
42
 
41
43
 
@@ -126,7 +128,12 @@ module OMQ
126
128
  loop do
127
129
  batch = [@send_queue.dequeue]
128
130
  drain_send_queue_capped(batch)
129
- write_batch(conn, batch)
131
+ @in_flight += batch.size
132
+ begin
133
+ write_batch(conn, batch)
134
+ ensure
135
+ @in_flight -= batch.size
136
+ end
130
137
  batch.each { |parts| @engine.emit_verbose_monitor_event(:message_sent, parts: parts) }
131
138
  Async::Task.current.yield
132
139
  end
@@ -154,10 +161,7 @@ module OMQ
154
161
  if batch.size == 1
155
162
  conn.send_message(transform_send(batch[0]))
156
163
  else
157
- # Single mutex acquisition for the whole batch (up to
158
- # BATCH_MSG_CAP messages). transform_send is identity for
159
- # most routings and only allocates a new parts array for REQ.
160
- conn.write_messages(batch.map { |parts| transform_send(parts) })
164
+ conn.write_messages(batch)
161
165
  conn.flush
162
166
  end
163
167
  end
data/lib/omq/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OMQ
4
- VERSION = "0.17.6"
4
+ VERSION = "0.17.8"
5
5
  end
data/lib/omq/writable.rb CHANGED
@@ -39,17 +39,25 @@ module OMQ
39
39
  def freeze_message(message)
40
40
  parts = message.is_a?(Array) ? message : [message]
41
41
  raise ArgumentError, "message has no parts" if parts.empty?
42
+
43
+ # Fast path: skip map when all parts are already frozen binary.
42
44
  if parts.frozen?
45
+ return parts if parts.all? { |p| p.is_a?(String) && p.frozen? && p.encoding == Encoding::BINARY }
43
46
  parts = parts.map { |p| frozen_binary(p) }
44
47
  else
45
- parts.map! { |p| frozen_binary(p) }
48
+ unless parts.all? { |p| p.is_a?(String) && p.frozen? && p.encoding == Encoding::BINARY }
49
+ parts.map! { |p| frozen_binary(p) }
50
+ end
46
51
  end
47
52
  parts.freeze
48
53
  end
49
54
 
50
55
 
51
- def frozen_binary(str)
52
- s = str.to_str
56
+ EMPTY_PART = "".b.freeze
57
+
58
+ def frozen_binary(obj)
59
+ return EMPTY_PART if obj.nil?
60
+ s = obj.to_s
53
61
  return s if s.frozen? && s.encoding == Encoding::BINARY
54
62
  s.b.freeze
55
63
  end
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.17.6
4
+ version: 0.17.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger