Skip to content

WebSocket: a dropped connection leaves the in-flight query pending forever #283

Description

@damlayildiz

Summary

On the WebSocket transport, if the connection drops while a query is in flight, the query's promise never settles: no result, no error. The disconnect is reported to the connect callback, which already ran, so the pending command is never told. Unlike the TLS transport, there's also no timeout to fall back on.

Version: @sqlitecloud/drivers 1.0.878 (socket.io-client 4.8.3), with usewebsocket: true, in Node and in the browser.

Reproduce

The easiest trigger is a request over 1 MB, which the gateway drops (see the Context section):

const db = new Database({ host, apikey, database, usewebsocket: true })
await db.sql(`SELECT length(X'${'AB'.repeat(495_000)}') AS n`) // 0.99 MB → ok, ~0.7 s
await db.sql(`SELECT length(X'${'AB'.repeat(505_000)}') AS n`) // 1.01 MB → never resolves

In the second case the console logs SQLiteCloudConnection.connect - error connecting … SQLiteCloudError: Disconnected (ERR_CONNECTION_ENDED, cause transport error), but the db.sql(...) promise stays pending forever.

Request size Result
0.50 MB ok, 641 ms
0.99 MB ok, 676 ms
1.01 MB pending forever, connection closed
1.50 MB pending forever, connection closed

Any other drop mid-query (for example a ping timeout) goes through the same code path.

Cause (lib/drivers/connection-ws.js)

  1. Wrong callback: connectTransport registers socket.on('disconnect', …) (line 94). On disconnect it calls close(), then the connect callback, which already ran on connect. Nothing reaches the command in flight.
  2. The pending query is never resolved: transportCommands (line 133) waits on the ack of socket.emit('GET /v2/weblite/sql', …, ack). When the socket closes, that ack never fires and nothing else calls the command's callback. Because close() also calls this.operations.clear(), the queued done is dropped as well.
  3. No timeout: the TLS transport honours config.timeout (default 300 s, connection-tls.js:134); the WebSocket transport has none.

Context: the 1 MB limit (a configuration decision, not a bug)

The drop at ~1 MB comes from the gateway. sqlitecloud-gateway src/gatewayWebsocket.ts:30 creates the socket.io Server without maxHttpBufferSize, so socket.io's default of 1,000,000 bytes applies, and engine.io closes any connection that sends a larger message. That limit is worth reconsidering:

  • BLOBs sent as X'…' hex double in size, so a ~490 KB file already exceeds it.
  • A multi-statement transaction (for example a Studio Save of several rows) can reach it too.

Raising it to a deliberate value, e.g. 16–64 MB in line with the core's limits, and documenting it would help. This issue doesn't depend on that change: whatever the limit is, the driver should report the error instead of hanging.

Impact

The SQLite Cloud dashboard's Studio uses this transport. A save that includes a BLOB over ~490 KB, or any batch over 1 MB, shows "Saving…" forever. Nothing is written, but the user gets no error.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions