use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
use super::{
Captured, TransactionContext, TransactionFailure, capture, is_supported, system_proc,
with_applied,
};
type CreateTransactionFn = unsafe extern "system" fn(
*mut core::ffi::c_void,
*mut core::ffi::c_void,
u32,
u32,
u32,
u32,
*const u16,
) -> HANDLE;
type RollbackTransactionFn = unsafe extern "system" fn(HANDLE) -> i32;
struct Transaction(HANDLE);
impl Transaction {
fn new() -> Option<Self> {
let create = system_proc("ktmw32.dll", b"CreateTransaction\0")?;
let create: CreateTransactionFn = unsafe { std::mem::transmute(create) };
let handle = unsafe {
create(
std::ptr::null_mut(),
std::ptr::null_mut(),
0,
0,
0,
0,
std::ptr::null(),
)
};
if handle.is_null() || handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE {
return None;
}
Some(Self(handle))
}
}
impl Drop for Transaction {
fn drop(&mut self) {
if let Some(rollback) = system_proc("ktmw32.dll", b"RollbackTransaction\0") {
let rollback: RollbackTransactionFn = unsafe { std::mem::transmute(rollback) };
unsafe { rollback(self.0) };
}
unsafe { CloseHandle(self.0) };
}
}
fn while_transacted<T>(transaction: &Transaction, body: impl FnOnce() -> T) -> T {
super::set_current(transaction.0).expect("installing a transaction");
let outcome = body();
super::set_current(std::ptr::null_mut()).expect("clearing the transaction");
outcome
}
fn live() -> HANDLE {
super::current_raw().expect("the entry points resolved")
}
#[test]
fn the_entry_points_resolve_on_this_system() {
assert!(
is_supported(),
"ktmw32.dll did not offer the thread-transaction entry points"
);
}
#[test]
fn a_thread_without_a_transaction_captures_as_absent() {
assert!(
super::is_none_sentinel(live()),
"precondition: no transaction"
);
let captured = capture().expect("capture succeeds");
assert!(
matches!(captured, Captured::Absent),
"an untransacted thread should capture as Absent, not NotCaptured or Present"
);
}
#[test]
fn not_captured_leaves_the_threads_own_transaction_alone() {
let Some(transaction) = Transaction::new() else {
eprintln!("skipped: this system cannot create a transaction");
return;
};
while_transacted(&transaction, || {
let before = live();
let value = with_applied(&Captured::NotCaptured, || {
assert_eq!(live(), before, "NotCaptured disturbed the thread");
7
})
.expect("nothing to install");
assert_eq!(value, 7);
assert_eq!(live(), before);
});
}
#[test]
fn absent_clears_the_threads_transaction_for_the_operation() {
let Some(transaction) = Transaction::new() else {
eprintln!("skipped: this system cannot create a transaction");
return;
};
while_transacted(&transaction, || {
let before = live();
assert!(!super::is_none_sentinel(before), "precondition: transacted");
let cleared = with_applied(&Captured::Absent, live).expect("apply absent");
assert!(
super::is_none_sentinel(cleared),
"Absent did not clear the thread's transaction"
);
assert_eq!(live(), before, "the entry transaction was not restored");
});
}
#[test]
fn a_captured_transaction_is_installed_and_restored() {
let Some(transaction) = Transaction::new() else {
eprintln!("skipped: this system cannot create a transaction");
return;
};
let captured = while_transacted(&transaction, || {
capture().expect("capture while transacted")
});
assert!(matches!(captured, Captured::Present(_)));
assert!(
super::is_none_sentinel(live()),
"precondition: untransacted"
);
let during = with_applied(&captured, live).expect("apply");
assert!(
!super::is_none_sentinel(during),
"the transaction was not installed"
);
assert!(
super::is_none_sentinel(live()),
"the thread was left transacted"
);
}
#[test]
fn the_captured_handle_is_a_duplicate_not_the_originals_value() {
let Some(transaction) = Transaction::new() else {
eprintln!("skipped: this system cannot create a transaction");
return;
};
let original = transaction.0;
let captured = while_transacted(&transaction, || capture().expect("capture"));
let Captured::Present(context) = &captured else {
panic!("expected a present transaction");
};
assert_ne!(
context.as_raw(),
original,
"capture returned the caller's own handle rather than a duplicate"
);
}
#[test]
fn a_captured_transaction_applies_on_another_thread() {
let Some(transaction) = Transaction::new() else {
eprintln!("skipped: this system cannot create a transaction");
return;
};
let captured = while_transacted(&transaction, || capture().expect("capture"));
let observed = std::thread::spawn(move || {
let inherited = super::is_none_sentinel(live());
let during = with_applied(&captured, || super::is_none_sentinel(live()))
.expect("apply on the worker");
(inherited, during, super::is_none_sentinel(live()))
})
.join()
.expect("the worker did not panic");
assert!(observed.0, "a fresh worker should carry no transaction");
assert!(!observed.1, "the transaction did not reach the worker");
assert!(observed.2, "the worker was left transacted");
}
#[test]
fn the_captured_value_outlives_the_originating_handle() {
let Some(transaction) = Transaction::new() else {
eprintln!("skipped: this system cannot create a transaction");
return;
};
let captured = while_transacted(&transaction, || capture().expect("capture"));
drop(transaction);
let during = with_applied(&captured, live).expect("apply after the original closed");
assert!(
!super::is_none_sentinel(during),
"the duplicate did not survive its originating handle"
);
}
#[test]
fn the_operations_return_value_is_passed_through() {
let value = with_applied(&Captured::NotCaptured, || String::from("carried"))
.expect("nothing to install");
assert_eq!(value, "carried");
}
#[test]
fn an_unsupported_failure_is_distinct_from_absence() {
assert_ne!(TransactionFailure::Unsupported, TransactionFailure::Install);
assert_ne!(
TransactionFailure::Unsupported,
TransactionFailure::Duplicate
);
}
#[test]
fn a_context_is_send_so_it_can_reach_a_worker() {
fn assert_send<T: Send>() {}
assert_send::<TransactionContext>();
assert_send::<Captured<TransactionContext>>();
}