Skip to content

PublishOptions#streamTimeout has no effect on the publish acknowledgement wait #1610

Description

@utamas

Observed behavior

PublishOptions.streamTimeout has no effect. The value is accepted, validated and stored, but never read by the library — so a publish uses the JetStream context's requestTimeout regardless of what the publish options say.

In 2.25.3, getStreamTimeout() has exactly one occurrence in the entire library: its own declaration at PublishOptions.java:104. Nothing calls it. NatsJetStream.publishSyncInternal (line 157) passes getTimeout() — the context's requestTimeout, per NatsJetStreamImpl's constructor — to makeInternalRequestResponseRequired, and never consults the publish options.

The setting appears functional because Builder.streamTimeout(Duration) validates the argument via validateDurationNotRequiredGtOrEqZero and stores it, and because the method is not deprecated (its neighbour Builder.stream(String) is). Its javadoc states:

Sets the timeout to wait for a publish acknowledgement from a JetStream enabled NATS server.

Measured, with PublishOptions.streamTimeout set and the context left at its default:

streamTimeout requested publish actually failed after
PT4S 2.033 s
PT1S 2.036 s
(no publish options at all) 2.034 s

All three land on Options.DEFAULT_CONNECTION_TIMEOUT (2 s), which is what the context falls back to. The value has no effect in either direction, so it is not being clamped — it is unread.

Setting the two against each other confirms which one wins: a context with requestTimeout(Duration.ofSeconds(10)) and a publish with streamTimeout(Duration.ofSeconds(1)) fails after 10.03 s.

Expected behavior

Either of these would be fine — the current state is the problem, because the API documents a behaviour it does not implement:

  1. streamTimeout bounds the wait for that publish's acknowledgement, as its javadoc says, taking precedence over the context's requestTimeout. This is the behaviour the Go client already provides — on Custom timeout for a JetStream API request not work nats.go#1246 a maintainer describes AckWait on js.Publish() as "a timeout waiting for the server to acknowledge the publish (if reached, ErrTimeout is returned)".
  2. Or, if the context's requestTimeout is intended to be authoritative, then streamTimeout should be deprecated and its javadoc corrected to say it has no effect, so callers are not silently misled.

Today a caller that sets streamTimeout gets no timeout behaviour and no indication that the setting was ignored.

Server and client version

  • Client: jnats 2.25.3 (current release at time of writing). Also reproduced on 2.21.1 with identical timings.
  • Server: nats-server 2.12.3 (official nats:2.12.3 Docker image, started with -js).

Host environment

  • Linux 6.8.0 x86_64
  • OpenJDK (Temurin) 25.0.1+8
  • Server in Docker; client on the host.

Steps to reproduce

Run a JetStream-enabled server (nats-server -js, or docker run -p 4222:4222 nats:2.12.3 -js), then run this against jnats 2.25.3. The context is given a deadline of one nanosecond, which no network round trip can satisfy, while the publish itself is given a generous ten seconds — so if streamTimeout were honoured, the publish would comfortably succeed:

import io.nats.client.Connection;
import io.nats.client.JetStream;
import io.nats.client.JetStreamOptions;
import io.nats.client.Nats;
import io.nats.client.PublishOptions;
import io.nats.client.api.StreamConfiguration;

import java.time.Duration;

public class StreamTimeoutRepro {
    public static void main(String[] args) throws Exception {
        Connection nc = Nats.connect("nats://localhost:4222");

        nc.jetStreamManagement().addStream(StreamConfiguration.builder()
                .name("repro")
                .subjects("repro.subject")
                .build());

        // The context is given a deadline no round trip can meet...
        JetStream js = nc.jetStream(JetStreamOptions.builder()
                .requestTimeout(Duration.ofNanos(1))
                .build());

        // ...while the publish itself is given a generous one.
        PublishOptions publishOptions = PublishOptions.builder()
                .streamTimeout(Duration.ofSeconds(10))
                .build();

        js.publish("repro.subject", "payload".getBytes(), publishOptions);
        System.out.println("published - streamTimeout was honoured");

        nc.close();
    }
}

Expected: prints published, because the publish was allowed 10 seconds against a healthy local server.

Actual: throws immediately, with the context's 1 ns applied and the publish's 10 s discarded:

Exception in thread "main" java.io.IOException: Timeout or no response waiting for NATS JetStream server
	at io.nats.client.impl.NatsJetStreamImpl.responseRequired(NatsJetStreamImpl.java:258)
	at io.nats.client.impl.NatsJetStreamImpl.makeInternalRequestResponseRequired(NatsJetStreamImpl.java:249)
	at io.nats.client.impl.NatsJetStream.publishSyncInternal(NatsJetStream.java:157)
	at io.nats.client.impl.NatsJetStream.publish(NatsJetStream.java:70)
	at StreamTimeoutRepro.main(StreamTimeoutRepro.java:29)

To see the reverse direction, invert the values — context requestTimeout(10s), publish streamTimeout(1s) — and stall the server after connecting but before publishing (kill -STOP the nats-server process, or docker pause the container, so the TCP connection stays open but no acknowledgement arrives). The publish fails after ~10 s rather than ~1 s.

Metadata

Metadata

Assignees

No one assigned

    Labels

    defectSuspected defect such as a bug or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions