Skip to main content

xlog_cuda_tests/categories/
c08_synchronization.rs

1//! Category 8: Synchronization and Memory Ordering
2//!
3//! Tests synchronization primitives and memory ordering including atomics,
4//! block synchronization barriers, and concurrent operations.
5
6use crate::harness::{CategoryResult, TestContext, TestResult};
7use std::time::Instant;
8use xlog_core::{ScalarType, Schema};
9
10/// Run all tests in this category.
11pub fn run_all(ctx: &TestContext) -> CategoryResult {
12    let mut results = CategoryResult::new("c08_synchronization");
13    let start = Instant::now();
14
15    results.add_result(test_hash_join_atomics(ctx));
16    results.add_result(test_filter_scan_sync(ctx));
17    results.add_result(test_sort_barrier_correctness(ctx));
18    results.add_result(test_dedup_atomic_marking(ctx));
19    results.add_result(test_concurrent_operations(ctx));
20
21    results.set_duration(start.elapsed());
22    results
23}
24
25/// Test 1: Hash join uses atomics for hash table - verify correctness.
26///
27/// Hash join operations use atomic operations for building the hash table.
28/// This test verifies that concurrent atomic operations produce correct results.
29fn test_hash_join_atomics(ctx: &TestContext) -> TestResult {
30    let start = Instant::now();
31
32    let left_schema = Schema::new(vec![
33        ("key".to_string(), ScalarType::U32),
34        ("lval".to_string(), ScalarType::U32),
35    ]);
36    let right_schema = Schema::new(vec![
37        ("key".to_string(), ScalarType::U32),
38        ("rval".to_string(), ScalarType::U32),
39    ]);
40
41    // Create left table with sequential keys
42    const LEFT_SIZE: usize = 10000;
43    let left_keys: Vec<u32> = (0..LEFT_SIZE as u32).collect();
44    let left_vals: Vec<u32> = (0..LEFT_SIZE as u32).map(|i| i * 10).collect();
45
46    // Create right table with overlapping keys (every 2nd key)
47    const RIGHT_SIZE: usize = 5000;
48    let right_keys: Vec<u32> = (0..RIGHT_SIZE as u32).map(|i| i * 2).collect();
49    let right_vals: Vec<u32> = (0..RIGHT_SIZE as u32).map(|i| i * 100).collect();
50
51    let left_buffer = match ctx
52        .provider
53        .create_buffer_from_u32_columns(&[&left_keys, &left_vals], left_schema.clone())
54    {
55        Ok(buf) => buf,
56        Err(e) => {
57            return TestResult::error(
58                "test_hash_join_atomics",
59                start.elapsed(),
60                format!("Failed to create left buffer: {}", e),
61            )
62        }
63    };
64
65    let right_buffer = match ctx
66        .provider
67        .create_buffer_from_u32_columns(&[&right_keys, &right_vals], right_schema.clone())
68    {
69        Ok(buf) => buf,
70        Err(e) => {
71            return TestResult::error(
72                "test_hash_join_atomics",
73                start.elapsed(),
74                format!("Failed to create right buffer: {}", e),
75            )
76        }
77    };
78
79    // Perform hash join
80    let joined = match ctx
81        .provider
82        .hash_join(&left_buffer, &right_buffer, &[0], &[0])
83    {
84        Ok(j) => j,
85        Err(e) => {
86            return TestResult::error(
87                "test_hash_join_atomics",
88                start.elapsed(),
89                format!("Hash join failed: {}", e),
90            )
91        }
92    };
93
94    // Expected: matches for keys 0, 2, 4, ..., 9998 = 5000 matches
95    let expected_matches = RIGHT_SIZE;
96    if ctx.device_row_count(&joined) != expected_matches as u64 {
97        return TestResult::error(
98            "test_hash_join_atomics",
99            start.elapsed(),
100            format!(
101                "Join returned {} rows, expected {}",
102                ctx.device_row_count(&joined),
103                expected_matches
104            ),
105        );
106    }
107
108    // Download and verify join results
109    let joined_keys = match ctx.provider.download_column::<u32>(&joined, 0) {
110        Ok(d) => d,
111        Err(e) => {
112            return TestResult::error(
113                "test_hash_join_atomics",
114                start.elapsed(),
115                format!("Failed to download joined keys: {}", e),
116            )
117        }
118    };
119
120    let joined_lvals = match ctx.provider.download_column::<u32>(&joined, 1) {
121        Ok(d) => d,
122        Err(e) => {
123            return TestResult::error(
124                "test_hash_join_atomics",
125                start.elapsed(),
126                format!("Failed to download joined lvals: {}", e),
127            )
128        }
129    };
130
131    let joined_rvals = match ctx.provider.download_column::<u32>(&joined, 2) {
132        Ok(d) => d,
133        Err(e) => {
134            return TestResult::error(
135                "test_hash_join_atomics",
136                start.elapsed(),
137                format!("Failed to download joined rvals: {}", e),
138            )
139        }
140    };
141
142    // Verify that all joined rows have matching keys and correct values
143    for i in 0..ctx.device_row_count(&joined) as usize {
144        let key = joined_keys[i];
145        let lval = joined_lvals[i];
146        let rval = joined_rvals[i];
147
148        // Key should be even (matching pattern)
149        if key % 2 != 0 {
150            return TestResult::error(
151                "test_hash_join_atomics",
152                start.elapsed(),
153                format!("Row {}: key {} should be even", i, key),
154            );
155        }
156
157        // lval should be key * 10
158        let expected_lval = key * 10;
159        if lval != expected_lval {
160            return TestResult::error(
161                "test_hash_join_atomics",
162                start.elapsed(),
163                format!(
164                    "Row {}: lval {} doesn't match expected {} for key {}",
165                    i, lval, expected_lval, key
166                ),
167            );
168        }
169
170        // rval should be (key/2) * 100
171        let expected_rval = (key / 2) * 100;
172        if rval != expected_rval {
173            return TestResult::error(
174                "test_hash_join_atomics",
175                start.elapsed(),
176                format!(
177                    "Row {}: rval {} doesn't match expected {} for key {}",
178                    i, rval, expected_rval, key
179                ),
180            );
181        }
182    }
183
184    if let Err(e) = ctx.sync_and_check() {
185        return TestResult::error(
186            "test_hash_join_atomics",
187            start.elapsed(),
188            format!("Sync failed: {}", e),
189        );
190    }
191
192    TestResult::passed("test_hash_join_atomics", start.elapsed())
193}
194
195/// Test 2: Filter uses scan which requires block synchronization.
196///
197/// Filter operations use prefix scan to compute output positions. Prefix scan
198/// requires __syncthreads() for correctness within blocks.
199fn test_filter_scan_sync(ctx: &TestContext) -> TestResult {
200    let start = Instant::now();
201    let schema = Schema::new(vec![("val".to_string(), ScalarType::U32)]);
202
203    // Test various sizes that exercise different scan patterns
204    type SizePredicate = (usize, Box<dyn Fn(usize) -> bool>);
205    let test_cases: Vec<SizePredicate> = vec![
206        // (size, predicate)
207        (1000, Box::new(|i| i % 2 == 0)),          // Keep even
208        (10000, Box::new(|i| i % 3 == 0)),         // Keep multiples of 3
209        (50000, Box::new(|i| i < 25000)),          // Keep first half
210        (100000, Box::new(|i| i % 7 == 0)),        // Keep multiples of 7
211        (65536, Box::new(|i| (i / 256) % 2 == 0)), // Keep alternating chunks
212    ];
213
214    for (size, predicate) in test_cases {
215        // Create sequential data
216        let data: Vec<u32> = (0..size as u32).collect();
217
218        let buffer = match ctx
219            .provider
220            .create_buffer_from_slice::<u32>(&data, schema.clone())
221        {
222            Ok(buf) => buf,
223            Err(e) => {
224                return TestResult::error(
225                    "test_filter_scan_sync",
226                    start.elapsed(),
227                    format!("Size {}: failed to create buffer: {}", size, e),
228                )
229            }
230        };
231
232        // Create mask based on predicate
233        let mask: Vec<u8> = (0..size)
234            .map(|i| if predicate(i) { 1 } else { 0 })
235            .collect();
236        let expected_count: usize = mask.iter().map(|&m| m as usize).sum();
237
238        let filtered = match ctx.provider.filter_by_mask(&buffer, &mask) {
239            Ok(f) => f,
240            Err(e) => {
241                return TestResult::error(
242                    "test_filter_scan_sync",
243                    start.elapsed(),
244                    format!("Size {}: filter failed: {}", size, e),
245                )
246            }
247        };
248
249        // Verify count
250        if ctx.device_row_count(&filtered) != expected_count as u64 {
251            return TestResult::error(
252                "test_filter_scan_sync",
253                start.elapsed(),
254                format!(
255                    "Size {}: filter returned {} rows, expected {}",
256                    size,
257                    ctx.device_row_count(&filtered),
258                    expected_count
259                ),
260            );
261        }
262
263        // Download and verify values
264        let filtered_data = match ctx.provider.download_column::<u32>(&filtered, 0) {
265            Ok(d) => d,
266            Err(e) => {
267                return TestResult::error(
268                    "test_filter_scan_sync",
269                    start.elapsed(),
270                    format!("Size {}: failed to download: {}", size, e),
271                )
272            }
273        };
274
275        // Verify each filtered value matches predicate
276        let mut expected_idx = 0;
277        for i in 0..size {
278            if predicate(i) {
279                if expected_idx >= filtered_data.len() {
280                    return TestResult::error(
281                        "test_filter_scan_sync",
282                        start.elapsed(),
283                        format!(
284                            "Size {}: filtered data too short at expected index {}",
285                            size, expected_idx
286                        ),
287                    );
288                }
289                if filtered_data[expected_idx] != i as u32 {
290                    return TestResult::error(
291                        "test_filter_scan_sync",
292                        start.elapsed(),
293                        format!(
294                            "Size {}: filtered[{}] = {}, expected {}",
295                            size, expected_idx, filtered_data[expected_idx], i
296                        ),
297                    );
298                }
299                expected_idx += 1;
300            }
301        }
302    }
303
304    if let Err(e) = ctx.sync_and_check() {
305        return TestResult::error(
306            "test_filter_scan_sync",
307            start.elapsed(),
308            format!("Sync failed: {}", e),
309        );
310    }
311
312    TestResult::passed("test_filter_scan_sync", start.elapsed())
313}
314
315/// Test 3: Sort uses __syncthreads() - verify correct ordering.
316///
317/// Sort algorithms like radix sort use block synchronization barriers
318/// (__syncthreads) for correctness. This test verifies that synchronization
319/// works correctly across various data patterns.
320fn test_sort_barrier_correctness(ctx: &TestContext) -> TestResult {
321    let start = Instant::now();
322    let schema = Schema::new(vec![
323        ("key".to_string(), ScalarType::U32),
324        ("val".to_string(), ScalarType::U32),
325    ]);
326
327    // Test patterns that stress synchronization
328    let test_patterns: Vec<(&str, Vec<u32>)> = vec![
329        ("reverse", (0..10000u32).rev().collect()),
330        (
331            "alternating",
332            (0..10000u32)
333                .map(|i| if i % 2 == 0 { i } else { 10000 - i })
334                .collect(),
335        ),
336        ("sawtooth", (0..10000u32).map(|i| i % 100).collect()),
337        (
338            "random_lcg",
339            (0..10000usize)
340                .map(|i| ((i * 1103515245 + 12345) % 10000) as u32)
341                .collect(),
342        ),
343        (
344            "blocks_reversed",
345            (0..10000u32)
346                .map(|i| {
347                    let block = i / 256;
348                    let offset = i % 256;
349                    block * 256 + (255 - offset)
350                })
351                .collect(),
352        ),
353    ];
354
355    for (name, keys) in test_patterns {
356        let size = keys.len();
357        let vals: Vec<u32> = (0..size as u32).collect();
358
359        let buffer = match ctx
360            .provider
361            .create_buffer_from_u32_columns(&[&keys, &vals], schema.clone())
362        {
363            Ok(buf) => buf,
364            Err(e) => {
365                return TestResult::error(
366                    "test_sort_barrier_correctness",
367                    start.elapsed(),
368                    format!("Pattern {}: failed to create buffer: {}", name, e),
369                )
370            }
371        };
372
373        // Sort by key
374        let sorted = match ctx.provider.sort(&buffer, &[0]) {
375            Ok(s) => s,
376            Err(e) => {
377                return TestResult::error(
378                    "test_sort_barrier_correctness",
379                    start.elapsed(),
380                    format!("Pattern {}: sort failed: {}", name, e),
381                )
382            }
383        };
384
385        // Verify row count
386        if ctx.device_row_count(&sorted) != size as u64 {
387            return TestResult::error(
388                "test_sort_barrier_correctness",
389                start.elapsed(),
390                format!(
391                    "Pattern {}: sort returned {} rows, expected {}",
392                    name,
393                    ctx.device_row_count(&sorted),
394                    size
395                ),
396            );
397        }
398
399        // Download sorted keys
400        let sorted_keys = match ctx.provider.download_column::<u32>(&sorted, 0) {
401            Ok(d) => d,
402            Err(e) => {
403                return TestResult::error(
404                    "test_sort_barrier_correctness",
405                    start.elapsed(),
406                    format!("Pattern {}: failed to download sorted keys: {}", name, e),
407                )
408            }
409        };
410
411        // Verify sorted order
412        for i in 1..size {
413            if sorted_keys[i] < sorted_keys[i - 1] {
414                return TestResult::error(
415                    "test_sort_barrier_correctness",
416                    start.elapsed(),
417                    format!(
418                        "Pattern {}: sort order incorrect at {}: {} > {}",
419                        name,
420                        i,
421                        sorted_keys[i - 1],
422                        sorted_keys[i]
423                    ),
424                );
425            }
426        }
427
428        // Verify all original keys are present (same multiset)
429        let mut original_sorted = keys.clone();
430        original_sorted.sort();
431        if sorted_keys != original_sorted {
432            return TestResult::error(
433                "test_sort_barrier_correctness",
434                start.elapsed(),
435                format!("Pattern {}: keys not preserved through sort", name),
436            );
437        }
438    }
439
440    if let Err(e) = ctx.sync_and_check() {
441        return TestResult::error(
442            "test_sort_barrier_correctness",
443            start.elapsed(),
444            format!("Sync failed: {}", e),
445        );
446    }
447
448    TestResult::passed("test_sort_barrier_correctness", start.elapsed())
449}
450
451/// Test 4: Dedup uses atomic operations - verify correctness.
452///
453/// Dedup operations use atomic operations to mark duplicates. This test
454/// verifies that concurrent atomic marking produces correct deduplication.
455fn test_dedup_atomic_marking(ctx: &TestContext) -> TestResult {
456    let start = Instant::now();
457    let schema = Schema::new(vec![
458        ("key".to_string(), ScalarType::U32),
459        ("val".to_string(), ScalarType::U32),
460    ]);
461
462    // Test various duplicate patterns
463    let test_cases: Vec<(&str, Vec<u32>, Vec<u32>)> = vec![
464        // (name, keys, vals)
465        ("all_same", vec![42; 10000], (0..10000u32).collect()),
466        (
467            "pairs",
468            (0..5000u32).flat_map(|i| vec![i, i]).collect(),
469            (0..10000u32).collect(),
470        ),
471        (
472            "triples",
473            (0..3333u32)
474                .flat_map(|i| vec![i, i, i])
475                .take(9999)
476                .collect(),
477            (0..9999u32).collect(),
478        ),
479        ("no_dups", (0..10000u32).collect(), (0..10000u32).collect()),
480        (
481            "many_dups",
482            (0..10000u32).map(|i| i % 100).collect(),
483            (0..10000u32).collect(),
484        ),
485    ];
486
487    for (name, keys, vals) in test_cases {
488        let buffer = match ctx
489            .provider
490            .create_buffer_from_u32_columns(&[&keys, &vals], schema.clone())
491        {
492            Ok(buf) => buf,
493            Err(e) => {
494                return TestResult::error(
495                    "test_dedup_atomic_marking",
496                    start.elapsed(),
497                    format!("Pattern {}: failed to create buffer: {}", name, e),
498                )
499            }
500        };
501
502        // Dedup by key column
503        let deduped = match ctx.provider.dedup(&buffer, &[0]) {
504            Ok(d) => d,
505            Err(e) => {
506                return TestResult::error(
507                    "test_dedup_atomic_marking",
508                    start.elapsed(),
509                    format!("Pattern {}: dedup failed: {}", name, e),
510                )
511            }
512        };
513
514        // Calculate expected unique count
515        let mut unique_keys: std::collections::HashSet<u32> = std::collections::HashSet::new();
516        for &k in &keys {
517            unique_keys.insert(k);
518        }
519        let expected_unique = unique_keys.len();
520
521        if ctx.device_row_count(&deduped) != expected_unique as u64 {
522            return TestResult::error(
523                "test_dedup_atomic_marking",
524                start.elapsed(),
525                format!(
526                    "Pattern {}: dedup returned {} rows, expected {}",
527                    name,
528                    ctx.device_row_count(&deduped),
529                    expected_unique
530                ),
531            );
532        }
533
534        // Download and verify uniqueness
535        let deduped_keys = match ctx.provider.download_column::<u32>(&deduped, 0) {
536            Ok(d) => d,
537            Err(e) => {
538                return TestResult::error(
539                    "test_dedup_atomic_marking",
540                    start.elapsed(),
541                    format!("Pattern {}: failed to download deduped keys: {}", name, e),
542                )
543            }
544        };
545
546        // Verify all keys are unique
547        let mut seen_keys: std::collections::HashSet<u32> = std::collections::HashSet::new();
548        for &k in &deduped_keys {
549            if !seen_keys.insert(k) {
550                return TestResult::error(
551                    "test_dedup_atomic_marking",
552                    start.elapsed(),
553                    format!("Pattern {}: duplicate key {} in dedup result", name, k),
554                );
555            }
556        }
557
558        // Verify all original unique keys are present
559        if seen_keys != unique_keys {
560            return TestResult::error(
561                "test_dedup_atomic_marking",
562                start.elapsed(),
563                format!("Pattern {}: dedup result missing some unique keys", name),
564            );
565        }
566    }
567
568    if let Err(e) = ctx.sync_and_check() {
569        return TestResult::error(
570            "test_dedup_atomic_marking",
571            start.elapsed(),
572            format!("Sync failed: {}", e),
573        );
574    }
575
576    TestResult::passed("test_dedup_atomic_marking", start.elapsed())
577}
578
579/// Test 5: Run multiple independent operations, verify all correct.
580///
581/// Tests that multiple independent operations can run and complete correctly,
582/// verifying that GPU resource management and synchronization work properly.
583fn test_concurrent_operations(ctx: &TestContext) -> TestResult {
584    let start = Instant::now();
585    let schema = Schema::new(vec![("val".to_string(), ScalarType::U32)]);
586
587    // Create multiple buffers
588    const NUM_BUFFERS: usize = 5;
589    const BUFFER_SIZE: usize = 10000;
590
591    let mut buffers = Vec::with_capacity(NUM_BUFFERS);
592    let mut original_data = Vec::with_capacity(NUM_BUFFERS);
593
594    for i in 0..NUM_BUFFERS {
595        // Each buffer has different data pattern
596        let data: Vec<u32> = (0..BUFFER_SIZE)
597            .map(|j| ((j * (i + 1) * 7 + i * 13) % BUFFER_SIZE) as u32)
598            .collect();
599
600        let buffer = match ctx
601            .provider
602            .create_buffer_from_slice::<u32>(&data, schema.clone())
603        {
604            Ok(buf) => buf,
605            Err(e) => {
606                return TestResult::error(
607                    "test_concurrent_operations",
608                    start.elapsed(),
609                    format!("Failed to create buffer {}: {}", i, e),
610                )
611            }
612        };
613
614        original_data.push(data);
615        buffers.push(buffer);
616    }
617
618    // Perform different operations on each buffer
619    let mut results = Vec::with_capacity(NUM_BUFFERS);
620
621    // Buffer 0: Sort
622    let sorted0 = match ctx.provider.sort(&buffers[0], &[0]) {
623        Ok(s) => s,
624        Err(e) => {
625            return TestResult::error(
626                "test_concurrent_operations",
627                start.elapsed(),
628                format!("Sort of buffer 0 failed: {}", e),
629            )
630        }
631    };
632    results.push(("sort", sorted0));
633
634    // Buffer 1: Filter (keep first half)
635    let mask1: Vec<u8> = (0..BUFFER_SIZE)
636        .map(|i| if i < BUFFER_SIZE / 2 { 1 } else { 0 })
637        .collect();
638    let filtered1 = match ctx.provider.filter_by_mask(&buffers[1], &mask1) {
639        Ok(f) => f,
640        Err(e) => {
641            return TestResult::error(
642                "test_concurrent_operations",
643                start.elapsed(),
644                format!("Filter of buffer 1 failed: {}", e),
645            )
646        }
647    };
648    results.push(("filter", filtered1));
649
650    // Buffer 2: Sort
651    let sorted2 = match ctx.provider.sort(&buffers[2], &[0]) {
652        Ok(s) => s,
653        Err(e) => {
654            return TestResult::error(
655                "test_concurrent_operations",
656                start.elapsed(),
657                format!("Sort of buffer 2 failed: {}", e),
658            )
659        }
660    };
661    results.push(("sort", sorted2));
662
663    // Buffer 3: Filter (keep even indices)
664    let mask3: Vec<u8> = (0..BUFFER_SIZE)
665        .map(|i| if i % 2 == 0 { 1 } else { 0 })
666        .collect();
667    let filtered3 = match ctx.provider.filter_by_mask(&buffers[3], &mask3) {
668        Ok(f) => f,
669        Err(e) => {
670            return TestResult::error(
671                "test_concurrent_operations",
672                start.elapsed(),
673                format!("Filter of buffer 3 failed: {}", e),
674            )
675        }
676    };
677    results.push(("filter", filtered3));
678
679    // Buffer 4: Sort
680    let sorted4 = match ctx.provider.sort(&buffers[4], &[0]) {
681        Ok(s) => s,
682        Err(e) => {
683            return TestResult::error(
684                "test_concurrent_operations",
685                start.elapsed(),
686                format!("Sort of buffer 4 failed: {}", e),
687            )
688        }
689    };
690    results.push(("sort", sorted4));
691
692    // Verify all results
693    // Sort results (0, 2, 4)
694    for &idx in &[0usize, 2, 4] {
695        let (op, ref result) = results[idx];
696        assert!(op == "sort");
697
698        if ctx.device_row_count(result) != BUFFER_SIZE as u64 {
699            return TestResult::error(
700                "test_concurrent_operations",
701                start.elapsed(),
702                format!(
703                    "Buffer {}: {} returned {} rows, expected {}",
704                    idx,
705                    op,
706                    ctx.device_row_count(result),
707                    BUFFER_SIZE
708                ),
709            );
710        }
711
712        let sorted_data = match ctx.provider.download_column::<u32>(result, 0) {
713            Ok(d) => d,
714            Err(e) => {
715                return TestResult::error(
716                    "test_concurrent_operations",
717                    start.elapsed(),
718                    format!("Buffer {}: failed to download: {}", idx, e),
719                )
720            }
721        };
722
723        // Verify sorted order
724        for i in 1..BUFFER_SIZE {
725            if sorted_data[i] < sorted_data[i - 1] {
726                return TestResult::error(
727                    "test_concurrent_operations",
728                    start.elapsed(),
729                    format!(
730                        "Buffer {}: sort order incorrect at {}: {} > {}",
731                        idx,
732                        i,
733                        sorted_data[i - 1],
734                        sorted_data[i]
735                    ),
736                );
737            }
738        }
739
740        // Verify same values (sorted)
741        let mut expected = original_data[idx].clone();
742        expected.sort();
743        if sorted_data != expected {
744            return TestResult::error(
745                "test_concurrent_operations",
746                start.elapsed(),
747                format!("Buffer {}: sorted data doesn't match expected", idx),
748            );
749        }
750    }
751
752    // Filter results (1, 3)
753    // Buffer 1: first half
754    let (op1, ref result1) = results[1];
755    assert!(op1 == "filter");
756    if ctx.device_row_count(result1) != (BUFFER_SIZE / 2) as u64 {
757        return TestResult::error(
758            "test_concurrent_operations",
759            start.elapsed(),
760            format!(
761                "Buffer 1: filter returned {} rows, expected {}",
762                ctx.device_row_count(result1),
763                BUFFER_SIZE / 2
764            ),
765        );
766    }
767
768    // Buffer 3: even indices
769    let (op3, ref result3) = results[3];
770    assert!(op3 == "filter");
771    if ctx.device_row_count(result3) != BUFFER_SIZE.div_ceil(2) as u64 {
772        return TestResult::error(
773            "test_concurrent_operations",
774            start.elapsed(),
775            format!(
776                "Buffer 3: filter returned {} rows, expected {}",
777                ctx.device_row_count(result3),
778                BUFFER_SIZE.div_ceil(2)
779            ),
780        );
781    }
782
783    // Verify original buffers are unchanged
784    for i in 0..NUM_BUFFERS {
785        let current_data = match ctx.provider.download_column::<u32>(&buffers[i], 0) {
786            Ok(d) => d,
787            Err(e) => {
788                return TestResult::error(
789                    "test_concurrent_operations",
790                    start.elapsed(),
791                    format!("Failed to verify buffer {}: {}", i, e),
792                )
793            }
794        };
795
796        if current_data != original_data[i] {
797            return TestResult::error(
798                "test_concurrent_operations",
799                start.elapsed(),
800                format!("Buffer {} was modified by operations", i),
801            );
802        }
803    }
804
805    if let Err(e) = ctx.sync_and_check() {
806        return TestResult::error(
807            "test_concurrent_operations",
808            start.elapsed(),
809            format!("Sync failed: {}", e),
810        );
811    }
812
813    TestResult::passed("test_concurrent_operations", start.elapsed())
814}