Gist · 3
/ URL: https://kallithea.mmp.dnsalias.org/_admin/gists/3
added file: filename1.markdown
filename1.markdown
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | はい、このコードには他にもいくつか潜在的な問題があります:
## 1. **テスト間の干渉(最も深刻)**
```rust
static LOCK_GCTEST: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
```
このロックでテスト間の並行実行を防いでいますが、**GCワーカースレッドはシングルトンで常駐**しています:
```rust
fn gc_instance() -> &'static GC<Self> {
static INSTANCE: OnceLock<GC<RFtlObj>> = 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**(メモリ順序)が最も危険です。
|