Skip to content

Conversation

@tejasbadadare
Copy link
Contributor

@tejasbadadare tejasbadadare commented Jan 10, 2025

Purpose

Improve JS SDK reliability via redundant parallel websocket connections (default of 3.) This reduces the chance of dropped messages during reconnection events, since other websockets will be receiving the otherwise missed messages. The parallel streams are merged and deduped to provide a single reliable stream to the SDK user.

Try it out by running pnpm run example (examples/index.ts)

Technical details

  • PythLazerClient is a thin wrapper around WebSocketPool which provides some convenient parsing on the received messages.

  • Introduced WebSocketPool, which handles resilience at the pool level.

    • It wraps the collection of redundant ReliableWebSocket instances, and provides usage semantics similar to a single standard WebSocket so that users don't have to deal with the details of managing multiple connections.
    • Takes multiple URLs and round robins the sockets between them.
    • Handles broadcasting send commands to all sockets in the pool for un/subscribing.
    • Manages the user's subscriptions. Subscription requests are replayed to the underlying ReliableWebSockets to reestablish the stream upon reconnection events.
    • Deduplicates messages from all of the cxns using a TTLCache before forwarding them to the user-provided message handler.
      • A TTLCache was used to avoid unbounded memory usage, and the TTL is set to a short 10 seconds since the deduping only needs to happen between messages received very close to each other in time. No locking needed due to the single threaded event loop.
  • Reused and adapted ReliableWebSocket to handle resilience at the individual connection level.

    • This class wraps WebSocket and restarts unexpectedly dropped connections.
    • Handles restarting the cxn if no message or ping received for 10s, or if the underlying WebSocket closes unexpectedly
    • Takes wsOptions to pass to the WebSocket init, enabling using native WS features like header based auth
    • Known sharp corner here is that the ReliableWebSocket class doesn't wait to receive an open message before sending messages. However, this hasn't been an issue in practice when testing manually.

Testing

Tested manually successfully with up to 50 parallel connections. Reconnections are handled cleanly and no duplicate messages are delivered.

@vercel
Copy link

vercel bot commented Jan 10, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
api-reference ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 10, 2025 7:37pm
proposals ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 10, 2025 7:37pm
staking ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 10, 2025 7:37pm
2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
component-library ⬜️ Ignored (Inspect) Visit Preview Jan 10, 2025 7:37pm
insights ⬜️ Ignored (Inspect) Visit Preview Jan 10, 2025 7:37pm

Comment on lines 117 to 123
addSubscription(request: Request) {
if (request.type !== "subscribe") {
throw new Error("Request must be a subscribe request");
}
this.subscriptions.set(request.subscriptionId, request);
this.sendRequest(request);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's good to listen for subscribed response here and throw an error if it was not successful

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added error handling code to dedupeHandler, which will throw an Error if the server responds with a subscriptionError or error type message. Btw in my manual testing, looks like the server abruptly closes the connection for issues like bad subscription payload.
image

Copy link
Collaborator

Choose a reason for hiding this comment

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

we should improve the server side error handling for sure

Comment on lines 130 to 136
removeSubscription(subscriptionId: number) {
this.subscriptions.delete(subscriptionId);
const request: Request = {
type: "unsubscribe",
subscriptionId,
};
this.sendRequest(request);
Copy link
Contributor

Choose a reason for hiding this comment

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

same here

@tejasbadadare tejasbadadare merged commit 7cb1725 into main Jan 10, 2025
8 of 11 checks passed
@tejasbadadare tejasbadadare deleted the tb/lazer/js-sdk-resilient-cxns branch January 10, 2025 19:35
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.

4 participants