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

[y-partyserver] provider.destroy() leaks reconnect timer, hanging Node processes and delaying GC

オープン 初心者向け
#402 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

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

調査の方向性

y-partyserver/providerのエントリーポイントから開始し、WebSocketのcloseイベントと再接続のスケジューリングを通じてWebsocketProvider.destroy()を追跡します。issueのNode.js再現スクリプトを実行し、providerを破棄した後にイベントループを開いたままにする再接続タイマーが残らないこと、またプロセスが遅延なしに終了することを確認します。

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

説明

I maintain YAOS a local-first sync engine running inside Obsidian, built on DOs and y-partyserver. While debugging a CI pipeline that inexplicably took 140 seconds instead of 36 seconds, we discovered that y-partyserver is leaking timers directly into the event loop every time a connection is torn down.

The Bug
When provider.destroy() is called on a WebsocketProvider in y-partyserver, the WebSocket's onclose event listener unconditionally queues a reconnect timer via setTimeout(setupWS, backoff, provider).

While the setupWS callback correctly checks this.shouldConnect and aborts (meaning it doesn't actually reconnect), the setTimeout handle itself is never stored or cleared during the destroy() lifecycle.

The Real-World Impact

  1. Delayed Garbage Collection (Electron/SPA): In long-lived environments like ours, the setTimeout closure retains the entire provider instance and its massive associated Y.Doc in memory until the timer naturally fires (up to 2.5s). In environments with flapping networks where connections are repeatedly destroyed and recreated, this creates a massive GC backlog and stutters the UI thread.

  2. Node.js / CI Hangs: The Node event loop refuses to exit until the backoff timer fires. In test suites that instantiate and destroy multiple providers, this compounds into massive CI delays, forcing developers to use process.exit(0) hacks just to allow scripts to terminate cleanly.

This issue is inherited DNA from y-websocket. The exact same architectural flaw was reported upstream 6 months ago (see yjs/y-websocket#198), but it has not merged. Since y-partyserver ships its own adapted provider implementation, it needs to be patched here.

Steps to Reproduce
Running this Node.js script proves the event loop is held hostage by the leaked timer, even after the provider is explicitly destroyed:

import YSyncProvider from "y-partyserver/provider";
import WebSocket from "ws";

async function run() {
  const ydoc = new Y.Doc();
  const provider = new YSyncProvider("ws://127.0.0.1:8787", "room", ydoc, { 
    WebSocketPolyfill: WebSocket 
  });

  // Wait for initial connection logic
  await new Promise(r => setTimeout(r, 100));

  console.log("Destroying provider...");
  provider.destroy();

  // Wait a tick for the ws 'close' event to fire and schedule the leak
  await new Promise(r => setTimeout(r, 100));

  const handles = process._getActiveHandles().filter(
    h => h.constructor.name === 'Timeout' || h.constructor.name === 'TimerWrap'
  );
  
  console.log(`Active timers preventing exit: ${handles.length}`);
  // Process hangs here for up to 2.5 seconds instead of exiting immediately
}

run();
主要言語
TypeScript
スター
1.3k
フォーク
84
PR マージ指標
30日以内にマージされた PR はありません

環境構築

はじめの一歩

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

cloudflare/partykit のほかの issue

cloudflare/partykit の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

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

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