|
1 | 1 | #![cfg(not(target_arch = "wasm32"))] |
2 | | -use std::sync::{ |
3 | | - atomic::{AtomicBool, Ordering}, |
4 | | - Arc, |
| 2 | +use std::{ |
| 3 | + panic::AssertUnwindSafe, |
| 4 | + sync::{ |
| 5 | + atomic::{AtomicBool, Ordering}, |
| 6 | + Arc, |
| 7 | + }, |
5 | 8 | }; |
6 | 9 |
|
| 10 | +fn raise_validation_error(device: &wgpu::Device) { |
| 11 | + let _buffer = device.create_buffer(&wgpu::BufferDescriptor { |
| 12 | + label: None, |
| 13 | + size: 1 << 63, // Too large! |
| 14 | + usage: wgpu::BufferUsages::COPY_SRC, |
| 15 | + mapped_at_creation: false, |
| 16 | + }); |
| 17 | +} |
| 18 | + |
| 19 | +fn register_uncaptured_error_handler(device: &wgpu::Device) -> Arc<AtomicBool> { |
| 20 | + let error_occurred = Arc::new(AtomicBool::new(false)); |
| 21 | + let error_occurred_clone = error_occurred.clone(); |
| 22 | + |
| 23 | + device.on_uncaptured_error(Arc::new(move |_error| { |
| 24 | + error_occurred_clone.store(true, Ordering::Relaxed); |
| 25 | + })); |
| 26 | + |
| 27 | + error_occurred |
| 28 | +} |
| 29 | + |
| 30 | +// Test that error scopes work correctly in the basic case. |
| 31 | +#[test] |
| 32 | +fn basic() { |
| 33 | + let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default()); |
| 34 | + |
| 35 | + let scope = device.push_error_scope(wgpu::ErrorFilter::Validation); |
| 36 | + raise_validation_error(&device); |
| 37 | + let error = pollster::block_on(scope.pop()); |
| 38 | + |
| 39 | + assert!(error.is_some()); |
| 40 | +} |
| 41 | + |
7 | 42 | // Test that error scopes are thread-local: an error scope pushed on one thread |
8 | 43 | // does not capture errors generated on another thread. |
9 | 44 | #[test] |
10 | 45 | fn multi_threaded_scopes() { |
11 | 46 | let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default()); |
12 | 47 |
|
13 | | - let other_thread_error = Arc::new(AtomicBool::new(false)); |
14 | | - let other_thread_error_clone = other_thread_error.clone(); |
15 | | - |
16 | 48 | // Start an error scope on the main thread. |
17 | | - device.push_error_scope(wgpu::ErrorFilter::Validation); |
| 49 | + let scope = device.push_error_scope(wgpu::ErrorFilter::Validation); |
18 | 50 | // Register an uncaptured error handler to catch errors from other threads. |
19 | | - device.on_uncaptured_error(Arc::new(move |_error| { |
20 | | - other_thread_error_clone.store(true, Ordering::Relaxed); |
21 | | - })); |
| 51 | + let other_thread_error = register_uncaptured_error_handler(&device); |
22 | 52 |
|
23 | 53 | // Do something invalid on another thread. |
24 | 54 | std::thread::scope(|s| { |
25 | 55 | s.spawn(|| { |
26 | | - let _buffer = device.create_buffer(&wgpu::BufferDescriptor { |
27 | | - label: None, |
28 | | - size: 1 << 63, // Too large! |
29 | | - usage: wgpu::BufferUsages::COPY_SRC, |
30 | | - mapped_at_creation: false, |
31 | | - }); |
| 56 | + raise_validation_error(&device); |
32 | 57 | }); |
33 | 58 | }); |
34 | 59 |
|
35 | 60 | // Pop the error scope on the main thread. |
36 | | - let error = pollster::block_on(device.pop_error_scope()); |
| 61 | + let error = pollster::block_on(scope.pop()); |
37 | 62 |
|
38 | 63 | // The main thread's error scope should not have captured the other thread's error. |
39 | 64 | assert!(error.is_none()); |
40 | 65 | // The other thread's error should have been reported to the uncaptured error handler. |
41 | 66 | assert!(other_thread_error.load(Ordering::Relaxed)); |
42 | 67 | } |
| 68 | + |
| 69 | +// Test that error scopes error when popped in the wrong order. |
| 70 | +#[test] |
| 71 | +#[should_panic(expected = "error scopes must be popped in reverse order")] |
| 72 | +fn pop_out_of_order() { |
| 73 | + let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default()); |
| 74 | + |
| 75 | + let scope1 = device.push_error_scope(wgpu::ErrorFilter::Validation); |
| 76 | + let _scope2 = device.push_error_scope(wgpu::ErrorFilter::Validation); |
| 77 | + |
| 78 | + let _ = pollster::block_on(scope1.pop()); |
| 79 | +} |
| 80 | + |
| 81 | +// Test that error scopes are automatically popped when dropped. |
| 82 | +#[test] |
| 83 | +fn drop_automatically_pops() { |
| 84 | + let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default()); |
| 85 | + |
| 86 | + let uncaptured_error = register_uncaptured_error_handler(&device); |
| 87 | + |
| 88 | + let scope = device.push_error_scope(wgpu::ErrorFilter::Validation); |
| 89 | + raise_validation_error(&device); |
| 90 | + drop(scope); // Automatically pops the error scope. |
| 91 | + |
| 92 | + assert_eq!(uncaptured_error.load(Ordering::Relaxed), false); |
| 93 | + |
| 94 | + // Raising another error will go to the uncaptured error handler, not the dropped scope. |
| 95 | + raise_validation_error(&device); |
| 96 | + |
| 97 | + assert_eq!(uncaptured_error.load(Ordering::Relaxed), true); |
| 98 | +} |
| 99 | + |
| 100 | +// Test that error scopes are automatically popped when dropped during unwinding, |
| 101 | +// even when they are dropped out of order. |
| 102 | +#[test] |
| 103 | +fn drop_during_unwind() { |
| 104 | + let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default()); |
| 105 | + |
| 106 | + let scope1 = device.push_error_scope(wgpu::ErrorFilter::Validation); |
| 107 | + let scope2 = device.push_error_scope(wgpu::ErrorFilter::Validation); |
| 108 | + |
| 109 | + let _ = std::panic::catch_unwind(AssertUnwindSafe(|| { |
| 110 | + raise_validation_error(&device); |
| 111 | + // Move scope1 so that it is dropped before scope2. |
| 112 | + let _scope2 = scope2; |
| 113 | + let _scope1 = scope1; |
| 114 | + panic!("trigger unwind"); |
| 115 | + })); |
| 116 | +} |
0 commit comments