comq 0.18.0 → 0.20.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.
- package/discussions/poison-message-handling.md +14 -10
- package/docs/headers.md +1 -0
- package/features/events.poison.feature +11 -0
- package/features/rpc.call.feature +71 -0
- package/features/rpc.call.recovery.feature +75 -0
- package/features/rpc.call.shards.feature +24 -0
- package/features/rpc.parked.feature +20 -0
- package/features/rpc.timeout.feature +19 -0
- package/features/steps/call.js +462 -0
- package/features/steps/context.js +4 -0
- package/features/steps/events.js +15 -0
- package/features/steps/network.js +8 -0
- package/features/steps/networks.js +24 -4
- package/features/steps/parked.js +102 -0
- package/features/steps/poison.js +3 -2
- package/package.json +1 -1
- package/readme.md +176 -31
- package/source/attributes/recall.js +2 -1
- package/source/channel.js +138 -3
- package/source/events.js +1 -1
- package/source/index.js +6 -0
- package/source/io.js +239 -14
- package/source/shards/channel.js +20 -3
- package/source/unroutable.js +27 -0
- package/source/verdicts.js +54 -0
- package/test/amqplib.mock.js +1 -0
- package/test/channel.held.test.js +237 -0
- package/test/channel.test.js +104 -0
- package/test/connection.mock.js +1 -0
- package/test/io.call.test.js +161 -0
- package/test/io.reply.test.js +66 -0
- package/test/io.request.terms.test.js +247 -0
- package/test/shards/channel.test.js +68 -0
- package/test/verdicts.test.js +57 -0
- package/types/channel.d.ts +3 -0
- package/types/diagnostic.d.ts +1 -1
- package/types/index.d.ts +26 -0
- package/types/io.d.ts +42 -2
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
> became shared and delay-named, the delay became configurable, and message provenance moved to
|
|
7
7
|
> where it survives a retry.
|
|
8
8
|
>
|
|
9
|
-
> **
|
|
10
|
-
>
|
|
11
|
-
> was; it describes what the code now does, except where Part 3 says otherwise.
|
|
9
|
+
> **All three parts are implemented.** Text below is written as a proposal because that is what
|
|
10
|
+
> it was; it describes what the code now does.
|
|
12
11
|
|
|
13
12
|
When a consumer callback rejects, comq is supposed to retry the message a few times and then
|
|
14
13
|
give up on it. What it actually does is kill the process, and take every other consumer in that
|
|
@@ -1111,10 +1110,15 @@ Nothing blocking. The three questions this document opened are resolved:
|
|
|
1111
1110
|
- **The noun** — `Park` / `comq.parked.<queue>`. See Part 1.
|
|
1112
1111
|
- **`Park` on an `io.reply` producer** — rejected as a category error, treated at runtime as a
|
|
1113
1112
|
bare rejection plus a diagnostic. See Part 3.
|
|
1114
|
-
- **Whether Part 3 is built** —
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1113
|
+
- **Whether Part 3 is built** — built. Parts 1 and 2 shipped first as the emergency, and a
|
|
1114
|
+
consumer of comq got retry-then-park by upgrading and writing no code; the verdicts followed
|
|
1115
|
+
as an additive minor.
|
|
1116
|
+
|
|
1117
|
+
One thing the design did not settle, found while building it: it placed the `Park`-from-a-Producer
|
|
1118
|
+
rejection in `#failed`, which cannot implement it — `Channel` takes a topology but not its type
|
|
1119
|
+
([channel.js:64](../source/channel.js#L64)), so it cannot tell the request channel from the event
|
|
1120
|
+
one. It lives in `#getRequestConsumer` ([io.js:297](../source/io.js#L297)) instead, which knows by
|
|
1121
|
+
construction that it is wrapping a `Producer`. And rather than emit a diagnostic nobody may be
|
|
1122
|
+
subscribed to, it rethrows with an explanation, which reaches the parked message as its
|
|
1123
|
+
`x-comq-reason` and both existing diagnostics as their exception — louder, and no new event name
|
|
1124
|
+
to document.
|
package/docs/headers.md
CHANGED
|
@@ -28,6 +28,7 @@ the message properties.
|
|
|
28
28
|
| `x-comq-key` | a retried or parked message | The routing key it was originally published with. |
|
|
29
29
|
| `x-comq-queue` | a parked message | The queue it was consumed from. |
|
|
30
30
|
| `x-comq-reason` | a parked message | The `message` of the exception that ended its attempts. |
|
|
31
|
+
| `x-comq-cause` | a parked message | The `message` of that exception's `cause`, when it has one — a verdict is usually thrown with the failure that prompted it. |
|
|
31
32
|
| `x-comq-at` | a parked message | When it was parked, as `Date.now()`. |
|
|
32
33
|
|
|
33
34
|
`x-comq-exchange` and `x-comq-key` are recorded on the first failure rather than read at parking
|
|
@@ -40,3 +40,14 @@ Feature: Poison events
|
|
|
40
40
|
When an event is emitted to the `poison_open` exchange
|
|
41
41
|
And after 1000ms
|
|
42
42
|
Then `bystander` has received 1 event
|
|
43
|
+
|
|
44
|
+
Scenario: A consumer refusing a message parks it at once
|
|
45
|
+
|
|
46
|
+
A `Park` says the consumer will never process this message, so climbing the
|
|
47
|
+
ladder would only delay the inevitable.
|
|
48
|
+
|
|
49
|
+
Given that events from the `poison_refused` exchange are refused as poison
|
|
50
|
+
When an event is emitted to the `poison_refused` exchange
|
|
51
|
+
Then the event is attempted 1 time
|
|
52
|
+
And the message is parked
|
|
53
|
+
And the parked message is kept, and says it came from the `poison_refused` exchange
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Feature: Calls under a key
|
|
2
|
+
|
|
3
|
+
A call reaches the one connection holding its key. A call under a key nobody holds is refused
|
|
4
|
+
at once, and a holder that stops withdraws its key before it finishes what it holds.
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given an active connection to the broker
|
|
8
|
+
|
|
9
|
+
Scenario: Answered by the holder of the key
|
|
10
|
+
Given a holder answering `sums` under the `a` key:
|
|
11
|
+
"""
|
|
12
|
+
({ a, b }) => a + b
|
|
13
|
+
"""
|
|
14
|
+
When the consumer calls `sums` under the `a` key with:
|
|
15
|
+
"""yaml
|
|
16
|
+
a: 1
|
|
17
|
+
b: 2
|
|
18
|
+
"""
|
|
19
|
+
Then the consumer receives the reply:
|
|
20
|
+
"""yaml
|
|
21
|
+
3
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
Scenario: Answered by the holder of that key only
|
|
25
|
+
Given a holder answering `names` under the `a` key:
|
|
26
|
+
"""
|
|
27
|
+
() => 'a'
|
|
28
|
+
"""
|
|
29
|
+
And a holder answering `names` under the `b` key:
|
|
30
|
+
"""
|
|
31
|
+
() => 'b'
|
|
32
|
+
"""
|
|
33
|
+
When the consumer calls `names` under the `b` key
|
|
34
|
+
Then the consumer receives the reply:
|
|
35
|
+
"""yaml
|
|
36
|
+
b
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
Scenario: Refused when nobody holds the key
|
|
40
|
+
Given a holder answering `names` under the `a` key:
|
|
41
|
+
"""
|
|
42
|
+
() => 'a'
|
|
43
|
+
"""
|
|
44
|
+
When the consumer calls `names` under the `c` key
|
|
45
|
+
Then the call is refused as unroutable
|
|
46
|
+
|
|
47
|
+
Scenario: A sealed holder answers what it holds and refuses what follows
|
|
48
|
+
Given a holder on another connection answering `slow` under the `a` key in 500ms
|
|
49
|
+
When the consumer calls `slow` under the `a` key
|
|
50
|
+
And after 100ms
|
|
51
|
+
And the holder is sealed
|
|
52
|
+
Then the consumer receives the reply
|
|
53
|
+
When the consumer calls `slow` under the `a` key
|
|
54
|
+
Then the call is refused as unroutable
|
|
55
|
+
|
|
56
|
+
Scenario: A second holder waits for the key
|
|
57
|
+
Given a holder on another connection answering `echo` under the `a` key
|
|
58
|
+
When a second holder on another connection holds `echo` under the `a` key
|
|
59
|
+
Then the second holder finds the key taken
|
|
60
|
+
When the first holder disconnects
|
|
61
|
+
Then the second holder holds the key
|
|
62
|
+
When the consumer calls `echo` under the `a` key
|
|
63
|
+
Then the consumer receives the reply
|
|
64
|
+
|
|
65
|
+
Scenario: A caller stops waiting for a holder that crashed
|
|
66
|
+
Given a holder never answering `void` under the `a` key
|
|
67
|
+
When the consumer calls `void` under the `a` key with a 1000ms timeout
|
|
68
|
+
And after 100ms
|
|
69
|
+
And the silent holder crashes
|
|
70
|
+
Then the consumer stops waiting
|
|
71
|
+
And a call to `void` under the `a` key is refused within 10 seconds
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
@heavy
|
|
2
|
+
Feature: Calls under a key across failures
|
|
3
|
+
|
|
4
|
+
A holder that loses its connection holds its key again once the connection is restored, and a
|
|
5
|
+
caller's timeout ends its wait whatever the broker is doing.
|
|
6
|
+
|
|
7
|
+
Scenario: A holder holds its key again after the broker restarts
|
|
8
|
+
Given an active connection to the broker
|
|
9
|
+
And a holder on another connection answering `echo` under the `a` key
|
|
10
|
+
When the broker has crashed
|
|
11
|
+
And the broker is up
|
|
12
|
+
Then a call to `echo` under the `a` key is answered within 60 seconds
|
|
13
|
+
|
|
14
|
+
Scenario: A holder holds its key again once the broker lets its silent connection go
|
|
15
|
+
|
|
16
|
+
The holder reconnects while the broker still holds the connection that went silent, and with
|
|
17
|
+
it the key. The holder finds the key taken until the broker lets that connection go, and
|
|
18
|
+
claims it then.
|
|
19
|
+
|
|
20
|
+
Given watchdog interval is set to 2000ms with 60s AMQP heartbeat
|
|
21
|
+
And a network that can go silent
|
|
22
|
+
And an active connection to the broker
|
|
23
|
+
And a holder answering `echo` under the `a` key:
|
|
24
|
+
"""
|
|
25
|
+
(payload) => payload
|
|
26
|
+
"""
|
|
27
|
+
When the network goes silent
|
|
28
|
+
Then the connection is lost within 10 seconds
|
|
29
|
+
And the holder finds its key taken while the broker holds the silent connection
|
|
30
|
+
When the silent connection is let go
|
|
31
|
+
Then a call to `echo` under the `a` key from another connection is answered within 60 seconds
|
|
32
|
+
|
|
33
|
+
Scenario: A call in flight when the broker crashes ends
|
|
34
|
+
Given an active connection to the broker
|
|
35
|
+
And a holder on another connection answering `slow` under the `a` key in 2000ms
|
|
36
|
+
When the consumer calls `slow` under the `a` key
|
|
37
|
+
And after 200ms
|
|
38
|
+
And the broker has crashed
|
|
39
|
+
And the broker is up
|
|
40
|
+
Then the call ends
|
|
41
|
+
|
|
42
|
+
Scenario: A caller stops waiting for a request at its timeout while the broker is down
|
|
43
|
+
Given an active connection to the broker
|
|
44
|
+
And a producer replying `echo` queue
|
|
45
|
+
When the broker has crashed
|
|
46
|
+
And the consumer sends a request to the `echo` queue with a 1000ms timeout
|
|
47
|
+
Then the consumer stops waiting within 5 seconds
|
|
48
|
+
|
|
49
|
+
Scenario: A caller stops waiting for a call at its timeout while the broker is down
|
|
50
|
+
Given an active connection to the broker
|
|
51
|
+
And a holder on another connection answering `echo` under the `a` key
|
|
52
|
+
When the broker has crashed
|
|
53
|
+
And the consumer calls `echo` under the `a` key with a 1000ms timeout
|
|
54
|
+
Then the consumer stops waiting within 5 seconds
|
|
55
|
+
|
|
56
|
+
Scenario: A request re-sent after the broker restarts expires at its caller's deadline
|
|
57
|
+
|
|
58
|
+
The request is lost with the broker and sent again once the connection is restored, carrying
|
|
59
|
+
the time its caller has left.
|
|
60
|
+
|
|
61
|
+
Given an active connection to the broker
|
|
62
|
+
When the consumer sends a request to the `resent` queue with a 30000ms timeout
|
|
63
|
+
And after 200ms
|
|
64
|
+
And the broker has crashed
|
|
65
|
+
And the broker is up
|
|
66
|
+
Then the consumer stops waiting within 40 seconds
|
|
67
|
+
When a producer counting requests to the `resent` queue
|
|
68
|
+
Then the producer receives nothing
|
|
69
|
+
|
|
70
|
+
Scenario: A call reaches its holder while one of the brokers is down
|
|
71
|
+
Given an active sharded connection
|
|
72
|
+
And a holder on another connection answering `echo` under the `a` key
|
|
73
|
+
When one of the brokers has crashed
|
|
74
|
+
And the consumer calls `echo` under the `a` key 20 times
|
|
75
|
+
Then every call is answered
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Feature: Calls under a key over a sharded connection
|
|
2
|
+
|
|
3
|
+
A call is published to one shard, and a shard where nobody holds the key returns it: it is
|
|
4
|
+
published on the next one, and refused once every shard has returned it.
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given an active sharded connection
|
|
8
|
+
|
|
9
|
+
Scenario: Answered by a holder on one broker only
|
|
10
|
+
Given a holder connected to broker 0 answering `echo` under the `a` key
|
|
11
|
+
When the consumer calls `echo` under the `a` key 20 times
|
|
12
|
+
Then every call is answered
|
|
13
|
+
|
|
14
|
+
Scenario: Refused when nobody holds the key on any broker
|
|
15
|
+
Given a holder connected to broker 0 answering `echo` under the `a` key
|
|
16
|
+
When the consumer calls `echo` under the `b` key
|
|
17
|
+
Then the call is refused as unroutable
|
|
18
|
+
|
|
19
|
+
Scenario: A key taken on one broker is held on the other, and on both once it is let go
|
|
20
|
+
Given a holder connected to broker 0 answering `whom` under the `a` key as `first`
|
|
21
|
+
When a second holder on another connection holds `whom` under the `a` key as `second`
|
|
22
|
+
Then the second holder finds the key taken
|
|
23
|
+
When the first holder disconnects
|
|
24
|
+
Then every call to `whom` under the `a` key is answered by `second` within 30 seconds
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Feature: Parked requests
|
|
2
|
+
|
|
3
|
+
Background:
|
|
4
|
+
Given an active connection to the broker
|
|
5
|
+
|
|
6
|
+
Scenario: A parked request can still be answered
|
|
7
|
+
|
|
8
|
+
A caller that sets no timeout is certain of an eventual answer for as long as it is
|
|
9
|
+
alive, and parking is what keeps that true when no consumer can produce one: the
|
|
10
|
+
request is kept, along with who was asking. Somebody else can answer it, and the
|
|
11
|
+
caller — still waiting, still holding an exclusive reply queue — receives it.
|
|
12
|
+
|
|
13
|
+
A caller that sets a timeout trades that for an end to the wait: once it stops
|
|
14
|
+
waiting, a parked request is addressed to a caller that has gone.
|
|
15
|
+
|
|
16
|
+
Given a producer failing every request to the `unanswerable` queue
|
|
17
|
+
When a request is sent to the `unanswerable` queue
|
|
18
|
+
Then the request is parked
|
|
19
|
+
When the parked request from the `unanswerable` queue is answered by hand
|
|
20
|
+
Then the caller receives the reply
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Feature: Request timeout
|
|
2
|
+
|
|
3
|
+
A caller may say how long it waits for a reply. A request nobody took by then is dropped by the
|
|
4
|
+
broker; one already taken is still processed, and its reply is discarded.
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given an active connection to the broker
|
|
8
|
+
|
|
9
|
+
Scenario: A caller stops waiting at its timeout
|
|
10
|
+
Given a producer replying `slow` queue in 1000ms
|
|
11
|
+
When the consumer sends a request to the `slow` queue with a 200ms timeout
|
|
12
|
+
Then the consumer stops waiting
|
|
13
|
+
|
|
14
|
+
Scenario: A request nobody took in time is never processed
|
|
15
|
+
When the consumer sends a request to the `later` queue with a 200ms timeout
|
|
16
|
+
Then the consumer stops waiting
|
|
17
|
+
When after 300ms
|
|
18
|
+
And a producer counting requests to the `later` queue
|
|
19
|
+
Then the producer receives nothing
|