cube-js/cube · error · ConnectionError

Cube Store connection is closed

Error message

Cube Store connection is closed

What it means

WebSocketConnection.initWebSocket refuses to open a new WebSocket after the connection has been closed via close(), throwing a ConnectionError. This makes close terminal: any query issued after release is rejected instead of silently re-opening a socket. In-flight messages on the re-send path are also rejected with this error.

Source

Thrown at packages/cubejs-cubestore-driver/src/WebSocketConnection.ts:120

  // messages that were still in flight at that point.
  private closedAt: Date | null = null;

  public constructor(url: string) {
    this.url = url;
    this.messageCounter = 1;
    this.maxConnectRetries = getEnv('cubeStoreMaxConnectRetries');
    this.noHeartBeatTimeout = getEnv('cubeStoreNoHeartBeatTimeout');
    this.maxMessageSize = getEnv('cubeStoreMaxMessageSize');
    this.currentConnectionTry = 0;
    this.connectionId = uuidv4();
  }

  protected async initWebSocket(): Promise<CubeStoreWebSocket> {
    if (this.closed) {
      // Refusing here is what makes close() terminal: it covers a query issued
      // after the release as well as the re-send path, whose `catch` then
      // rejects the messages that were in flight instead of re-opening for them.
      throw new ConnectionError('Cube Store connection is closed');
    }

    if (!this.webSocket) {
      const headers: Record<string, string> = {};
      headers['x-process-id'] = getProcessUid();

      const webSocket = new WebSocket(this.url, { headers, maxPayload: this.maxMessageSize }) as CubeStoreWebSocket;
      webSocket.on('upgrade', (response: any) => {
        this.cubeStoreVersion = response.headers['x-cubestore-version'] || null;
      });

      webSocket.readyPromise = new Promise<CubeStoreWebSocket>((resolve, reject) => {
        webSocket.lastHeartBeat = new Date();
        const pingInterval = setInterval(() => {
          if (webSocket.readyState === WebSocket.OPEN) {
            webSocket.ping();
          }

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Ensure queries complete before calling close()/release()
  2. Re-create the driver/connection instead of reusing a closed one
  3. Add readiness checks before dispatching queries during shutdown
  4. In tests, scope driver lifetime to the test and close last

Example fix

// before
connection.close();
await connection.query('SELECT 1', []); // throws ConnectionError
// after
await connection.query('SELECT 1', []);
connection.close();
Defensive patterns

Strategy: try-catch

Validate before calling

// track lifecycle in a wrapper
let released = false;
const guard = (conn) => { if (released) throw new Error('driver already released'); return conn; };

Type guard

const isConnectionClosed = (e) => e instanceof ConnectionError && /connection is closed/i.test(e.message);

Try / catch

try {
  await driver.query(sql, params);
} catch (e) {
  if (isConnectionClosed(e)) {
    driver = createDriver(); // recreate instead of reusing closed connection
    return driver.query(sql, params);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling driver.query()/release() or re-sending a queued message after WebSocketConnection.close() has been called; using a driver after its connection was released (e.g. after pool teardown or process shutdown).

Common situations: App shutting down while queries are still queued; a query orchestrator retrying a request after the Cube Store connection was released; holding a driver reference past its lifecycle in tests.

Related errors


AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02). Data as JSON: /api/errors/bdad47fdd32b60e0. Report an issue: GitHub.