SIGN IN SIGN UP

Fix reading an object so it no longer panics on a JetStream timeouts in Object Store

`Object`'s `AsyncRead` impl creates its ordered consumer lazily on the
first poll and `unwrap()`s the result. A JetStream request that ages past
the client's timeout therefore panics inside a runtime worker rather than
surfacing as an error — and in a binary built with `panic = "abort"` that
takes the whole process down, along with every unrelated in-flight
request.

Observed in production: a front-door process aborted (SIGABRT) mid-fetch,
twice within a minute, each time killing connections belonging to other
callers. The build the fetch belonged to had already succeeded
server-side.

Two unwraps are on that path and both are removed:

- `create_consumer(...).unwrap()` becomes a `map_err` into `StreamError`,
  preserving the timeout classification (`ConsumerErrorKind::TimedOut` →
  `StreamErrorKind::TimedOut`) rather than flattening every failure to
  `Other`. The future's declared output is already
  `Result<Ordered, StreamError>`, so nothing above changes shape.
- `subscription.unwrap()` in the poll becomes a `map_err` into
  `io::Error`, which is what the surrounding `poll_read` already returns
  for subscription failures a few lines below.

Removing the panic leaves two further properties to keep:

- The future is taken out of the `Object` before it is polled and put
  back only while it is still pending. Left in place after it resolved,
  it would be polled again by a caller that retries the read, and polling
  a completed `async` block panics ("`async fn` resumed after
  completion") — the same abort, one step further along. A retry now
  builds a fresh future and asks the server again.
- The classification survives the `io::Error` boundary, which is all an
  `AsyncRead` caller ever sees: a timeout arrives as
  `ErrorKind::TimedOut` rather than `Other`, with the `StreamError`
  retained as its source.

Both are covered against a real server. A read whose consumer creation
fails (the bucket is removed between `get` and the first read) is an
error on the second read too, rather than a panic; a read whose request
has nobody left to answer it comes back as `ErrorKind::TimedOut`.

A reader now gets `Err` where it previously got a dead process. No public
API change.

Refs nats-io/nats.rs#1615.
M
macrauder committed
b125cf3bb6014e1148d20fab469d2910c19d1d53
Parent: 7d4423f
Committed by GitHub <noreply@github.com> on 9/25/2026, 5:35:29 AM