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

Address retirement on port-release timeout is one-shot for the process lifetime; with a 1-address pool the run deadlocks in `getNextAvailableLoopbackAddress` even though retirement is meant to be a parking action

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

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

評価

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

調査の方向性

dist/harperLifecycle.js:618-629 の teardownHarper から始め、dist/loopbackAddressPool.js:414-425 の getNextAvailableLoopbackAddress と removeDeadProcessesFromPool を追跡します。1 アドレスのプール、--isolation=none、固定ポートを占有する listener を使って再現します。タイムアウト後も連続するスイートがハングせず、強制終了または再起動されたプロセス間でも永続化されたプール状態が安全なままであれば完了です。

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

説明

teardownHarper waits DEFAULT_PORT_RELEASE_TIMEOUT_MS (default 5s) for all fixed Harper
ports to free, and on expiry it does not release the loopback address
(dist/harperLifecycle.js:618-629 in 0.7.1): the slot stays parked under the current
process's PID and the warning says "The slot will be reclaimed when this process exits."
There is no later re-check. Retirement is permanent for the process lifetime, however briefly
the ports were actually held.

That interacts badly with two facts:

  • Under --isolation=none (harper-integration-test-run) every suite in the run shares one
    process, so "reclaimed when this process exits" means "never, for the rest of this run".
  • On stock macOS only 127.0.0.1 is bindable (127.0.0.2+ need aliases), so a practical local
    pool is one address (HARPER_INTEGRATION_TEST_LOOPBACK_POOL_START=1, COUNT=1).

After one retirement, the next suite's startHarper enters
getNextAvailableLoopbackAddress and spins forever: the free-slot scan finds nothing and
removeDeadProcessesFromPool (dist/loopbackAddressPool.js:414-425) cannot reclaim a slot
whose holder PID is the caller's own live process. The wait is unbounded and silent.

Measured sequence, one macOS host, 2026-08-14, three consecutive
npm run test:integration invocations of the same two-file suite:

Run (local time) PORT_RELEASE_TIMEOUT_MS Suite 1 Then
15:42 5000 (default) green, 15.2s total wait expired, warning logged, address retired
15:53 5000 (default) green, 13.4s total same
16:05 60000 (override) green, 68.1s total 60s wait also expired; suite 2 never acquired an address; the harness had to be killed externally and $TMPDIR/harper-integration-test-loopback-pool.json reset by hand

Raising the timeout 12x changed only the stall length, which separates the two defects: why
the ports were held is its own issue (on this host an unrelated resident MQTT broker held
1883, filed separately), but the one-shot retirement is what turns any expiry into a wedged
run. In the case the code comments actually contemplate (a slow Harper child that exits a few
seconds after the deadline), the ports free and the address stays retired anyway; that case
follows directly from the code path (no re-check exists) and is expected rather than
separately measured here.

A second, related defect: the pool persists holder PIDs in
$TMPDIR/harper-integration-test-loopback-pool.json across processes
(dist/loopbackAddressPool.js:23). A run killed with SIGKILL leaves its PID parked; after
the 16:05 run was killed, the pool file still carried that run's slot state and was reset to
[] by hand before anything else ran (measured). What happens without that reset is a code
read, not a measurement: reclaim runs only when the pool is exhausted and tests liveness
with process.kill(pid, 0), so a dead run's slot is reclaimed eventually, unless the OS has
reused the PID for any live process, in which case the slot stays parked indefinitely and
later runs are poisoned by a run that no longer exists.

Suggested fixes, independently useful:

  • On each acquisition attempt inside the same process, re-probe addresses this process
    previously retired instead of skipping them for the process lifetime. The conflict canary
    in getNextAvailableLoopbackAddress already exists to make handing out a still-held
    address safe; a freed address should return to service on the next probe.
  • Stamp pool entries with the holder's process start time (or a timestamp plus a nonce) so
    removeDeadProcessesFromPool can distinguish a dead run from a reused PID.
  • Name the still-bound ports in the teardown warning (today it prints none), so an operator
    can see in one line whether the holder is a Harper child or an unrelated service.

Reproduction

  1. macOS with no loopback aliases; HARPER_INTEGRATION_TEST_LOOPBACK_POOL_START=1,
    HARPER_INTEGRATION_TEST_LOOPBACK_POOL_COUNT=1.
  2. Ensure any listener occupies one fixed Harper port on 127.0.0.1 for the duration (e.g.
    nc -l 127.0.0.1 1883 &), so the port-release wait must expire.
  3. Run two Harper suites via harper-integration-test-run --isolation=none.
  4. Observed: suite 1 passes; teardown logs
    Harper ports on 127.0.0.1 still in use after teardown (5000ms); NOT recycling the address;
    suite 2 blocks forever in getNextAvailableLoopbackAddress with no output. Kill the run;
    the pool JSON still holds the dead PID.

Measured on

Component Version
@harperfast/integration-testing 0.7.1 (dist/harperLifecycle.js, dist/loopbackAddressPool.js)
harper (system under test) 5.2.1
Node v24.16.0
OS macOS 26.5.2 (arm64, Darwin 25.5.0), stock loopback (127.0.0.1 only)
suites datadog-agent-binary bbeb99a integration tests, --isolation=none

主要言語
TypeScript
スター
1
フォーク
0
PR マージ指標
30日以内にマージされた PR はありません

環境構築

はじめの一歩

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

HarperFast/integration-testing のほかの issue

HarperFast/integration-testing の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

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

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