I'm working on some FFI bindings to the nvml (also known as libpmem
or pmem.io
) family of C libraries - specifically, libpmemobj. This library makes heavy use of macros which hide the use of setjmp
for transactions which persist 'objects' (C structs). I'm slowly unwrapping these and trying to translate them into Rust code. My understanding of using setjmp
(apart from don't) is that any locally defined variables used need to be volatile
in C-speak. Rust doesn't have the equivalent - it uses explicit loads and stores. I'm nervous of potential gotchas when wrapping this code:-
- Should I use a function wrapping the core logic which is marked as
#[inline(never)]
to isolate thesetjmp
code? - Do any function arguments passed to such an isolating function also need to be treated as if they were volatile?
- Is it safe for a
panic!
to cross the boundary of asetjmp
? - This code needs to perform well, as object persistence transactions could easily be in an application's hot path; in some of the use cases I am envisaging, it could be the majority of the work (reading data at 10Gb/sec+ and persisting). Is passing
Fn()
pointers the best way to achieve this? Would a macro with blocks be better? Is there anyway of telling thematch
code thatwork
is the most likelyif
branch?
The code I have at the moment (a bit long, but I think it makes the question clearer) is below. It assumes the answers are:-
- Yes
- No
- No
- N/A
/// Please note that work() may not ever be called - in which case, the next logic called is onAbort()
pub fn persistentObjectTransaction<Committed: Sized, Aborted: Sized, W: Fn(), C: Fn() -> Committed, A: Fn() -> Aborted>(pop: *mut PMEMobjpool, work: W, onCommit: C, onAbort: A) -> Result<Committed, Aborted>
{
// Must be used as a function, to prevent the volatile restrictions of setjmp leaking out
#[inline(never)]
fn internal
<
Committed: Sized,
Aborted: Sized,
W: Fn(),
C: Fn() -> Committed,
A: Fn() -> Aborted
>
(
pop: *mut PMEMobjpool,
work: W,
onCommit: C,
onAbort: A,
panicPayload: &mut Option<Box<Any + Send + 'static>>,
functionResult: &mut Option<Result<Committed, Aborted>>
) -> Result<Committed, Aborted>
{
const panicOsErrorNumber: c_int = 2;
let mut txSetJmpEnvironment: jmp_buf;
// != 0 if returning from longjmp()
if setjmp(txSetJmpEnvironment) != 0
{
//setErrorNumber(pmemobj_tx_errno());
}
else
{
pmemobj_tx_begin(pop, txSetJmpEnvironment, TX_PARAM_NONE, TX_PARAM_NONE);
// let osErrorNumber = pmemobj_tx_begin(pop, txSetJmpEnvironment, TX_PARAM_NONE, TX_PARAM_NONE);
// if unlikely(osErrorNumber != 0)
// {
// setErrorNumber(osErrorNumber);
// }
}
let mut stage: pobj_tx_stage;
while
{
stage = pmemobj_tx_stage();
stage != pobj_tx_stage::TX_STAGE_NONE
}
{
match stage
{
pobj_tx_stage::TX_STAGE_WORK =>
{
match catch_unwind(AssertUnwindSafe(|| work())
{
Ok(someOsErrorNumberForAbort) =>
{
if likely(someOsErrorNumberForAbort == 0)
{
pmemobj_tx_commit();
}
else
{
pmemobj_tx_abort(someOsErrorNumberForAbort);
}
},
Err(payload) =>
{
pmemobj_tx_abort(panicOsErrorNumber);
*panicPayload = Some(payload);
},
};
pmemobj_tx_process();
},
pobj_tx_stage::TX_STAGE_ONCOMMIT =>
{
match catch_unwind(AssertUnwindSafe(|| onCommit())
{
Ok(result) =>
{
*functionResult = Some(Ok(result))
},
Err(payload) =>
{
if panicPayload.is_none()
{
*panicPayload = Some(payload)
}
}
};
pmemobj_tx_process();
},
pobj_tx_stage::TX_STAGE_ONABORT =>
{
match catch_unwind(AssertUnwindSafe(|| onAbort())
{
Ok(result) =>
{
*functionResult = Some(Err(result))
},
Err(payload) =>
{
if panicPayload.is_none()
{
*panicPayload = Some(payload)
}
}
};
pmemobj_tx_process();
},
pobj_tx_stage::TX_STAGE_FINALLY =>
{
pmemobj_tx_process();
},
_ =>
{
pmemobj_tx_process();
},
}
}
pmemobj_tx_end();
// let ifAbortedTheTransactionErrorNumber = pmemobj_tx_end();
// if unlikely(ifAbortedTheTransactionErrorNumber != 0)
// {
// setErrorNumber(ifAbortedTheTransactionErrorNumber);
// }
}
let mut panicPayload = None;
let mut functionResult = None;
internal(work, onCommit, onAbort, &mut panicPayload, &mut functionResult);
if let Some(payload) = panicPayload
{
resume_unwind(payload);
}
functionResult.unwrap()
}
On a side-note, objects that are persist-able will need to defined using structs in Rust that are #[repr(C)]
. Is there a marker trait that I can rely to enforce this anything passed to a generic fn persist<T: ReprIsC>(value: T)
function?
Many thanks if you've read this far.