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.
@@ -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
- > **Parts 1 and 2 are implemented.** Part 3 — the `Retry` / `Park` verdict API — is decided but
10
- > not yet scheduled; see the end. Text below is written as a proposal because that is what it
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** — **decided, not yet scheduled.** Parts 1 and 2 are the emergency
1115
- and do not depend on it; a consumer of comq gets retry-then-park by upgrading and writing no
1116
- code. But the parking queue takes its noun from a verdict class that would not otherwise
1117
- exist, and without verdicts a message a contract has already refused costs every attempt —
1118
- two minutes at the event default — and then lands in the parking queue beside the
1119
- messages that genuinely failed. "Decided, not yet scheduled" rather than "open", because
1120
- people read this deciding whether to build on it.
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