eventhub-processor2 1.28.1 → 1.28.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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +39 -3
- data/lib/eventhub/actor_listener_amqp.rb +6 -0
- data/lib/eventhub/correlation_id.rb +12 -10
- data/lib/eventhub/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd2f7112cce3b298538f8fa33ad0218ced28e2c86a2064b8cd6054df7e5a5718
|
|
4
|
+
data.tar.gz: 759bcd489e715c26146e82982f1c894fa3a97615be8c30334159d719cee8f4a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aeacaf0d8a2878add3b3b7081f8a386cff4bc996fc66f0ca46832d898cd732e71f8f2efd0009510398fc7ef9ef7fa944ec5f66892e2a5304707c79d52f222921
|
|
7
|
+
data.tar.gz: b8b0d57be7bc929b3262b6092e8cac7989c1341071c43c69e7ab1556b78902088d6dc6cc6112eaea48896d053a7d528ed0f1a594a01df845c7a765bd767684f4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog of EventHub::Processor2
|
|
2
2
|
|
|
3
|
+
# 1.28.2 / 2026-05-20
|
|
4
|
+
|
|
5
|
+
* Fix `CorrelationId.current` leaking across messages on the same Bunny consumer thread. `CorrelationId.with` now always saves and restores, even when called with nil/empty. Symptom in production: deadletter messages stamped with an earlier message's `execution_id`.
|
|
6
|
+
* Listener now also resets `correlation_id` after each message (extra safety on top of the fix above, in case some future code writes it outside of `CorrelationId.with`).
|
|
7
|
+
* README: clarify `correlation_id` is the AMQP **property** (envelope-level), not a custom field in the headers table. Producers must set the property; a header named `correlation_id` is invisible to the gem.
|
|
8
|
+
|
|
3
9
|
# 1.28.1 / 2026-05-19
|
|
4
10
|
|
|
5
11
|
* Fix `LoggerProxy` double-wrapping when callers pass a Hash to a logger method. Hash inputs are now merged (not nested under `:message`), so the resulting structured event has the caller's fields at the top level - restoring the behaviour that existed before `LoggerProxy` was introduced in 1.26.0, while keeping automatic thread-local injection of `correlation_id` and `execution_id`. Caller-provided values win over thread-locals via `||=`.
|
data/README.md
CHANGED
|
@@ -117,6 +117,31 @@ I, [2018-02-09T15:22:35.658522 #37966] INFO -- : Listener is starting...
|
|
|
117
117
|
I, [2018-02-09T15:22:35.699161 #37966] INFO -- : Listening to queue [example]
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
+
## Concurrency Model
|
|
121
|
+
|
|
122
|
+
Processor2 runs **one consumer thread per queue** listed in `listener_queues`. Each consumer holds a Bunny worker thread with `prefetch(1)`, so it processes its messages sequentially. Multiple queues run their consumers in parallel.
|
|
123
|
+
|
|
124
|
+
| `listener_queues` | Concurrency |
|
|
125
|
+
|-------------------------------|------------------------------------------------|
|
|
126
|
+
| `["q1"]` | 1 consumer thread, sequential |
|
|
127
|
+
| `["q1", "q2"]` | 2 consumer threads, parallel (one per queue) |
|
|
128
|
+
| `["q1", "q1", "q1"]` | 3 consumer threads, all on the same queue |
|
|
129
|
+
|
|
130
|
+
To increase throughput for a single queue, either list it multiple times in `listener_queues` (each entry gets its own consumer thread) or run multiple processes. The single-process / single-consumer-per-queue model is what the gem is designed for.
|
|
131
|
+
|
|
132
|
+
Correlation context (`correlation_id`, `execution_id`) is held in thread-local storage, so concurrent consumer threads cannot interfere with each other - each message's outbound `correlation_id` is the one it received (or its body's fallback), regardless of what other threads are doing.
|
|
133
|
+
|
|
134
|
+
### A note on `prefetch`
|
|
135
|
+
|
|
136
|
+
The AMQP `prefetch` value is **fixed at 1** and not currently exposed via configuration. Raising it would require:
|
|
137
|
+
|
|
138
|
+
- bumping Bunny's `channel.work_pool.size` in lockstep (raising `prefetch` alone only buffers messages in Bunny - it does not add parallelism);
|
|
139
|
+
- pooling or removing `ActorPublisher`'s single-mailbox bottleneck, otherwise N parallel consumers serialize through one publisher;
|
|
140
|
+
- making `handle_message` a thread-safety contract for gem users (today it is implicitly single-threaded - any `@ivar` on the processor class is safe by accident);
|
|
141
|
+
- making `Statistics` and any other shared state thread-safe.
|
|
142
|
+
|
|
143
|
+
These trade-offs change the gem's call-surface contract, so a configurable `prefetch` would be a minor-version feature, not a patch. For now, the supported way to scale a single queue is to list it multiple times in `listener_queues` or run multiple processes.
|
|
144
|
+
|
|
120
145
|
## Logging
|
|
121
146
|
|
|
122
147
|
By default, Processor2 logs to both stdout (standard format) and a logstash file. For containerized environments (Docker, Kubernetes), use the `--console-log-only` option to output structured JSON logs to stdout only:
|
|
@@ -135,10 +160,21 @@ This outputs logs in JSON format suitable for log aggregation systems:
|
|
|
135
160
|
|
|
136
161
|
Processor2 supports automatic propagation of correlation IDs for distributed tracing. While EventHub messages already contain an `execution_id` in the message body for tracing, the AMQP `correlation_id` provides an additional benefit: it's part of the message metadata (envelope), not the payload. This means it's available even when the JSON body is invalid and cannot be parsed - useful for error tracking and debugging malformed messages.
|
|
137
162
|
|
|
138
|
-
|
|
163
|
+
> **Important: `correlation_id` is an AMQP `property`, not a `header`.**
|
|
164
|
+
>
|
|
165
|
+
> In AMQP 0.9.1, `correlation_id` is one of the 14 standard message *properties* (top-level envelope fields, alongside `message_id`, `reply_to`, `content_type`, etc.). The *headers table* is a separate, optional dictionary inside the property block for application-defined fields. They are not interchangeable.
|
|
166
|
+
>
|
|
167
|
+
> Producers must set `correlation_id` as a property. A custom field called `correlation_id` placed inside the headers table is **invisible to this gem** and will silently fall back to the message body's `execution_id`.
|
|
168
|
+
>
|
|
169
|
+
> Concretely:
|
|
170
|
+
> - In Bunny: `channel.default_exchange.publish(payload, correlation_id: "...")` ✅ property
|
|
171
|
+
> - In Bunny: `... publish(payload, headers: { "correlation_id" => "..." })` ❌ custom header, not seen
|
|
172
|
+
> - In the RabbitMQ Management UI's "Publish message" form: set the value in the **Properties** section, not in the **Headers** section. When inspecting a queued message, look at "Properties → correlation_id", not at "Headers".
|
|
173
|
+
|
|
174
|
+
When an incoming AMQP message includes a `correlation_id` in its metadata (as a property):
|
|
139
175
|
|
|
140
176
|
1. **Automatic logging**: All log messages during message processing will include `correlation_id` as a separate field in structured JSON output
|
|
141
|
-
2. **Automatic publishing**: Any messages published during processing will automatically include the same correlation_id
|
|
177
|
+
2. **Automatic publishing**: Any messages published during processing will automatically include the same correlation_id as an AMQP `correlation_id` property (envelope-level, not in the headers table)
|
|
142
178
|
3. **Available in args**: The correlation_id is passed to `handle_message` via `args[:correlation_id]`
|
|
143
179
|
4. **Consistent execution_id**: When creating new messages, `execution_id` is automatically set to match `correlation_id`, ensuring consistent tracing across both AMQP metadata and message body
|
|
144
180
|
|
|
@@ -174,7 +210,7 @@ If no `correlation_id` is present in the AMQP metadata, the message body's `exec
|
|
|
174
210
|
3. **Stored**: The value is stored in thread-local storage (`Thread.current`) for the duration of message processing
|
|
175
211
|
4. **Passed**: The `correlation_id` is passed to `handle_message` via `args[:correlation_id]`
|
|
176
212
|
5. **Logging**: The logger automatically reads from thread-local storage and includes it in JSON output
|
|
177
|
-
6. **Publishing**: The publisher automatically reads from thread-local storage and
|
|
213
|
+
6. **Publishing**: The publisher automatically reads from thread-local storage and sets it as the outgoing AMQP `correlation_id` property (can be overwritten by passing `correlation_id:` explicitly)
|
|
178
214
|
7. **New messages**: When creating a new `EventHub::Message`:
|
|
179
215
|
- With `correlation_id` present → `execution_id` is set to match `correlation_id`
|
|
180
216
|
- Without `correlation_id` → `execution_id` is set to a new UUID (default behavior)
|
|
@@ -90,6 +90,12 @@ module EventHub
|
|
|
90
90
|
" acknowledged")
|
|
91
91
|
ensure
|
|
92
92
|
ExecutionId.clear
|
|
93
|
+
# Belt-and-suspenders: CorrelationId.with's ensure already
|
|
94
|
+
# restores the prior value, but clearing here protects any
|
|
95
|
+
# future code path that writes CorrelationId.current outside
|
|
96
|
+
# of `.with` (e.g. handle_payload's fallback was the original
|
|
97
|
+
# leak source pre-1.28.2).
|
|
98
|
+
CorrelationId.clear
|
|
93
99
|
end
|
|
94
100
|
end
|
|
95
101
|
queue.subscribe_with(consumer, block: false)
|
|
@@ -16,18 +16,20 @@ module EventHub
|
|
|
16
16
|
Thread.current[:eventhub_correlation_id] = nil
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
# Execute block with correlation_id set, ensures cleanup
|
|
19
|
+
# Execute block with correlation_id set, ensures cleanup.
|
|
20
|
+
#
|
|
21
|
+
# Always saves the prior value and restores it on exit, even when
|
|
22
|
+
# called with nil/empty - otherwise any value written to `current`
|
|
23
|
+
# inside the block (e.g. handle_payload's fallback to the message
|
|
24
|
+
# body's execution_id) would leak onto the consumer thread and be
|
|
25
|
+
# picked up by the next message's processing.
|
|
20
26
|
def with(correlation_id)
|
|
21
|
-
|
|
27
|
+
old_value = current
|
|
28
|
+
begin
|
|
29
|
+
self.current = correlation_id unless correlation_id.nil? || correlation_id.to_s.empty?
|
|
22
30
|
yield
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
begin
|
|
26
|
-
self.current = correlation_id
|
|
27
|
-
yield
|
|
28
|
-
ensure
|
|
29
|
-
self.current = old_value
|
|
30
|
-
end
|
|
31
|
+
ensure
|
|
32
|
+
self.current = old_value
|
|
31
33
|
end
|
|
32
34
|
end
|
|
33
35
|
end
|
data/lib/eventhub/version.rb
CHANGED