Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Connection State Recovery fails if client reconnects before server emits any events ("Zero-Event" reconnect)

オープン
#5,538 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 2 日以内に返信

@marwan562 がすでに取り組んでいます。

2026年8月23日 から。

  • #5541 @marwan562 による — オープン

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
56/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
javascript, node.js, typescript

調査の方向性

socket.io/lib/namespace.ts のリカバリーゲートから開始し、socket.io-client が _lastOffset をどのように保持し、再接続ハンドシェイクを送信するかを追跡します。イベントが 0 件のパスを restoreSession() および既存の接続状態リカバリーの動作と比較します。事前にイベントがない状態での再接続によってセッション ID、ルーム、データが保持され、socket.recovered が true として報告されれば完了です。

索引モデルが issue の本文から書いたものです。

説明

enhancement

Describe the bug
When connectionStateRecovery is enabled, a client that connects, joins rooms, and disconnects before the server emits any events cannot recover its session on reconnection.

Even if the client reconnects within maxDisconnectionDuration, socket.recovered evaluates to false, a new socket.id is generated, and room memberships / socket.data are lost. This happens because _lastOffset on the client is undefined in the zero event case, causing the server's typeof offset === "string" check in namespace.ts to fail and bypass restoreSession().

To Reproduce

Socket.IO server version: 4.8.1

Server

import { Server } from "socket.io";

const io = new Server(3000, {
  connectionStateRecovery: {
    maxDisconnectionDuration: 2 * 60 * 1000,
  },
});

io.on("connection", async (socket) => {
  if (socket.recovered) {
    console.log(`RECOVERED: ${socket.id} (rooms: ${Array.from(socket.rooms).join(", ")})`);
    return;
  }

  console.log(`INITIAL CONNECT: ${socket.id}`);
  await socket.join("room:alerts");
  // Deliberately no events emitted to the client
});

Socket.IO client version: 4.8.1

Client

import { io } from "socket.io-client";

const socket = io("http://localhost:3000", {
  reconnection: true,
});

socket.on("connect", () => {
  console.log(`connect ${socket.id} (recovered: ${socket.recovered})`);

  // Simulate a brief network drop shortly after connecting
  setTimeout(() => {
    socket.io.engine.close();
  }, 200);
});

Expected behavior
When the client reconnects after the transient drop, the server should recognize the pid, restore the previous session (socket.id, room memberships, socket.data), and set socket.recovered = true.

Platform:

  • Device: PC / Mac / Server
  • OS: Linux / macOS / Windows
  • Node.js version: 20.x / 22.x

Additional context

  • Root Cause: In socket.io-client, _lastOffset remains undefined until a packet of type EVENT with an offset is received. On reconnect, the handshake sends { pid: "<id>", offset: undefined }. In socket.io/lib/namespace.ts, recovery is gated by:

    if (
      this.server.opts.connectionStateRecovery &&
      typeof sessionId === "string" &&
      typeof offset === "string"
    ) {
      session = await this.adapter.restoreSession(sessionId, offset);
    }
    

    Because offset is undefined, restoreSession() is skipped entirely despite having a valid active pid.

  • Impact: Quiet subscriber channels (e.g. alert rooms, idle game lobbies) lose session state on brief network blips unless application code manually emits a synthetic dummy event on first connect to "prime" _lastOffset.

  • Potential Solutions:

    1. Include the current server stream offset in the initial CONNECT (packet type 0) handshake payload alongside { sid, pid } so _lastOffset is initialized at connection time.
    2. Allow restoreSession(sessionId, offset) to run when offset is undefined, restoring id, rooms, and data without replaying events.
主要言語
TypeScript
スター
63.2k
フォーク
10.3k
平均マージ
3時間 24分
マージ済み PR(30日)
1

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

socketio/socket.io のほかの issue

socketio/socket.io の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。