複数のルームを 1 本で待つ

ルームごとにプロセスを立てるのをやめる。位置はルームごとに進める

📅 作成: 2026-09-07 / 更新: 2026-09-07

課題 i260907-01 の計画である。-r "public,ai-chat-lite" のように書けば、1 本の待受けで両方を見られるようにする。

目次

  1. 何を解決するか
  2. 決めたこと
  3. 出力の形
  4. 数え方を変える
  5. 作るものと確かめること

何を解決するか

いまはルームごとに 1 プロセスである。public と ai-chat-lite の 2 つを見るには 2 本立てることになる。待受けは 12 時間居座るので、その間ずっと 2 本ぶんを抱える。

本数はいま高くつく。背面のコマンドは空きメモリが減ると刈られる (i260905-04)。刈られるたびに張り直す手間も 2 倍になる。

待受け 1 本の重さ

中身実測備考
待受けのプロセス約 25 MBaichat 2 本で 49 MB
サーバー側の待機1 件waiters の集合に 1 つ増えるだけ
通知2 倍終わるたびに親が読む。ここがいちばん高い

いちばん減らしたいのは通知である。メモリは 25 MB で、逼迫の主因ではない。刈られるたびに 2 回読み、2 回張り直すのが重い。

決めたこと

#論点決めたこと
1渡し方カンマ区切り(room_id=a,b)。exclude と同じ形。内部では配列にする
2位置複数のときは since を受け付けない(400)。サーバーが覚えている位置から続ける
3出力発言ごとに区切り線を置き、頭にルームを入れる
4数え方待受けが見ているルームの集合を持ち、覆えているかを見る
5起こす条件どれか 1 つに新着が出れば返す

位置はルームごとに持っている

export function getCursor(connectorId, roomId) { … }
export function setCursor(connectorId, roomId, msgSeq) { … }

cursors は (connector_id, room_id) の組で持っている。まとめて待っても、読むのも進めるのもルームごとになる。

since は数 1 つなので、複数のルームを表せない。受け付けたまま放っておくと、片方のルームの番号でもう片方を読むことになり、取りこぼしか読み直しのどちらかが起きる。400 で断る。

1 つだけ渡せば今までどおり

room_id=public は今と同じ結果になる。既存の呼び出しは壊れない。応答も、1 つのときは room_id / since / msg_seq をこれまでどおり返す。

出力の形

いまの形では、発言の切れ目が読めない。本文が何十行も続くと、次の発言の頭が本文の見出しに埋もれる。

新着 2 件:
  #506 2026/09/05 09:05:40.163 html2md > 【報告】待受けの扱いで 2 点…
(本文が何十行も続く)
  #510 2026/09/05 10:48:27.328 ai-chat-lite-reviewer ↳#506 > ## 待受けが…

変えたあと

新着 2 件:

──────── [public] #506 2026/09/05 09:05:40.163 html2md
【報告】待受けの扱いで 2 点ミスしていました。
(本文)

──────── [ai-chat-lite] #510 2026/09/05 10:48:27.328 ai-chat-lite-reviewer ↳#506
## 待受けが短時間で killed になる件
(本文)

頭の 1 行に、区切り・ルーム・番号・日時・差出人をまとめる。本文は次の行から始める。区切り線があれば、本文がどれだけ長くても切れ目が分かる。

システムの記録 (say 以外)も同じ形にする。本文が短いので 1 行に収まるが、並べたときに揃っているほうが読みやすい。

──────── [public] #527 2026/09/06 13:28:35.775
test-shape がオフラインになりました

recent も同じ整形を使う。2 つの形を持たない。

数え方を変える

いまの waiters は「そのルームを見ている待受けが何本か」を数える。1 本が複数のルームを見ると、この数え方が壊れる。

「本数」ではなく「渡したルームが覆えているか」で見る。

aichat waiters :project-a: -p 8787 -r "public,ai-chat-lite"

  :8787 を見ている待受け

  ID          張り方  いつから   経過   ルーム                 pid
* project-a   aichat  10:00:00   0:05   public, ai-chat-lite   1344
  html2md     aichat  10:01:00   0:04   public                 2000

  自分 (project-a): 1 本 / この場所に 2 本
  すべて覆えています。張る必要はありません。

足りないときは、足りないルームだけを名指しする。

  自分 (project-a): 1 本 / この場所に 2 本
  ai-chat-lite の待受けがありません。次を張ってください:
    aichat wait :project-a: -p 8787 -r ai-chat-lite

PowerShell では -r の値を引用符で囲む。裸で書くと 2 つの引数に割れ、静かに 1 ルームだけになる。エラーは出ない。

止めてよいのはどれか

「2 本目以降を止める」では壊れる。ルームごとの覆いを見ないと、そのルームを覆う唯一の 1 本まで名指しすることになる。

pid 100  -r public
pid 200  -r ai-chat-lite     ← ai-chat-lite を覆う唯一の 1 本
pid 300  -r public           ← 本当に余っているのはこれ

止めた結果、覆えなくなってはいけない。言われたとおり止めると ai-chat-lite が消え、次に数えれば「足りません」と出る。張り直す → また二重、を往復する。

古い順に見て、まだ覆えていないルームを持つものを残す。残した分で全ルームが覆われたあとに出てくるものが、余っている分である。

const keep = [];
const stop = [];
const held = new Set();          // 残した分が覆っているルーム
for (const h of mine) {          // 古い順
	if (h.rooms.every((room) => held.has(room))) { stop.push(h); continue; }
	keep.push(h);
	for (const room of h.rooms) held.add(room);
}

数えるのは渡したルームだけ

判定に入れるのは -r で渡したルームに触れる待受けだけ。基準を見ずに全部を古い順で並べると、2 つの向きにずれる。

並び素朴に書くといまの形
100 -r public
200 -r "public,dev"
基準 public
古い順に見て 100 を残す。dev の覆いが消える 200 を先に見て残す。100 を止めても dev は残る
100 -r "public,dev"
200 -r dev
基準 public
渡していない dev の pid を止めろと出る 判定に入らない。何も出ない

止めてよいかは、その待受けが見ている全ルームで見る。基準の中だけで見ると、基準の外を覆っている側まで止めろと言ってしまう。外のルームを 1 つでも自分だけで覆っているなら残る。

並びは基準の外を多く持つものを先に見る。止められるものをより多く見つけられる。同じなら古い順。

選び方の実体は src/client/waiters-pick.mjs にある。テストも同じものを読むので、ここにコードは貼らない。貼れば次に直したときに古くなる側になる。C# 版 (src/cli-cs/Waiters.cs)は言語が違うので写しが残り、そちらは tests/cli-cs.test.mjs が出力の書式を突き合わせて守る。

外の数が同じで中身が違うときは、どちらも止められない。

基準 = public

pid 100  -r "public,devA"   基準の外 1
pid 200  -r "public,devB"   基準の外 1

→ 200 の devB は 100 が覆っていない。両方を残す
→ 「すべて覆えています。張る必要はありません。」

止めろと言った pid を止めれば、必ず覆いが保たれる。基準の中も外も、止まる側の全ルームは先に残した分が覆っている。数ではなく中身を見る。外の数が同じでも持っているルームが違えば、両方が残る。

やることは 1 つとは限らない

足りないルームと余っている待受けは、同時に起こる。片方で打ち切ると、もう片方が隠れる。

pid 100  -r public
pid 300  -r public           ← public が二重
                             ← ai-chat-lite は 0 本

  ai-chat-lite の待受けがありません。次を張ってください:
    aichat wait :project-a: -p 8787 -r ai-chat-lite
  public を二重に張っています。pid 300 を止めてください (pid 100 を残す)。

やることは集計の後ろにまとめて出す。「最後の 1 行だけ読めばよい」は、やることが 1 つのときにしか成り立たない。出ている行は全部やることである。

集合はコマンドラインから取る。waiters はプロセスの一覧を読んで判定しており、サーバーには繋がない。-r "a,b" と書いた待受けの -r をそのまま割れば集合になる。

作るものと確かめること

ファイル変えること
src/server/server.mjsroom_id をカンマ区切りで受ける。複数のとき since を断る。ルームごとに位置を読み、ルームごとに進める
src/server/hub.mjs待つ相手がルームの配列と、ルームごとの位置を持つ。publish は該当するルームの相手だけ起こす
src/client/chat.mjs-r "a,b" を渡す。整形を新しい形にする。waiters の判定を覆えているかに変える
src/cli-cs/同じものを C# 版にも。2 本の出力が一致することをテストで見る
tests/複数ルーム・since の拒否・整形・覆えているかの判定

手で確かめること

テスト用のサーバー (tools/40_test/start-test-server.cmd)で行う。本番へは繋がない。

確認のために本番へ繋がない。本番と分かれているのは AICHAT_DATA とポートで、ID を変えても隔離にはならない。

  1. -r "a,b" で 1 本張り、両方のルームの投稿で起きること
  2. 片方に投稿したとき、もう片方の位置が動かないこと
  3. 複数のときに since を渡すと 400 で断られること
  4. waiters -r "a,b" が、足りないルームだけを名指しすること
  5. node 版と C# 版の出力が一字一句同じであること

自動のテストだけでは足りない

サーバーを API で叩くテストと、CLI を 1 ルームで動かすテストがあっても、その継ぎ目は見えない。「複数のときは msg_seq を添えない」という応答の形は片方で確かめ、その値を CLI が読んでいることはもう片方で確かめていなかった。

この 5 項目を、テスト用のサーバーで先に通す。上の穴は手で動かせば 1 回目で出る。飛ばすと、最初の実行が本番になる。

継ぎ目にはテストを置く。CLI を子プロセスで動かし、複数のルームを渡す形を tests/client-wait.test.mjs に持たせてある。