Skip to content

Add connectAsynchronously to Options allowing a nats connection initialization that does not block. - #33

Open
nviliunov-evolution-throwaway wants to merge 2 commits into
masterfrom
nv/conn-async
Open

nviliunov-evolution-throwaway wants to merge 2 commits into
masterfrom
nv/conn-async

Conversation

@nviliunov-evolution-throwaway

@nviliunov-evolution-throwaway nviliunov-evolution-throwaway commented Oct 5, 2026 •

Copy link
Copy Markdown

A conceptual replacement for jnats' Nats.connectAsynchronously. API changes from the Java library:

  • jnats does not return any object. It expects you to receive a connection through a callback when it's ready.
    • I could have used a F[F[Connection[F]]] signature, but it makes things less ergonomic, as the easy path is to
      flatten the result, which defeats the original purpose.
    • calls on the resulting object block until the connection is ready – this sort of mirrors putting messages into the internal queue, only the queue is already "full"
  • jnats spawns a native Thread that does connection initialization. I know that jnats is liberal with its usage of threads, but we can easily avoid this particular one by doing it in a cats-effect fiber.
  • jnats accepts reconnectOnConnect, while I always retry a connection. The retry is done in cats-effect instead of jnats, providing cancellation windows between attempt rounds.

@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: evolution-gaming/coderabbit/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dbdb561e-5692-4732-9bd6-d14e4becf45a
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

def connectUntilEstablished(javaConnection: Deferred[F, WrappedConnection[F]]): F[Unit] =
Async[F]
.uncancelable { _ =>
Async[F].blocking(JNats.connect(javaOptions)).flatMap(c => javaConnection.complete(new WrappedConnection[F](c, options)).void)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will reconnectOnConnect be ignored?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, reconnectOnConnect is used only when the connection is synchronous

val wrappedConnection = connection match {
case wrapped: com.evolution.natseffect.impl.WrappedConnection[F] => wrapped
case _: com.evolution.natseffect.impl.AsyncConnection[F] =>
throw new IllegalArgumentException("JetStream is not supported on a connection created with Options.connectAsynchronously")

@stasimus stasimus Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole point of connectAsynchronously is that the app starts even when NATS is down. But JetStream.make now throws on an AsyncConnection, so anyone using JetStream or KeyValue crashes on startup the moment they turn the option on, broker up or not.


override def flush(timeout: Duration): F[Unit] = withConnection(_.flush(timeout))

override def status: F[Status] = withConnection(_.status)

@stasimus stasimus Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't wait for the connection in this one method. Peek at the Deferred with tryGet. If it's empty, return Status.CONNECTING

.uncancelable { _ =>
Async[F].blocking(JNats.connect(javaOptions)).flatMap(c => javaConnection.complete(new WrappedConnection[F](c, options)).void)
}
.handleErrorWith(_ => Async[F].sleep(options.reconnectWait) >> connectUntilEstablished(javaConnection))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All connect errors swallowed, retried forever. The handleErrorWith(_ => ...) drops AuthenticationException and misconfiguration errors alike. With bad credentials the fiber loops indefinitely and callers only ever see the generic NotEstablishedException, with no log or callback exposing the cause.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants