はい、このコードには他にもいくつか潜在的な問題があります: ## 1. **テスト間の干渉(最も深刻)** ```rust static LOCK_GCTEST: LazyLock> = LazyLock::new(|| Mutex::new(())); ``` このロックでテスト間の並行実行を防いでいますが、**GCワーカースレッドはシングルトンで常駐**しています: ```rust fn gc_instance() -> &'static GC { static INSTANCE: OnceLock> = OnceLock::new(); INSTANCE.get_or_init(|| { let gc = GC::new(GC_OBJECT_NUM); std::thread::spawn(move || { // ← 常駐スレッド // ... }); gc }) } ``` **問題点:** - テストAが終了してもGCワーカーは動き続ける - テストBが開始するとき、前のテストのオブジェクトがまだ`object_list`に残っている可能性 - `gc_verbose(true)`の設定が他のテストに影響 **推奨対策:** ```rust #[cfg(test)] impl RFtlObj { pub fn gc_reset_for_test() { let gc = Self::gc_instance(); gc.done_wait(); // GC完了を待つ gc.state.write().object_list.clear(); gc.pool_size.store(GC_OBJECT_NUM, Relaxed); gc.set_verbose(false); } } #[test] fn test_something() { let _lock = LOCK_GCTEST.lock(); RFtlObj::gc_reset_for_test(); // ← 各テストの最初に追加 // ... } ``` ## 2. **`done_wait()`のタイムアウトなしループ** ```rust fn done_wait(&self) { let mut requested = self.requested.lock(); if *requested { self.cond.wait_while(&mut requested, |requested| *requested); } } ``` GCワーカーが何らかの理由でハングすると、テストが永遠に待機します。 **推奨対策:** ```rust fn done_wait_timeout(&self, timeout: Duration) -> bool { let mut requested = self.requested.lock(); if *requested { self.cond.wait_while_for(&mut requested, |requested| *requested, timeout).timed_out() } else { false } } ``` ## 3. **`start_wait_for()`の不完全な通知** ```rust // notifyが必ず効くとは限らないのでタイムアウトつきで待機してループ fn start_wait_for(&self, timeout: Duration) { if let Some(mut requested) = self.requested.try_lock() { while !*requested { self.cond.wait_while_for(&mut requested, |requested| !*requested, timeout); } } } ``` `try_lock()`が失敗すると**何もせずリターン**します。GC開始を見逃す可能性があります。 ## 4. **テスト中の競合状態** ```rust // scan完了を待つ(marking状態になるまで) for _ in 0..100 { if gc_instance.state.read().marking != super::GcPhase::Idle { break; } thread::sleep(Duration::from_millis(10)); } ``` - リリースビルドでは最適化により、GCフェーズ遷移が極端に速くなる - ループを抜けた直後に次のフェーズに進む可能性 - **総待機時間1秒 (10ms × 100回) でも足りない場合がある** **推奨対策:** ```rust fn wait_for_phase(&self, expected: GcPhase, timeout: Duration) -> bool { let start = Instant::now(); while start.elapsed() < timeout { if self.state.read().marking == expected { return true; } thread::sleep(Duration::from_millis(1)); } false } ``` ## 5. **`register()`と`write_barrier()`の順序依存性** コメントに記載の通り: ```rust // 1. オブジェクト(の強参照)を取り出す // 2. 取り出したオブジェクトにwrite_barrierをかける // 3. 新たなオブジェクト(の弱参照)を設定する ``` この順序を守らないとマーク漏れが発生しますが、**コンパイラによる順序保証がない**です。 **推奨対策:** ```rust use std::sync::atomic::compiler_fence; pub fn safe_replace_weak_ref(&self, new_obj: &RFtlObj) { let old = self.get_weak_ref().upgrade(); if let Some(old_obj) = old { old_obj.gc_write_barrier(); compiler_fence(Acquire); // ← メモリ順序を保証 } self.set_weak_ref(new_obj.downgrade()); } ``` ## 6. **パフォーマンステストの信頼性** ```rust #[test] fn test_upgrade() { const N: usize = 1000000; // ... let start = Instant::now(); for _ in 0..N { vec.push(weak.upgrade().unwrap()); } // ... } ``` - CPUスロットリング、バックグラウンドプロセスの影響を受ける - リリースビルドとデバッグビルドで結果が大きく異なる - CI環境では不安定 **推奨対策:** - `#[ignore]` をつけてデフォルトでは実行しない - 複数回実行して中央値を取る - または削除してベンチマーク専用ツール(criterion)を使う --- ## まとめ **最も危険な順:** 1. ✅ テスト間の状態共有(GCシングルトン) 2. ✅ ロック保持中の`upgrade()`によるデッドロック(質問の件) 3. ⚠️ タイムアウトなしの無限待機 4. ⚠️ GCフェーズ遷移の競合状態 5. ⚠️ メモリ順序の未保証 テストコードでは特に**1と2**に注意が必要です。本番コードでは**5**(メモリ順序)が最も危険です。