nats-pure 2.5.0 → 2.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +90 -0
- data/README.md +83 -0
- data/lib/nats/io/client.rb +173 -60
- data/lib/nats/io/jetstream/api.rb +213 -5
- data/lib/nats/io/jetstream/errors.rb +25 -0
- data/lib/nats/io/jetstream/header.rb +115 -0
- data/lib/nats/io/jetstream/js/header.rb +1 -0
- data/lib/nats/io/jetstream/js/status.rb +1 -0
- data/lib/nats/io/jetstream/js.rb +30 -2
- data/lib/nats/io/jetstream/manager.rb +247 -67
- data/lib/nats/io/jetstream/msg/ack.rb +7 -3
- data/lib/nats/io/jetstream/msg/ack_methods.rb +14 -4
- data/lib/nats/io/jetstream/msg/metadata.rb +2 -1
- data/lib/nats/io/jetstream/pull_subscription.rb +288 -190
- data/lib/nats/io/jetstream.rb +98 -9
- data/lib/nats/io/kv/api.rb +4 -0
- data/lib/nats/io/kv/bucket_status.rb +12 -0
- data/lib/nats/io/kv/manager.rb +7 -1
- data/lib/nats/io/kv.rb +21 -15
- data/lib/nats/io/parser.rb +3 -3
- data/lib/nats/io/subscription.rb +18 -10
- data/lib/nats/io/version.rb +2 -2
- data/lib/nats/io/websocket.rb +32 -12
- data/lib/nats/service/validator.rb +2 -2
- data/sig/nats/io/jetstream/api.rbs +12 -3
- data/sig/nats/io/jetstream/errors.rbs +12 -0
- data/sig/nats/io/jetstream/header.rbs +33 -0
- data/sig/nats/io/jetstream/js/header.rbs +1 -0
- data/sig/nats/io/jetstream/js/status.rbs +1 -0
- data/sig/nats/io/jetstream/js.rbs +3 -1
- data/sig/nats/io/jetstream/manager.rbs +21 -1
- data/sig/nats/io/jetstream/pull_subscription.rbs +40 -2
- data/sig/nats/io/jetstream.rbs +9 -1
- data/sig/nats/io/kv/bucket_status.rbs +4 -0
- metadata +5 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 18d1e6a53bcd6c3dc5c3fe1aa9f03c5b1d17a68fdf6381bae8f7ef3c6a6995b3
|
|
4
|
+
data.tar.gz: ce0f4719c5902ad9b7bec424f341cab7f39a980de31452fe445fbe6733a401d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 48591880c765700baac0ab6762fc437ef1da21cd728c43f64886547da8607208f8b68599cb75802ca3bc82d1e10e6c3875c9eac9c86e9ec955f035049d4e25db
|
|
7
|
+
data.tar.gz: 6de527742e1cb8a380f2806dae0f365612f7a6dbc622ebda50bc9cb46d3a4221dc296308c4c82b9c72c108dc0ff5462138f31aaf8898429d1028e1b7b2c50573
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
1
|
# Change log
|
|
2
2
|
|
|
3
3
|
## main
|
|
4
|
+
|
|
5
|
+
## v2.7.0
|
|
6
|
+
|
|
7
|
+
This release adds the JetStream features of nats-server 2.10 to 2.14 that the
|
|
8
|
+
client lacked, such as per-message TTLs, message schedules, and consumers that
|
|
9
|
+
can be paused, reset and serve priority groups. `fetch` now pulls once and
|
|
10
|
+
waits, like `Fetch` of the nats.go `jetstream` package, and a subscription read
|
|
11
|
+
with `next_msg` no longer drops every message after its first 64 MiB. Code that
|
|
12
|
+
passed stream or KV settings the client used to drop now sends them; see Changed.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- JetStream: `fetch(batch, no_wait: true)` takes what the server delivers at once, without waiting for more messages, like `FetchNoWait` of nats.go and `fetch` of nats.rs: an empty Array when none are pending, or when other pulls wait for more than are pending. While the consumer may not deliver more until some are acked (`max_ack_pending`), the server holds the pull back, and the fetch returns what it got once its timeout is up, 1 second by default. (#195)
|
|
17
|
+
- JetStream: `fetch` takes a block, to which it passes each delivery as it comes, in the calling thread, so that the message can be acked before the `ack_wait` of the consumer is up: `psub.fetch(100, timeout: 60) { |msg| msg.ack }`. It still returns the messages. (#195)
|
|
18
|
+
- JetStream: stream configs take the settings added in nats-server 2.10: `compression`, `first_seq`, `subject_transform` and `consumer_limits` (whose `inactive_threshold` is in nanoseconds, like the other stream durations). They used to be dropped, so a stream could not be created with them, and sending a fetched config back as an update left them out. (#191)
|
|
19
|
+
- JetStream: stream configs take the settings added in nats-server 2.11 to 2.14: `allow_msg_ttl` and `subject_delete_marker_ttl` (2.11; the latter in nanoseconds, like the other stream durations), `allow_msg_counter`, `allow_atomic`, `allow_msg_schedules` and `persist_mode` (2.12), and `allow_batched` (2.14). Like nats.go, the client does not send them at their defaults, such as `false`, as servers since 2.12, and 2.11 ones in strict mode, refuse settings they do not know. (#194)
|
|
20
|
+
- JetStream: `NATS::JetStream::Header` names the JetStream headers: those for deduplication and optimistic concurrency, such as `MSG_ID` and `EXPECTED_LAST_SUBJECT_SEQUENCE`, and those of nats-server 2.11 to 2.14: `EXPECTED_LAST_SUBJECT_SEQUENCE_SUBJECT`, `MSG_TTL` and `MARKER_REASON` (2.11), the atomic batch headers (2.12; `BATCH_COMMIT` `"eob"` needs 2.14), the message schedule headers (2.12 and 2.14), and `REQUIRED_API_LEVEL` (2.12), which JetStream API requests send as a `header:` option, as in `js.stream_info(name, header: {...})`. It also names the headers that the server sets on the messages it republishes, sources or returns from direct gets, such as `STREAM` and `SEQUENCE`. (#194)
|
|
21
|
+
- JetStream: `js.publish` takes `ttl:`, the whole seconds after which the stream removes the message, or `:never` to keep it past the stream's `max_age`, for streams with `allow_msg_ttl` (nats-server 2.11). Other values raise `ArgumentError`. (#194)
|
|
22
|
+
- JetStream: `js.publish` takes `schedule:`, which makes the message a schedule that publishes it to its `target:` subject: once, `at:` a Time (nats-server 2.12), `every:` so many seconds, or on a `cron:` expression with seconds, in UTC or a `time_zone:` (2.14). The schedule can give the messages it publishes a `ttl:` (2.12), and publish the last message of a `source:` subject instead and roll up the target (`rollup: true`) (2.14; 2.12 ignores both). The stream needs `allow_msg_schedules`. An invalid schedule raises `ArgumentError`, before anything is sent; `NATS::JetStream::Header::SCHEDULE_NEXT` and `SCHEDULER` cancel a schedule. (#194)
|
|
23
|
+
- JetStream: `stream_info` reports the stream's `mirror` and `sources`, including their subject transforms, and both `stream_info` and `consumer_info` report when the server reported the info, as `ts`. (#191)
|
|
24
|
+
- JetStream: `create_consumer` and `update_consumer` use the create and update actions of nats-server 2.10. Creating a consumer that exists with a different config raises `NATS::JetStream::Error::ConsumerAlreadyExists`, and updating one that does not exist raises `NATS::JetStream::Error::ConsumerDoesNotExist`; both are `BadRequest` errors. An update replaces the whole config, so fields left out take their defaults. `add_consumer` still creates or updates. (#191)
|
|
25
|
+
- JetStream: `msg.term(reason: "...")` passes the reason on to the server's advisory for the terminated message. Servers before 2.10.4 do not terminate a message given a reason. (#191)
|
|
26
|
+
- JetStream: consumers can be paused (nats-server 2.11): created paused with `pause_until` in their config, as a `Time` or an RFC 3339 String, or paused and resumed with `pause_consumer` and `resume_consumer`; updating a consumer keeps its pause. `consumer_info` reports whether a consumer is `paused`, and the seconds until it resumes, as `pause_remaining`. Servers before 2.11 create consumers unpaused, and let `pause_consumer` and `resume_consumer` time out. (#193)
|
|
27
|
+
- JetStream: pull consumers can serve priority groups (nats-server 2.11). Consumer configs take `priority_policy`, `priority_groups` and `priority_timeout` (in seconds, like the other consumer durations), and `consumer_info` reports the state of the groups as `priority_groups`. `fetch` takes the `group` to pull from, `min_pending` and `min_ack_pending` for the `"overflow"` policy, and `priority` for the `"prioritized"` policy of nats-server 2.12. (#193)
|
|
28
|
+
- JetStream: with the `"pinned_client"` priority policy, a pull subscription keeps the pin id that the server delivers with its messages, and sends it with its pulls. When the server says that the subscription is no longer pinned, as its pin expired or it was unpinned with the new `unpin_consumer`, a fetch that got no messages raises `NATS::JetStream::Error::PinIdMismatch`, and the next fetch can be pinned again. The server counts the consumer's `priority_timeout` from each pull, not while a pull waits, so keep the fetch timeout, plus the time between fetches, below it, or the subscription loses its pin. (#193, #195)
|
|
29
|
+
- JetStream: `reset_consumer` makes a consumer deliver again from after its ack floor, or from the stream sequence given as `seq` (nats-server 2.14). A sequence that the deliver policy of the consumer does not let it be reset to raises `NATS::JetStream::Error::ConsumerInvalidReset`, a `BadRequest` error, and one that is not an integer of 0 or more raises `ArgumentError`. For a stream or consumer that does not exist, and with servers before 2.14, the request times out. (#193)
|
|
30
|
+
- JetStream: `stream_info` and `consumer_info` report the `cluster` of the stream or consumer as a Hash, including when its leader was elected, as `leader_since` (nats-server 2.12). `consumer_info` used to drop it. (#193)
|
|
31
|
+
- KV: buckets can be created with `compression: true` and with `metadata`, which `status.compressed?` and `status.metadata` report. (#191)
|
|
32
|
+
- KV: `watch` takes an Array of keys, watched by a single consumer (nats-server 2.10). An empty Array watches all keys. (#191)
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- JetStream: `fetch` pulls once, for the whole batch, and waits: it returns as soon as it has the batch, and otherwise the messages that came when its timeout is up, like `Fetch` of the nats.go `jetstream` package and `batch` of nats.rs. A fetch of more than one message used to ask first, without waiting, for the messages that were pending. When it got some, it then waited out its timeout without pulling for more, so it neither took the messages that came meanwhile nor returned sooner. When other pulls of the consumer waited for more messages than were pending, the server turned that first pull away (408 Requests Pending), and `fetch` raised `NATS::IO::Timeout` at once. (#195)
|
|
37
|
+
- JetStream: a pull subscription subscribes to `<inbox>.*`, which `subject` now returns, and each pull gets a reply subject of its own under it, as ADR-13 describes, so that a fetch tells the status that ended its own pull from those that ended the pulls of other fetches. A `fetch` with `no_wait: true` pulls with a subscription of its own, which it drops when done, so that its pull does not linger when the server holds it, as while the consumer may not deliver more messages until some are acked (`max_ack_pending`): the fetch then returns what it got once its timeout is up, and leaves what comes later to the next fetch. A server with leafnodes or gateways delivers to a pull until it is 2 seconds old even once the subscription is gone, so the subscription stays until then. (#195)
|
|
38
|
+
- JetStream: `fetch` raises `ArgumentError` for a `timeout` that is not a finite positive number, such as 0, with which it used to raise `NATS::IO::Timeout` at once. It leaves the options it is given unchanged; it used to set their `:timeout`. (#195)
|
|
39
|
+
- JetStream: as `fetch` always pulls with an expiry of its timeout, a consumer whose `max_expires` is shorter than the timeout turns every fetch away, with 409 "Exceeded MaxRequestExpires", raised as `NATS::JetStream::API::Error`. A fetch of more than one message used to get the messages that were pending first, with a pull that had no expiry. Pass a `timeout` no longer than the consumer's `max_expires`. (#195)
|
|
40
|
+
- JetStream: `fetch` returns when the server ends its pull, so it takes its timeout plus the round trip to the server. When no end comes, it waits up to 1 second longer, like nats.go, before it returns what it got or raises `NATS::IO::Timeout`. (#195)
|
|
41
|
+
- JetStream and KV: settings that used to be dropped are now sent. Code that already passed `compression`, `first_seq`, `subject_transform` or `consumer_limits` to `add_stream`, or `compression` or `metadata` to `create_key_value`, created its streams and buckets without them, so calling it again for an existing stream or bucket now fails with "stream name already in use with a different configuration" (err_code 10058). Apply the setting with `update_stream`, or stop passing it. (#191)
|
|
42
|
+
- JetStream: the same goes for the settings of nats-server 2.11 to 2.14: code that passed them to `add_stream` created its streams without them, so calling it again for an existing stream now fails with err_code 10058. `update_stream` applies them, except `allow_msg_counter` and `persist_mode`, which a stream cannot change. (#194)
|
|
43
|
+
- JetStream: consumer configs now send `pause_until`, `priority_policy`, `priority_groups` and `priority_timeout`, which used to be dropped. As `add_consumer` creates or updates, code that already passed the priority settings to it now gives an existing consumer priority groups, after which the server turns away pulls that name no group (400 "Priority Group missing"). (#193)
|
|
44
|
+
- KV: `create_key_value` takes only `true` or `false` for `compression`; anything else, such as `"s2"`, which used to be ignored, raises `ArgumentError`. (#191)
|
|
45
|
+
- JetStream: `add_consumer` leaves the `ConsumerConfig` it is given unchanged. It used to set its `name` and a default `ack_policy`, and convert its durations to nanoseconds, so passing the same config again sent durations a billion times too long. (#191)
|
|
46
|
+
- JetStream: two `stream_info` or `consumer_info` results are no longer `==` unless they were reported at the same time, as they now carry `ts`, and the `cluster`, `mirror` and `sources`, whose lag and activity change all the time, even with servers that do not report `ts`. (#191, #193)
|
|
47
|
+
- KV: `watch` no longer looks up the bucket's stream before creating its consumer. (#191)
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- JetStream: `update_stream` with the config from `stream_info` failed with err_code 10052, such as "message TTL status can not be disabled", for streams with per-message TTLs, subject delete markers, counters, message schedules or async persistence, and turned off atomic and fast batches, as `StreamConfig` dropped the settings of nats-server 2.11 to 2.14. It now keeps them. (#194)
|
|
52
|
+
- JetStream: `StreamConfig` dropped `discard_new_per_subject`, so `add_stream` created streams without it, and `update_stream` with the config from `stream_info` turned it off: a subject that held `max_msgs_per_subject` messages then discarded its oldest message instead of refusing the new one. It now keeps it. (#199)
|
|
53
|
+
- JetStream: `js.publish` with `stream:` added `Nats-Expected-Stream` to the header Hash it was given, so a caller that reused the Hash sent it with its later publishes. (#194)
|
|
54
|
+
- JetStream: `js.publish` raised `ArgumentError: unknown keywords` after the server had stored the message, when publishing to a stream with counters or committing an atomic batch, as the acks of nats-server 2.12 carry `val`, `batch` and `count`. `PubAck` now has these fields and ignores any it does not know, so fields added to acks later cannot break publish. (#192, #177, thanks @jnowakplacewise)
|
|
55
|
+
- JetStream: `add_consumer`, `consumer_info`, `subscribe` and `pull_subscribe` raised `NoMethodError` for consumers that do not ack (`ack_policy: "none"`) created without an `ack_wait`, after the consumer had been created. The server omits `ack_wait` for them, and `config.ack_wait` is now `nil`. (#192)
|
|
56
|
+
- JetStream: `fetch` took a status that an earlier pull had left in the subscription, such as the 408 of a fetch that timed out, for the reply to its own pull, and raised `NATS::IO::Timeout` at once, even with messages pending: a fetch of more than one message with any such status, a fetch of one message with a second one. `fetch` now first takes what earlier pulls left, and pulls for the rest only. And a fetch that got messages before an error status, such as 409 Consumer Deleted, raised the error and dropped them; like nats.go, it now returns them. (#193, #195)
|
|
57
|
+
- JetStream: when threads fetched from the same subscription at once, a fetch could take the 408 that ended the pull of another for the end of its own, and raise `NATS::IO::Timeout` before its timeout. (#195)
|
|
58
|
+
- JetStream: `fetch` pulled with an expiry a little short of its timeout only after a first pull, which did not wait, came back empty, so its pull outlived it by the time that first pull took: a message delivered meanwhile waited in the subscription for the next fetch, and, if none came, was delivered again once the `ack_wait` was up. A fetch now waits for the server to end its pull, which expires with its timeout. (#195)
|
|
59
|
+
- JetStream: a `fetch` that waited longer than the `ack_wait` of the consumer returned a message twice when the server delivered it again meanwhile. It now returns each message once, as last delivered. (#195)
|
|
60
|
+
- JetStream: `fetch` took a message of the stream with a `Status` header for a status of the server, and raised an error or ended without it. Statuses are now only the messages with a `Status` header that the server sends without a reply. (#195)
|
|
61
|
+
- JetStream: a `fetch` of one message raised `TypeError: nil can't be coerced into Float` when threads fetched from the same subscription at once: woken up for a message that another thread took, it did not wait on. It now waits until its timeout. (#193, #181, thanks @route)
|
|
62
|
+
- JetStream: a `fetch` of one message raised `NATS::IO::Timeout` ("nats: fetch request timeout") before its timeout when a status that ended an earlier pull, such as the 408 of a fetch that had just timed out, came while it waited, and left its own message for the next fetch. Like nats.go, it now waits on for its message until its timeout. (#193)
|
|
63
|
+
- JetStream: `fetch` could miss the reply to its pull when it came before the fetch waited for it, as when its thread was preempted: a fetch of one message then waited out its timeout and raised `NATS::IO::Timeout`, leaving its message to be redelivered after the ack wait, and a fetch of more messages returned only the first one, after its timeout. `fetch` now takes what is there before it waits. (#193)
|
|
64
|
+
- KV: after its consumer was recreated, as after a server restart, a watcher yielded the entries of every key in the bucket instead of the watched keys. The recreated consumer now keeps the keys filter. (#192)
|
|
65
|
+
- KV: after its consumer was recreated, a watcher yielded its last entry again, and a watcher with no entries yet, such as one on an empty bucket, never got its consumer back (`err_code` 10094) and stayed silent. The recreated consumer now starts after the last entry. (#192, #175, thanks @Xayc73)
|
|
66
|
+
- A subscription without a callback, read with `next_msg`, dropped every message and reported `NATS::IO::SlowConsumer` once it had received its `pending_bytes_limit` in all, 64 MiB by default, however fast it was read, as `next_msg` did not take the messages it returned off the pending bytes. (#196)
|
|
67
|
+
|
|
68
|
+
## v2.6.0
|
|
69
|
+
|
|
70
|
+
This release prepares nats-pure for nats-server 2.16, whose new JetStream
|
|
71
|
+
acknowledgement format breaks JetStream consumers and KV watchers in earlier
|
|
72
|
+
versions, and fixes a deadlock that could hang `Client#close`.
|
|
73
|
+
|
|
74
|
+
### Changed
|
|
75
|
+
|
|
76
|
+
- With `reconnect: false`, losing the connection now closes the client, as nats.go does: the status becomes `CLOSED`, the close callback is called, and `publish` raises `NATS::IO::ConnectionClosedError`. It used to stay `DISCONNECTED` and keep accepting publishes that were never sent, because the close, started from the read loop, killed the read loop part way through. (#189)
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- JetStream: accept the 11-token acknowledgement subjects that nats-server 2.16 sends by default (the v2 format of ADR-15). Earlier versions fail to read the metadata of every JetStream message from such a server, which breaks pull and push consumers and KV watchers. (#185)
|
|
81
|
+
- JetStream: an unrecognized acknowledgement subject raises `NATS::JetStream::Error::NotJSMessage` instead of `NameError`, and subjects whose numeric fields do not parse are rejected. (#185)
|
|
82
|
+
- JetStream: message metadata timestamps keep their full nanosecond precision. (#185)
|
|
83
|
+
- KV: watchers track their position from message metadata, so they resume after a server restart with either acknowledgement format. (#185)
|
|
84
|
+
- `Client#close` could hang forever. It stopped the client's background threads with `Thread#exit`, and killing a thread that is waiting for the client lock can lose Ruby's mutex wakeup, leaving `close` asleep on an unlocked lock. The threads are now asked to stop and joined, on close and on reconnect. (#183, #189)
|
|
85
|
+
- A close while a reconnect was under way did not stick: the reconnect carried on, connected again and restarted the client, subscriptions included. (#189)
|
|
86
|
+
- A subscription with a concurrency limit could run its callbacks in parallel, and so out of order: an empty pending queue released the concurrency permit twice. (#186)
|
|
87
|
+
- A subscription could stop processing messages for good after a reconnect: shutting down the executor during dispatch killed the read loop and leaked the concurrency permit. (#182)
|
|
88
|
+
- WebSocket: protocol lines sharing a frame, such as the `PONG` and `INFO` that nats-server 2.12.2 sends together, were misparsed (#174, thanks @artyomb), and bytes that arrived with the reply to the connect handshake were lost. (#182, #186)
|
|
89
|
+
- JRuby: a refused connection raises `Errno::ECONNREFUSED`, as on MRI. (#182, #186)
|
|
90
|
+
|
|
91
|
+
### Improved
|
|
92
|
+
|
|
93
|
+
- Tested against nats-server 2.14, 2.15 and the upcoming 2.16 (`main`), on Ruby 3.0 to 3.4 and JRuby. (#182, #188)
|
data/README.md
CHANGED
|
@@ -102,6 +102,89 @@ loop do
|
|
|
102
102
|
end
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
`fetch` returns as soon as it has the batch, or once its timeout is up, the
|
|
106
|
+
messages that came. With `no_wait: true`, it takes what the server delivers at
|
|
107
|
+
once, without waiting for more. Given a block, it passes each message to it as it comes,
|
|
108
|
+
so that the message can be acked before the `ack_wait` of the consumer is up:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
psub.fetch(100, timeout: 60) do |msg|
|
|
112
|
+
puts msg.data
|
|
113
|
+
msg.ack
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Messages can expire on their own, in streams that allow it (nats-server 2.11):
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
js.add_stream(name: "sessions", subjects: ["sessions.>"], allow_msg_ttl: true)
|
|
121
|
+
|
|
122
|
+
# Removed after an hour. With ttl: :never, kept past the max_age of the stream.
|
|
123
|
+
js.publish("sessions.42", "token", ttl: 3600)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Messages can also be scheduled (nats-server 2.12; `every:` and `cron:` need 2.14):
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
js.add_stream(name: "reminders", subjects: ["reminders.>", "due.>"], allow_msg_schedules: true)
|
|
130
|
+
|
|
131
|
+
# Published to due.42 in an hour. Another schedule on reminders.42 replaces it.
|
|
132
|
+
js.publish("reminders.42", "call back", schedule: {at: Time.now + 3600, target: "due.42"})
|
|
133
|
+
|
|
134
|
+
# Published to due.standup at 9:00 on weekdays, Warsaw time.
|
|
135
|
+
js.publish("reminders.standup", "stand-up",
|
|
136
|
+
schedule: {cron: "0 0 9 * * 1-5", time_zone: "Europe/Warsaw", target: "due.standup"})
|
|
137
|
+
|
|
138
|
+
# Cancels the schedule on reminders.42.
|
|
139
|
+
js.publish("reminders.cancelled", "", header: {
|
|
140
|
+
NATS::JetStream::Header::SCHEDULE_NEXT => "purge",
|
|
141
|
+
NATS::JetStream::Header::SCHEDULER => "reminders.42"
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Streams and consumers can also be managed directly:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
# Compressed, and no consumer may have more than 1000 unacknowledged messages.
|
|
149
|
+
js.add_stream(name: "orders", subjects: ["orders.>"], compression: "s2",
|
|
150
|
+
consumer_limits: {max_ack_pending: 1000})
|
|
151
|
+
|
|
152
|
+
config = {durable_name: "billing", filter_subject: "orders.paid", max_ack_pending: 100}
|
|
153
|
+
|
|
154
|
+
# Fails with NATS::JetStream::Error::ConsumerAlreadyExists if "billing"
|
|
155
|
+
# exists with a different config.
|
|
156
|
+
js.create_consumer("orders", config)
|
|
157
|
+
|
|
158
|
+
# An update replaces the whole config, so send all of it again.
|
|
159
|
+
# Fails with NATS::JetStream::Error::ConsumerDoesNotExist if there is no "billing".
|
|
160
|
+
js.update_consumer("orders", config.merge(max_ack_pending: 200))
|
|
161
|
+
|
|
162
|
+
# Deliver nothing for an hour, or until resumed.
|
|
163
|
+
js.pause_consumer("orders", "billing", Time.now + 3600)
|
|
164
|
+
js.resume_consumer("orders", "billing")
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Pull consumers can serve priority groups. With the `"pinned_client"` policy,
|
|
168
|
+
the server delivers to one subscription at a time, and to another once it
|
|
169
|
+
stops pulling for `priority_timeout` seconds. The server counts it from
|
|
170
|
+
each pull, not while a pull waits: keep the fetch timeout, 5 seconds by
|
|
171
|
+
default, plus the time between fetches, below `priority_timeout`, or the
|
|
172
|
+
subscription can lose its pin:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
js.create_consumer("orders", durable_name: "shipping", filter_subject: "orders.paid",
|
|
176
|
+
priority_policy: "pinned_client", priority_groups: ["workers"], priority_timeout: 60)
|
|
177
|
+
|
|
178
|
+
psub = js.pull_subscribe("orders.paid", "shipping", stream: "orders")
|
|
179
|
+
begin
|
|
180
|
+
psub.fetch(5, group: "workers").each(&:ack)
|
|
181
|
+
rescue NATS::JetStream::Error::PinIdMismatch
|
|
182
|
+
# This subscription is no longer pinned; the next fetch may be pinned again.
|
|
183
|
+
rescue NATS::IO::Timeout
|
|
184
|
+
# Nothing to do, or another subscription is pinned.
|
|
185
|
+
end
|
|
186
|
+
```
|
|
187
|
+
|
|
105
188
|
## Service API
|
|
106
189
|
|
|
107
190
|
The service API allows you to easily [build NATS services](docs/service_api.md).
|
data/lib/nats/io/client.rb
CHANGED
|
@@ -221,6 +221,10 @@ module NATS
|
|
|
221
221
|
# Track whether connect has been already been called.
|
|
222
222
|
@connect_called = false
|
|
223
223
|
|
|
224
|
+
# Bumped by every close, so that a reconnect started earlier can tell
|
|
225
|
+
# that the connection was closed meanwhile.
|
|
226
|
+
@close_generation = 0
|
|
227
|
+
|
|
224
228
|
# New style request/response implementation.
|
|
225
229
|
@resp_sub = nil
|
|
226
230
|
@resp_map = nil
|
|
@@ -1293,6 +1297,11 @@ module NATS
|
|
|
1293
1297
|
# Handles errors from reading, parsing the protocol or stale connection.
|
|
1294
1298
|
# the lock should not be held entering this function.
|
|
1295
1299
|
def process_op_error(e)
|
|
1300
|
+
# A background thread being stopped by close or a reconnect sees
|
|
1301
|
+
# errors from that; it must not start another close or reconnect,
|
|
1302
|
+
# which would wait for the thread that is stopping it.
|
|
1303
|
+
return if stopping?
|
|
1304
|
+
|
|
1296
1305
|
should_bail = synchronize do
|
|
1297
1306
|
connecting? || closed? || reconnecting?
|
|
1298
1307
|
end
|
|
@@ -1320,23 +1329,29 @@ module NATS
|
|
|
1320
1329
|
|
|
1321
1330
|
def initiate_reconnect
|
|
1322
1331
|
@status = RECONNECTING
|
|
1323
|
-
@io
|
|
1332
|
+
old_io = @io
|
|
1324
1333
|
@io = nil
|
|
1325
1334
|
|
|
1335
|
+
# Wake a read loop blocked on the socket by shutting it down, and
|
|
1336
|
+
# close the socket only once the loop has stopped: on JRuby a close
|
|
1337
|
+
# right after the shutdown can leave the read blocked for good.
|
|
1338
|
+
if @read_loop_thread&.alive? && @read_loop_thread != Thread.current
|
|
1339
|
+
old_io&.shutdown_read
|
|
1340
|
+
end
|
|
1341
|
+
|
|
1326
1342
|
# TODO: Reconnecting pending buffer?
|
|
1327
1343
|
|
|
1344
|
+
generation = @close_generation
|
|
1345
|
+
|
|
1328
1346
|
# Do reconnect under a different thread than the one
|
|
1329
1347
|
# in which we got the error.
|
|
1330
1348
|
Thread.new do
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
@read_loop_thread.exit if @read_loop_thread.alive?
|
|
1334
|
-
@flusher_thread.exit if @flusher_thread.alive?
|
|
1335
|
-
@ping_interval_thread.exit if @ping_interval_thread.alive?
|
|
1349
|
+
stop_threads!
|
|
1350
|
+
old_io&.close
|
|
1336
1351
|
|
|
1337
1352
|
@subscription_executor.shutdown
|
|
1338
1353
|
|
|
1339
|
-
attempt_reconnect
|
|
1354
|
+
attempt_reconnect(generation)
|
|
1340
1355
|
rescue NATS::IO::NoServersError => e
|
|
1341
1356
|
@last_err = e
|
|
1342
1357
|
close
|
|
@@ -1346,6 +1361,8 @@ module NATS
|
|
|
1346
1361
|
# Gathers data from the socket and sends it to the parser.
|
|
1347
1362
|
def read_loop
|
|
1348
1363
|
loop do
|
|
1364
|
+
return if stopping?
|
|
1365
|
+
|
|
1349
1366
|
should_bail = synchronize do
|
|
1350
1367
|
# FIXME: In case of reconnect as well?
|
|
1351
1368
|
@status == CLOSED or @status == RECONNECTING
|
|
@@ -1361,6 +1378,9 @@ module NATS
|
|
|
1361
1378
|
# FIXME: We do not really need a timeout here...
|
|
1362
1379
|
retry
|
|
1363
1380
|
rescue => e
|
|
1381
|
+
# Asked to stop: the error comes from the socket being shut down.
|
|
1382
|
+
return if stopping?
|
|
1383
|
+
|
|
1364
1384
|
# In case of reading/parser errors, trigger
|
|
1365
1385
|
# reconnection logic in case desired.
|
|
1366
1386
|
process_op_error(e)
|
|
@@ -1373,6 +1393,7 @@ module NATS
|
|
|
1373
1393
|
loop do
|
|
1374
1394
|
# Blocks waiting for the flusher to be kicked...
|
|
1375
1395
|
@flush_queue.pop
|
|
1396
|
+
return if stopping?
|
|
1376
1397
|
|
|
1377
1398
|
should_bail = synchronize do
|
|
1378
1399
|
(@status != CONNECTED && !draining?) || @status == CONNECTING
|
|
@@ -1411,9 +1432,9 @@ module NATS
|
|
|
1411
1432
|
end
|
|
1412
1433
|
end
|
|
1413
1434
|
|
|
1414
|
-
def ping_interval_loop
|
|
1435
|
+
def ping_interval_loop(stop)
|
|
1415
1436
|
loop do
|
|
1416
|
-
|
|
1437
|
+
return if stop.wait(@options[:ping_interval])
|
|
1417
1438
|
|
|
1418
1439
|
# Skip ping interval until connected
|
|
1419
1440
|
next if !connected?
|
|
@@ -1486,8 +1507,11 @@ module NATS
|
|
|
1486
1507
|
end
|
|
1487
1508
|
end
|
|
1488
1509
|
|
|
1489
|
-
# Reconnect logic
|
|
1490
|
-
|
|
1510
|
+
# Reconnect logic. generation is @close_generation from when the
|
|
1511
|
+
# reconnect started; a close since then cancels the reconnect.
|
|
1512
|
+
def attempt_reconnect(generation)
|
|
1513
|
+
return if closed_since?(generation)
|
|
1514
|
+
|
|
1491
1515
|
@disconnect_cb&.call(@last_err)
|
|
1492
1516
|
|
|
1493
1517
|
# Clear sticky error
|
|
@@ -1496,6 +1520,8 @@ module NATS
|
|
|
1496
1520
|
# Do reconnect
|
|
1497
1521
|
srv = nil
|
|
1498
1522
|
begin
|
|
1523
|
+
return if closed_since?(generation)
|
|
1524
|
+
|
|
1499
1525
|
srv = select_next_server
|
|
1500
1526
|
|
|
1501
1527
|
# Set hostname to use for TLS hostname verification
|
|
@@ -1537,37 +1563,49 @@ module NATS
|
|
|
1537
1563
|
retry
|
|
1538
1564
|
end
|
|
1539
1565
|
|
|
1540
|
-
#
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1566
|
+
# Under the lock, so that a concurrent close either cancels this or
|
|
1567
|
+
# finds the new connection in place and closes it.
|
|
1568
|
+
synchronize do
|
|
1569
|
+
if @close_generation != generation
|
|
1570
|
+
@io.close
|
|
1571
|
+
@io = nil
|
|
1572
|
+
return
|
|
1573
|
+
end
|
|
1544
1574
|
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
@
|
|
1548
|
-
|
|
1575
|
+
# Clear pending flush calls and reset state before restarting loops
|
|
1576
|
+
@flush_queue.clear
|
|
1577
|
+
@pings_outstanding = 0
|
|
1578
|
+
@pongs_received = 0
|
|
1549
1579
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
@io.write(cmds.join) unless cmds.empty?
|
|
1555
|
-
@status = CONNECTED
|
|
1556
|
-
@pending_size = 0
|
|
1580
|
+
# Replay all subscriptions
|
|
1581
|
+
@subs.each_pair do |sid, sub|
|
|
1582
|
+
@io.write("SUB #{sub.subject} #{sub.queue} #{sid}#{CR_LF}")
|
|
1583
|
+
end
|
|
1557
1584
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1585
|
+
# Flush anything which was left pending, in case of errors during flush
|
|
1586
|
+
# then we should raise error then retry the reconnect logic
|
|
1587
|
+
cmds = []
|
|
1588
|
+
cmds << @pending_queue.pop until @pending_queue.empty?
|
|
1589
|
+
@io.write(cmds.join) unless cmds.empty?
|
|
1590
|
+
@status = CONNECTED
|
|
1591
|
+
@pending_size = 0
|
|
1561
1592
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1593
|
+
# Reset parser state here to avoid unknown protocol errors
|
|
1594
|
+
# on reconnect...
|
|
1595
|
+
@parser.reset!
|
|
1596
|
+
|
|
1597
|
+
# Now connected to NATS, and we can restart parser loop, flusher
|
|
1598
|
+
# and ping interval
|
|
1599
|
+
start_threads!
|
|
1600
|
+
end
|
|
1565
1601
|
|
|
1566
|
-
# Dispatch the reconnected callback while holding lock
|
|
1567
|
-
# which we should have already
|
|
1568
1602
|
@reconnect_cb&.call
|
|
1569
1603
|
end
|
|
1570
1604
|
|
|
1605
|
+
def closed_since?(generation)
|
|
1606
|
+
synchronize { @close_generation != generation }
|
|
1607
|
+
end
|
|
1608
|
+
|
|
1571
1609
|
def close_connection(conn_status, do_cbs = true)
|
|
1572
1610
|
synchronize do
|
|
1573
1611
|
@connect_called = false
|
|
@@ -1575,25 +1613,10 @@ module NATS
|
|
|
1575
1613
|
@status = conn_status
|
|
1576
1614
|
return
|
|
1577
1615
|
end
|
|
1616
|
+
@close_generation += 1
|
|
1578
1617
|
end
|
|
1579
1618
|
|
|
1580
|
-
|
|
1581
|
-
@flush_queue << :fallout if @flush_queue
|
|
1582
|
-
Thread.pass
|
|
1583
|
-
|
|
1584
|
-
# FIXME: More graceful way of handling the following?
|
|
1585
|
-
# Ensure ping interval and flusher are not running anymore
|
|
1586
|
-
if @ping_interval_thread&.alive?
|
|
1587
|
-
@ping_interval_thread.exit
|
|
1588
|
-
end
|
|
1589
|
-
|
|
1590
|
-
if @flusher_thread&.alive?
|
|
1591
|
-
@flusher_thread.exit
|
|
1592
|
-
end
|
|
1593
|
-
|
|
1594
|
-
if @read_loop_thread&.alive?
|
|
1595
|
-
@read_loop_thread.exit
|
|
1596
|
-
end
|
|
1619
|
+
stop_threads!
|
|
1597
1620
|
|
|
1598
1621
|
@subscription_executor&.shutdown
|
|
1599
1622
|
@subscription_executor&.wait_for_termination(options[:close_timeout])
|
|
@@ -1642,21 +1665,58 @@ module NATS
|
|
|
1642
1665
|
end
|
|
1643
1666
|
end
|
|
1644
1667
|
|
|
1668
|
+
# Asks the read loop, flusher and ping threads to stop, wakes them from
|
|
1669
|
+
# wherever they block, and waits for them to finish.
|
|
1670
|
+
#
|
|
1671
|
+
# They are never killed: Thread#exit on a thread that is waiting for
|
|
1672
|
+
# the client lock can swallow the lock's wakeup, so that the next
|
|
1673
|
+
# thread waiting for the lock sleeps forever on an unlocked lock (#183).
|
|
1674
|
+
def stop_threads!(timeout = NATS::IO::THREADS_STOP_TIMEOUT)
|
|
1675
|
+
threads = [@read_loop_thread, @flusher_thread, @ping_interval_thread].compact
|
|
1676
|
+
threads.each { |t| t[:nats_stop]&.stop! } # also wakes the ping thread
|
|
1677
|
+
|
|
1678
|
+
begin
|
|
1679
|
+
@flush_queue&.push(:fallout, true)
|
|
1680
|
+
rescue ThreadError
|
|
1681
|
+
# Queue is full, so the flusher is awake already.
|
|
1682
|
+
end
|
|
1683
|
+
# Wake the read loop if it is blocked on the socket. Only while it
|
|
1684
|
+
# runs: shutdown acts on the socket itself, which a forked child
|
|
1685
|
+
# (whose threads are gone) shares with the parent.
|
|
1686
|
+
if @read_loop_thread&.alive? && @read_loop_thread != Thread.current
|
|
1687
|
+
@io&.shutdown_read
|
|
1688
|
+
end
|
|
1689
|
+
|
|
1690
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
1691
|
+
threads.each do |t|
|
|
1692
|
+
next if t == Thread.current
|
|
1693
|
+
|
|
1694
|
+
t.join([deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max)
|
|
1695
|
+
end
|
|
1696
|
+
end
|
|
1697
|
+
|
|
1698
|
+
def stopping?
|
|
1699
|
+
Thread.current[:nats_stop]&.stopped?
|
|
1700
|
+
end
|
|
1701
|
+
|
|
1702
|
+
def start_thread(name, stop, &block)
|
|
1703
|
+
thread = Thread.new(&block)
|
|
1704
|
+
thread[:nats_stop] = stop
|
|
1705
|
+
thread.name = name
|
|
1706
|
+
thread.abort_on_exception = true
|
|
1707
|
+
thread
|
|
1708
|
+
end
|
|
1709
|
+
|
|
1645
1710
|
def start_threads!
|
|
1646
1711
|
# Reading loop for gathering data
|
|
1647
|
-
@read_loop_thread =
|
|
1648
|
-
@read_loop_thread.name = "nats:read_loop"
|
|
1649
|
-
@read_loop_thread.abort_on_exception = true
|
|
1712
|
+
@read_loop_thread = start_thread("nats:read_loop", NATS::IO::StopSignal.new) { read_loop }
|
|
1650
1713
|
|
|
1651
1714
|
# Flusher loop for sending commands
|
|
1652
|
-
@flusher_thread =
|
|
1653
|
-
@flusher_thread.name = "nats:flusher_loop"
|
|
1654
|
-
@flusher_thread.abort_on_exception = true
|
|
1715
|
+
@flusher_thread = start_thread("nats:flusher_loop", NATS::IO::StopSignal.new) { flusher_loop }
|
|
1655
1716
|
|
|
1656
1717
|
# Ping interval handling for keeping alive the connection
|
|
1657
|
-
|
|
1658
|
-
@ping_interval_thread
|
|
1659
|
-
@ping_interval_thread.abort_on_exception = true
|
|
1718
|
+
ping_stop = NATS::IO::StopSignal.new
|
|
1719
|
+
@ping_interval_thread = start_thread("nats:ping_loop", ping_stop) { ping_interval_loop(ping_stop) }
|
|
1660
1720
|
|
|
1661
1721
|
# Subscription handling thread pool
|
|
1662
1722
|
@subscription_executor = Concurrent::ThreadPoolExecutor.new(
|
|
@@ -1889,6 +1949,44 @@ module NATS
|
|
|
1889
1949
|
DEFAULT_DRAIN_TIMEOUT = 30
|
|
1890
1950
|
DEFAULT_CLOSE_TIMEOUT = 30
|
|
1891
1951
|
|
|
1952
|
+
# How long to wait for the read loop, flusher and ping threads to
|
|
1953
|
+
# stop on close or reconnect.
|
|
1954
|
+
THREADS_STOP_TIMEOUT = 5
|
|
1955
|
+
|
|
1956
|
+
# Asks a background thread to stop, and lets it sleep until then.
|
|
1957
|
+
class StopSignal
|
|
1958
|
+
def initialize
|
|
1959
|
+
@mutex = Mutex.new
|
|
1960
|
+
@cond = ConditionVariable.new
|
|
1961
|
+
@stopped = false
|
|
1962
|
+
end
|
|
1963
|
+
|
|
1964
|
+
def stop!
|
|
1965
|
+
@mutex.synchronize do
|
|
1966
|
+
@stopped = true
|
|
1967
|
+
@cond.broadcast
|
|
1968
|
+
end
|
|
1969
|
+
end
|
|
1970
|
+
|
|
1971
|
+
def stopped?
|
|
1972
|
+
@stopped
|
|
1973
|
+
end
|
|
1974
|
+
|
|
1975
|
+
# Sleeps up to timeout seconds; returns whether stop! was called.
|
|
1976
|
+
def wait(timeout)
|
|
1977
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
1978
|
+
@mutex.synchronize do
|
|
1979
|
+
until @stopped
|
|
1980
|
+
left = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
1981
|
+
break if left <= 0
|
|
1982
|
+
|
|
1983
|
+
@cond.wait(@mutex, left)
|
|
1984
|
+
end
|
|
1985
|
+
@stopped
|
|
1986
|
+
end
|
|
1987
|
+
end
|
|
1988
|
+
end
|
|
1989
|
+
|
|
1892
1990
|
# Default Pending Limits
|
|
1893
1991
|
DEFAULT_SUB_PENDING_MSGS_LIMIT = 65536
|
|
1894
1992
|
DEFAULT_SUB_PENDING_BYTES_LIMIT = 65536 * 1024
|
|
@@ -2004,6 +2102,15 @@ module NATS
|
|
|
2004
2102
|
@socket.close
|
|
2005
2103
|
end
|
|
2006
2104
|
|
|
2105
|
+
# Makes a read blocked on the socket in another thread return,
|
|
2106
|
+
# while writes keep working.
|
|
2107
|
+
def shutdown_read
|
|
2108
|
+
io = @socket.respond_to?(:to_io) ? @socket.to_io : @socket
|
|
2109
|
+
io.shutdown(::Socket::SHUT_RD)
|
|
2110
|
+
rescue IOError, SystemCallError
|
|
2111
|
+
# Already closed or not connected.
|
|
2112
|
+
end
|
|
2113
|
+
|
|
2007
2114
|
def closed?
|
|
2008
2115
|
@socket.closed?
|
|
2009
2116
|
end
|
|
@@ -2030,6 +2137,12 @@ module NATS
|
|
|
2030
2137
|
end
|
|
2031
2138
|
|
|
2032
2139
|
sock
|
|
2140
|
+
rescue IOError => e
|
|
2141
|
+
# JRuby raises a plain IOError for a refused connection.
|
|
2142
|
+
if (RUBY_ENGINE == "jruby") && e.message.include?("Connection refused")
|
|
2143
|
+
raise Errno::ECONNREFUSED
|
|
2144
|
+
end
|
|
2145
|
+
raise
|
|
2033
2146
|
end
|
|
2034
2147
|
end
|
|
2035
2148
|
end
|