cdpt 0.1.0

An automatic, safe, and concurrent garbage collector for Rust
Documentation
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
//! Common utilities for concurrent map tests.
//!
//! This module provides shared traits, types, and test functions for testing
//! concurrent map data structures.

// Each integration test file includes this module via `mod map_common;`, compiling
// its own copy. Not every test file uses every item, so suppress per-crate warnings.
#![allow(dead_code)]

use cdpt::Handle;
use fastrand::shuffle;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread::{available_parallelism, scope};

/// A trait for value references returned by concurrent maps.
pub trait ValueRef<V> {
    fn borrow(&self) -> &V;
}

/// Trait for concurrent map data structures.
pub trait ConcurrentMap<K, V>: Send + Sync
where
    K: Send + Sync,
    V: Send + Sync,
{
    /// The type of value reference returned by get/remove operations.
    type ValueRef<'h>: ValueRef<V>
    where
        Self: 'h;

    /// Creates a new empty map.
    fn new() -> Self;

    /// Returns a reference to the value associated with the key.
    fn get<'h>(&self, key: &K, handle: &'h Handle) -> Option<Self::ValueRef<'h>>;

    /// Inserts a key-value pair into the map.
    /// Returns `true` if the insertion was successful (key was not present),
    /// or `false` if the key already existed.
    fn insert(&self, key: K, value: V, handle: &Handle) -> bool;

    /// Removes a key from the map and returns the associated value if present.
    fn remove<'h>(&self, key: &K, handle: &'h Handle) -> Option<Self::ValueRef<'h>>;
}

/// Configuration for smoke tests.
pub const SMOKE_THREADS: i32 = 30;
pub const SMOKE_ELEMENTS_PER_THREAD: i32 = 1000;

/// Basic smoke test for concurrent maps.
///
/// This test:
/// 1. Inserts elements from multiple threads (each thread inserts its own disjoint set)
/// 2. Removes elements from half the threads
/// 3. Verifies remaining elements can be read
pub fn smoke<M>()
where
    M: ConcurrentMap<i32, String>,
    for<'h> M::ValueRef<'h>: ValueRef<String>,
{
    use cdpt::handle;

    let map = &M::new();

    // Phase 1: Concurrent insertions
    scope(|s| {
        for t in 0..SMOKE_THREADS {
            s.spawn(move || {
                let handle = handle();
                let mut keys: Vec<i32> = (0..SMOKE_ELEMENTS_PER_THREAD)
                    .map(|k| k * SMOKE_THREADS + t)
                    .collect();
                shuffle(&mut keys);
                for i in keys {
                    assert!(map.insert(i, i.to_string(), &handle));
                }
            });
        }
    });

    // Phase 2: Concurrent removals (first half of threads' keys)
    scope(|s| {
        for t in 0..(SMOKE_THREADS / 2) {
            s.spawn(move || {
                let handle = handle();
                let mut keys: Vec<i32> = (0..SMOKE_ELEMENTS_PER_THREAD)
                    .map(|k| k * SMOKE_THREADS + t)
                    .collect();
                shuffle(&mut keys);
                for i in keys {
                    assert_eq!(
                        Some(&i.to_string()),
                        map.remove(&i, &handle).as_ref().map(|v| v.borrow())
                    );
                }
            });
        }
    });

    // Phase 3: Verify remaining elements (second half of threads' keys)
    scope(|s| {
        for t in (SMOKE_THREADS / 2)..SMOKE_THREADS {
            s.spawn(move || {
                let handle = handle();
                let mut keys: Vec<i32> = (0..SMOKE_ELEMENTS_PER_THREAD)
                    .map(|k| k * SMOKE_THREADS + t)
                    .collect();
                shuffle(&mut keys);
                for i in keys {
                    assert_eq!(
                        Some(&i.to_string()),
                        map.get(&i, &handle).as_ref().map(|v| v.borrow())
                    );
                }
            });
        }
    });
}

/// Tests basic single-threaded operations.
pub fn test_basic_operations<M>()
where
    M: ConcurrentMap<i32, String>,
    for<'h> M::ValueRef<'h>: ValueRef<String>,
{
    use cdpt::handle;

    let handle = handle();
    let map = M::new();

    // Test empty map
    assert!(map.get(&1, &handle).is_none());
    assert!(map.remove(&1, &handle).is_none());

    // Test insert and get
    assert!(map.insert(1, "one".to_string(), &handle));
    assert_eq!(
        Some(&"one".to_string()),
        map.get(&1, &handle).as_ref().map(|v| v.borrow())
    );

    // Test duplicate insert
    assert!(!map.insert(1, "one again".to_string(), &handle));
    assert_eq!(
        Some(&"one".to_string()),
        map.get(&1, &handle).as_ref().map(|v| v.borrow())
    );

    // Test remove
    assert_eq!(
        Some(&"one".to_string()),
        map.remove(&1, &handle).as_ref().map(|v| v.borrow())
    );
    assert!(map.get(&1, &handle).is_none());

    // Test double remove
    assert!(map.remove(&1, &handle).is_none());
}

/// Tests insert and remove with multiple elements.
pub fn test_multiple_elements<M>()
where
    M: ConcurrentMap<i32, String>,
    for<'h> M::ValueRef<'h>: ValueRef<String>,
{
    use cdpt::handle;

    let handle = handle();
    let map = M::new();

    // Insert elements in order
    for i in 0..100 {
        assert!(map.insert(i, i.to_string(), &handle));
    }

    // Verify all elements
    for i in 0..100 {
        assert_eq!(
            Some(&i.to_string()),
            map.get(&i, &handle).as_ref().map(|v| v.borrow())
        );
    }

    // Remove odd elements
    for i in (1..100).step_by(2) {
        assert_eq!(
            Some(&i.to_string()),
            map.remove(&i, &handle).as_ref().map(|v| v.borrow())
        );
    }

    // Verify odd elements are gone, even elements remain
    for i in 0..100 {
        if i % 2 == 0 {
            assert_eq!(
                Some(&i.to_string()),
                map.get(&i, &handle).as_ref().map(|v| v.borrow())
            );
        } else {
            assert!(map.get(&i, &handle).is_none());
        }
    }
}

/// Tests inserting elements in reverse order (stress test for tree balancing).
pub fn test_reverse_order_insert<M>()
where
    M: ConcurrentMap<i32, String>,
    for<'h> M::ValueRef<'h>: ValueRef<String>,
{
    use cdpt::handle;

    let handle = handle();
    let map = M::new();

    // Insert elements in reverse order
    for i in (0..100).rev() {
        assert!(map.insert(i, i.to_string(), &handle));
    }

    // Verify all elements
    for i in 0..100 {
        assert_eq!(
            Some(&i.to_string()),
            map.get(&i, &handle).as_ref().map(|v| v.borrow())
        );
    }
}

/// Tests concurrent insert and remove on overlapping keys.
pub fn test_concurrent_insert_remove<M>()
where
    M: ConcurrentMap<i32, String>,
    for<'h> M::ValueRef<'h>: ValueRef<String>,
{
    use cdpt::handle;

    const THREADS: usize = 8;
    const OPS_PER_THREAD: usize = 1000;

    let map = &M::new();
    let successful_inserts = &AtomicUsize::new(0);
    let successful_removes = &AtomicUsize::new(0);

    scope(|s| {
        // Half threads insert
        for _ in 0..(THREADS / 2) {
            s.spawn(|| {
                let handle = handle();
                for i in 0..OPS_PER_THREAD as i32 {
                    if map.insert(i, i.to_string(), &handle) {
                        successful_inserts.fetch_add(1, Ordering::Relaxed);
                    }
                }
            });
        }

        // Half threads remove
        for _ in 0..(THREADS / 2) {
            s.spawn(|| {
                let handle = handle();
                for i in 0..OPS_PER_THREAD as i32 {
                    if map.remove(&i, &handle).is_some() {
                        successful_removes.fetch_add(1, Ordering::Relaxed);
                    }
                }
            });
        }
    });

    // The total successful inserts minus successful removes should equal
    // the number of elements remaining in the map
    let remaining_inserts = successful_inserts.load(Ordering::Relaxed);
    let total_removes = successful_removes.load(Ordering::Relaxed);

    // Count remaining elements
    let handle = handle();
    let mut remaining = 0;
    for i in 0..OPS_PER_THREAD as i32 {
        if map.get(&i, &handle).is_some() {
            remaining += 1;
        }
    }

    assert_eq!(remaining_inserts - total_removes, remaining);
}

/// Stress test configuration.
pub struct StressConfig {
    /// Number of threads to use.
    pub threads: usize,
    /// Number of operations per thread.
    pub ops_per_thread: usize,
    /// Range of keys to use (0..key_range).
    pub key_range: i32,
}

impl Default for StressConfig {
    fn default() -> Self {
        Self {
            threads: 16,
            ops_per_thread: 10000,
            key_range: 1000,
        }
    }
}

impl StressConfig {
    /// Configuration for list-based data structures (smaller key range for better performance).
    pub fn for_list() -> Self {
        Self {
            threads: available_parallelism().map(|t| t.get()).unwrap_or(16),
            ops_per_thread: 10 * 1000 * 1000, // 10M
            key_range: 50,                    // Smaller range for O(n) traversal
        }
    }

    /// Configuration for tree-based data structures.
    pub fn for_tree() -> Self {
        Self {
            threads: available_parallelism().map(|t| t.get()).unwrap_or(16),
            ops_per_thread: 10 * 1000 * 1000, // 10M
            key_range: 1000,
        }
    }
}

/// Stress test that performs random operations from multiple threads.
/// This test is designed to be run with address sanitizer to detect memory issues.
///
/// **Recommended:** Run stress tests with release profile for reasonable performance:
/// ```sh
/// cargo test --release --all-targets -- --ignored
/// ```
///
/// To run with address sanitizer (requires nightly):
/// ```sh
/// RUSTFLAGS="-Z sanitizer=address" cargo +nightly test --release --all-targets -- --ignored
/// ```
/// (Set `--target` for your machine: https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html)
pub fn stress_test_with_config<M>(config: StressConfig)
where
    M: ConcurrentMap<i32, String>,
    for<'h> M::ValueRef<'h>: ValueRef<String>,
{
    use cdpt::handle;

    let map = &M::new();

    scope(|s| {
        for thread_id in 0..config.threads {
            s.spawn(move || {
                let handle = handle();
                let mut rng = fastrand::Rng::with_seed(thread_id as u64);

                for _ in 0..config.ops_per_thread {
                    let key = rng.i32(0..config.key_range);
                    let op = rng.u8(0..3);

                    match op {
                        0 => {
                            // Insert
                            let _ = map.insert(key, key.to_string(), &handle);
                        }
                        1 => {
                            // Get
                            if let Some(v) = map.get(&key, &handle) {
                                // Verify the value matches the key
                                assert_eq!(v.borrow(), &key.to_string());
                            }
                        }
                        2 => {
                            // Remove
                            if let Some(v) = map.remove(&key, &handle) {
                                // Verify the removed value matches the key
                                assert_eq!(v.borrow(), &key.to_string());
                            }
                        }
                        _ => unreachable!(),
                    }
                }
            });
        }
    });
}

/// Stress test with default configuration for trees.
pub fn stress_test<M>()
where
    M: ConcurrentMap<i32, String>,
    for<'h> M::ValueRef<'h>: ValueRef<String>,
{
    stress_test_with_config::<M>(StressConfig::for_tree());
}

/// Stress test with configuration for lists.
pub fn stress_test_list<M>()
where
    M: ConcurrentMap<i32, String>,
    for<'h> M::ValueRef<'h>: ValueRef<String>,
{
    stress_test_with_config::<M>(StressConfig::for_list());
}