Repository navigation
feat(rpc): #56 の要求・応答の形と、ホスト・クライアント側の照合(PR (a)) - #67
Conversation
Add host, engine, and composition identifiers, sequenced change and restore request shapes, the shared Rejected(Reason) response, and host-targeted shutdown responses. Bump the RPC protocol to v6. Behavior is deferred to the rest of #56a: the host answers Change / Restore with Error until matching, records, and restore are wired. Refs fukuyori#56 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Draft PR をありがとうございます。形を設計スレッド(9/21〜9/23)と突き合わせました。識別子( 確認の 5 点への回答
型を確定する段階で決めたい追加の 3 点A. B. C. 上記 A〜C を反映した時点で形を確定とし、(a) の残りに進んでください。 |
- Add ChangeOutcome and return it from Restored { then } and the new
Changed { outcome }, so Restore's `then` needs no extra RPC.
- Add ChangeRequest::kind() as the single ChangeRequest -> ChangeKind map.
- Add config_version: Option<[u8; 32]> (always None until fukuyori#66) to
Create, ShutdownIfConfigDiffers and Change.
- Document that a host_id mismatch on ShutdownIfConfigDiffers answers
ShutdownSkipped instead of Bool(false).
- Move RequestRecord into server.rs as host-internal state (no serde,
no re-export).
Refs fukuyori#56
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
レビューありがとうございます。A〜C と、確認 5 点のうち今回の形に関わる分を
|
|
既存の |
Restore can apply a change request through `then`, so it carries the same config version as Create / ShutdownIfConfigDiffers / Change. Always `None` until fukuyori#66, which checks it before restoring and applying `then` and applies neither on rejection. v6 is unreleased, so no further protocol version bump. Refs fukuyori#56 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
|
最新の Windows CI は、既存のモデル変換テストが Hugging Face キャッシュの
戻り値の変更に伴う |
Host: - Take a random host_id at startup and give each engine a generation (host_id, count) on Create / Reload; new engines start with no owner. - Keep one RequestRecord per tsf_id (last seq and reply, floor, sessions, in_flight, last_used). Hello registers the session, answers record_found, and creates missing records with floor = highest_sent. Records with no session and nothing in flight are collected oldest first above 256; otherwise the table grows and warns once. - Change: generation, then seq (same seq replays the stored reply, older or <= floor is ResultUnavailable), then owner, all under the engine lock (lock order state -> records). - Restore: seq check, then reset_preedit / force_preedit / push_char, reclaim the done converter, move ownership and apply `then` in the same lock. - Track the owner of a started BG conversion and hand its candidates only to that owner. - Shutdown / ShutdownIfConfigDiffers answer ShutdownSkipped for another host_id, and handle_session exits based on the reply. - Legacy change variants share apply_change without matching. Client: - A process-wide ledger holds tsf_id, the seq counter and the single Unresolved request; Hello reports tsf_id and highest_sent. - change() / restore() number requests, resend the same seq on retry, keep failures unresolved and refuse new changes until restore or abandon_unresolved(). - shutdown() fixes the target host_id before sending and returns Skipped; shutdown_if_config_differs() returns SameConfig / Restarting / Skipped. TSF: Skipped maps to ApplyOutcome::HostReplaced, which keeps the pending apply and does not fall back to an unconditional Shutdown. TSF senders still use the legacy variants. Refs fukuyori#56 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
形の確定をありがとうございます。(a) の残りのうちホスト側とクライアント側を
|
|
f20187e と説明を確認しました。TSFの送信を Change へ移す範囲については、案1を採用します。TSFの Change 移行と旧変更系variantの _Reserved 化は (b) へ移してください。 現在の ChangeOutcome::Unit / Bool だけでは、拒否後の復元元となる編集状態を保持できません。(b) で編集状態を応答に追加し、compositionごとの復元元保持、composition依存の読み取りの Result 化、TSFの Change 移行を一緒に行う形とします。(a) の段階では旧variantを使い続け、利用者向けの #56 の修正は (a)+(b) で完成するものとします。リリースも予定どおり両方が揃ってから1回です。 「スレッドで決めていなかったところ」として挙げられた7点は、次の条件を含めて了承します。
旧所有者のBG変換が実行中の場合の扱いは、composition依存の BgStatus の照合・Result 化とともに (b) で確定してください。現在の bg_reclaim() は非ブロッキングで、実行中の旧変換を待たないため、通常の "running"、OwnerMismatch、旧所有者のワーカー使用中を区別できる専用の結果と、TSF側の遷移が必要です。具体的な応答型と遷移を、(b) の実装前に示してください。 ShutdownSkipped の最小対応は依頼した範囲どおりです。反映待ちから現在の設定を使って新しいホストへ Create し直す #65 の組込みはこちらで行います。 CIの成功は確認しました。一方、apply_change / Restore のDLL呼び出し部分と実機での復旧動作は未確認なので、(b) の統合後に別途検証します。今回の回答は (a)/(b) の範囲と設計判断への回答であり、マージの承認ではありません。 (a) はこの範囲で完成とし、Draft を外してレビュー待ちにしてください。(b) は (a) のマージ後に、main を土台とした別の PR として始めてください(9/24・9/25 のとおり)。 |
|
範囲の確定と 7 点のご確認をありがとうございます。案 1 で進めます。Draft を外してレビュー待ちにしました。本文は、「まだ入れていないもの」を合意した内容に書き換え、(b) の受入条件を書き足しています。 (b) は (a) のマージ後に、main を土台とした別の PR として始めます。着手の前に、旧所有者の BG 変換が実行中の場合の応答型(通常の running/ |
|
f20187e を確認しました。(a) の実装範囲はおおむね合意どおりですが、マージ前に次の点を修正してください。 1.
|
… the engine check Client: - send_change no longer resolves the request on Rejected(ResultUnavailable); the next change fails with ChangeError::Unresolved. - send_restore does not fall back to the previous unresolved request on Rejected(ResultUnavailable): `then` stays unresolved if present, otherwise the previous unresolved request stays. Other rejections and Error still restore `prev`. - Document that the Hello-derived engine_gen() may predate a later Create and must not be the first Change's expectation. Host: - dispatch_restore checks the stored reply (Replay / Unavailable) before requiring an engine, so a resent Restore gets the stored Restored even if the engine was dropped in between. - Describe the BG handling as it is: bg_reclaim does not wait, a running conversion of another owner keeps running and only the owner record is cleared; legacy BgTakeCandidates can still take its result by key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
ご指摘ありがとうございます。4 点を 72a959f で修正しました。
|
|
72a959f の修正を確認しました。依頼した4点と PR #67 は rebase merge しました。main の先端は 次は合意どおり、最新の main を土台に (b) を別PRとして進めてください。ただし、実装へ入る前に、旧所有者のBG変換が実行中の場合について、次の3つを区別する具体的な応答型とTSF側の遷移を、このスレッドか #56 に示してください。
(b) では、これまでの合意どおり次をまとめて扱ってください。
#65 の再接続組込みは、(b) と並行してリリース前までにこちらで行います。 配布インストーラー更新時の旧ホスト停止は、(a) と (b) のマージ後、v6 のリリース前に、現行の リリースと実DLL・実機での統合確認は、(b)、#65 の再接続組込み、旧ホスト停止がすべて揃ってから行います。 |
|
(a) のマージをありがとうございました。(b) の実装前に、旧所有者の BG 変換が実行中の場合の応答型と TSF 側の遷移を示します。main 1. 3 つの状態をどこで分けるか所有権は
所有者のいない 2. 応答型/// 所有者が一致した要求元から見た BG 変換の状態。
pub enum BgView {
Idle,
/// 自分が開始した変換が実行中
Running,
Done,
/// 旧 "error"
Failed,
/// 自分は開始していないが、ワーカーが旧所有者の変換を実行中(変換器が戻っていない)
WorkerBusy,
}
pub enum BgStartOutcome {
Started,
/// 同じ読みの自分の変換が Running / Done(旧 false のうち再起動不要のもの)
AlreadyCurrent,
WorkerBusy,
/// 変換器が無い(モデル未ロード)・読みが空
NotReady,
}
クライアントは 3. TSF 側の遷移
Space で
タイマーが送るのは
4. テスト
変換ワーカーの Running は実モデルが無いと作れないので、ホスト側のテストは この形でよければ (b) の実装に入ります。 |
|
応答型と TSF 遷移の案をありがとうございます。main ただし、現在のコードと照らすと次の点が未定義または食い違っているので、実装に入る前に形を補ってください。 1.
|
|
ご確認ありがとうございます。7 点とも、main 1・2. 判定の材料を「変換器の位置」に替える
/// 変換キャッシュの中の変換器の位置。`same_reading` はキーがエンジンの現在の読みと一致するか。
pub enum BgSlot {
/// pending にも state にも無い(`holds_converter() == false`)
Empty,
/// pending に積まれ、ワーカーがまだ拾っていない(pending があればこれを優先)
Queued { same_reading: bool },
Running { same_reading: bool },
Done { same_reading: bool, failed: bool },
}ロックは ホストは要求の入口(エンジンロック内)で、
3.
|
| ライブ preview | 確定時の追いつき | |
|---|---|---|
Ok(None) |
現行どおり辞書由来の preview | 現行どおり |
Err(OwnerMismatch / GenMismatch) |
タイマーを止め、表示と復元元を保持(9/22) | 確定は止めない(9/22 の Commit の扱い)。BgStart は送らない |
Err(通信失敗) |
preview を書き換えず、その tick を終える | 待たずに現在の preview で確定し、「未収束」として学習を見送る |
BgTakeCandidates:ChangeOutcome::Candidates(Vec)をやめ、次の値にします
pub enum BgTakeOutcome {
/// 要求元の結果を取り出した(候補が空のこともある)
Taken(Vec<String>),
/// 要求元の変換だが、まだ完了していない・キーが違う
NotReady,
/// BG の結果が要求元のものではない(`bg_owner` が自分でない)。状態は進めない
NotYours,
}- 取得失敗を
"idle"とみなしている箇所(candidate_window.rs:1496-1499など)も対象に含めます。エンジンを取得できなかった tick は「状態不明」として何もせず(bg_startもフォールバックもしない)、抜けるのは 6 の上限だけにします。(b) で同種の箇所を洗い出して揃えます
6. TSF 側の上限
WorkerBusy の上限は #59 の監視に頼らず、すべて TSF 側に置きます。
| 経路 | 上限 | 達したとき |
|---|---|---|
Space の WorkerBusy 待ち |
MODEL_WAIT_LIMIT_MS(10 秒) |
待機を打ち切り、辞書候補のまま続ける |
| 待機タイマー(Waiting) | 既存の WAITING_LIMIT_MS(10 秒)の時計をそのまま使う |
既存の経路で辞書候補の Selecting へ |
| ライブ変換タイマー | 新設の上限(10 秒)。時計は「WorkerBusy を続けて見始めた時刻」で、他の値を見たら消す |
ライブタイマーを止め(モデル未準備のときと同じ止め方)、preview は辞書由来のまま続ける |
ライブ変換は、上限の後に次の打鍵でタイマーが張り直されても時計を引き継ぎ、WorkerBusy が続く間は 1 回読んで止めます。Space は独立に効きます。
7. その他
- 旧 variant の
BgTakeCandidates: BG 系(BgStart/BgTakeCandidates/BgReclaim)は、TSF の送信の移行と_Reserved化を同じコミットで行い、所有者を見ない旧経路が残る中間状態を作りません - ライブ変換の
WorkerBusy時の preview: 既存のimmediate dict preview while bg=running(has_immediate_live_preview_candidate)の経路をそのまま使います。preview の作り方は足しません - テスト:
pendingの状態でのWorkerBusy、入口の回収が空振りした場合のWorkerBusy、同じ所有者がRestoreし直した後は自分の実行中の変換もWorkerBusyになること(意図した挙動として)を加えます。判定は(bg_owner, BgSlot)の純粋関数に分け、実モデルなしで書きます
この形でよければ (b) の実装に入ります。今回もコードを読んでの整理で、実機では確かめていません。
|
10/2 の補足を確認しました。main 合意できる点
実装前に補ってほしい点A. ABI の版を 2 か所とも上げる
B. 読み取り要求の入口の回収が、エンジンの状態と結果を変えること入口の
C. 自分の
|
|
A〜E と確認事項 2 点への回答です。(b) の PR 本文の受入条件には、下記をそのまま載せます。 A. ABI の版
B. 読み取り要求の入口の回収
C. 自分の
|
|
A〜E と確認事項 2 点への回答を確認しました。main A(ABI を 10 → 11、2 か所を同じコミットで更新)、B(照合後にだけ回収、
(b) の着手を了承します。C について、実装の中で次の 3 点を決め、PR 本文に書いてください。 C について、PR 本文に書いてほしいこと
レビューで見る点「空振りの状態そのものが起きません」で防げるのは、競合を理由に
今回も、コードを読んでの照合で、実機では確かめていません。 |
Refs #56
#56 の PR (a) です。最初のコミットで #66 と共通の要求・応答の形を定め、9/25・9/26 のレビューを反映して形は確定しました(
c301361/2c91931/22e5e8b)。f20187eでホスト側の照合・記録・回収・復元と、クライアントの採番・未解決要求・Shutdown の宛先を入れ、(a) はこの範囲で完成です(9/28 のコメントで範囲を確定)。(b) は (a) のマージ後に main を土台とした別の PR として出します。main
4399279(0.11.9)に載せています(その後の main は docs だけの変更です)。形(確定)
HostId(u128)/TsfId(u128)/EngineGen { host_id, generation: u64 }/Owner { tsf_id, composition: u64 }RequestSeq = u64、Unresolved { seq, kind: ChangeKind, owner, engine_gen }Request::Change { seq, expect: Expect { engine_gen, owner }, request: ChangeRequest, config_version }→Response::Changed { outcome: ChangeOutcome }ChangeRequest::kind()(ChangeRequestからChangeKindへの対応はこの 1 関数だけ)ChangeOutcome = Unit | Bool | Candidates(Vec<String>) | InputChar { preedit, hiragana, bg_status }Request::Restore { owner, seq, reading, pending_romaji, then: Option<ChangeRequest>, config_version }→Response::Restored { engine_gen, then: Option<ChangeOutcome> }Response::Rejected(Reason)、Reason = GenMismatch | OwnerMismatch | ResultUnavailableRequest::Shutdown { expected_host_id }→ShutdownAccepted/ShutdownSkippedShutdownIfConfigDiffers { config_json, expected_host_id, config_version }。host_idが違えばShutdownSkippedCreate { config_json, config_version }、config_version: Option<[u8; 32]>(#66 まで常にNone)Hello { protocol_version, tsf_id, highest_sent }→Hello { protocol_version, host_id, engine_gen: Option<EngineGen>, record_found }RequestRecordはserver.rsの内部型PROTOCOL_VERSION5 → 6挙動(
f20187e)ホスト
host_idを乱数で採ります。エンジンを作るたびに世代(host_id, 作成回数)を振り、新しいエンジンは所有者なしで始まりますtsf_idごとに記録を 1 件持ちます。Helloで登録して、記録が無ければfloor = highest_sentで作ります。回収の対象はsessions == 0 && in_flight == 0の記録だけで、256 件を超えたら古い順に捨てます。回収できる記録が無いときは上限を超えて受け付け、WARN を 1 回出しますChangeは 1 回のエンジンロックの中で「世代 → 番号 → 所有者」の順に判定します(ロックの順序はstate→ 記録表)。同じ番号の再送には保持している応答を返しますRestoreは同じロックの中で、reset_preedit→force_preedit→push_char× k → Done の変換器の回収 → 所有権の移動 →thenの適用を行いますShutdown系はhost_idを照合します(エンジンのロックは取りません)。handle_sessionが終了するかは応答で決めますクライアント
tsf_id・採番・Unresolvedは、プロセスで 1 つの台帳に置きますchange()/restore()/abandon_unresolved()を追加しました。再送は同じ番号で行い、通信が最終的に失敗した要求は結果不明として残して、次の変更要求を止めますshutdown()は送る前に宛先のhost_idを確定し、再接続した後の再送でも差し替えません。ShutdownOutcome::Skippedを追加し、shutdown_if_config_differs()はConditionalShutdown { SameConfig, Restarting, Skipped }を返しますTSF: Skipped は
ApplyOutcome::HostReplacedにしました。反映待ちは解除せず、無条件のShutdownにも切り替えません。#65 の再接続の組込みは fukuyori さん側で行います。まだ入れていないもの
Changeへ移すことと、旧 variant の_Reserved化: 9/28 の合意(案 1)で (b) へ移しました。(a) では TSF が旧 variant を送り、ホストはそれを照合せずに処理します(挙動は従来どおり)。利用者向けの ホストが再起動した後、未確定文字が残っていると Space も Backspace も効かず、Esc で消すしかなくなる #56 の修正は (a)+(b) で完成し、リリースは両方が揃ってから 1 回ですRestore { ..., then }で送り、所有権を得る前にChangeを送らない/そのRestoreが返す世代を以後のChange.expect.engine_genに使う/旧所有者の BG 変換が実行中の場合の応答型と TSF の遷移は、(b) の実装前に示すReloadの variant と処理は触っていません。読み取り RPC は (b) で扱います確認したこと
cargo fmt --all -- --checkcargo test -p rakukan-engine-rpc(57 件)/cargo test -p rakukan-tsf(193 件)cargo clippy -p rakukan-engine-rpc -p rakukan-tsf --all-targets(警告なし)cargo check -p rakukan-engine-hostapply_change/Restoreの中身)は単体テストで通していません🤖 Generated with Claude Code