Skip to content

Commit 7db8049

Browse files
committed
simplify and fix FFI callback example
- make it thread-safe and also resistant to compiler optimization (setting the tag to invalid may be optimized away) - remove id
1 parent 05bd4cc commit 7db8049

File tree

2 files changed

+101
-71
lines changed

2 files changed

+101
-71
lines changed

src/en/07_ffi.md

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -667,21 +667,23 @@ In C for instance there is no easy way to check that the appropriate destructor
667667
is checked. A possible approach is to exploit callbacks to ensure that the
668668
reclamation is done.
669669

670-
The following Rust code is a **thread-unsafe** example of a C-compatible API
671-
that provide callback to ensure safe resource
672-
reclamation:
670+
The following Rust code is an example of a C-compatible API that exploits
671+
callbacks to ensure safe resource reclamation:
673672

674-
```rust,noplaypen
673+
```rust
675674
# use std::ops::Drop;
676675
#
677-
pub struct XtraResource {/*fields */}
676+
pub struct XtraResource { /* fields */ }
678677

679678
impl XtraResource {
680679
pub fn new() -> Self {
681680
XtraResource { /* ... */}
682681
}
683-
pub fn dosthg(&mut self) {
684-
/*...*/
682+
pub fn dosthg(&mut self, arg: u32) {
683+
/*... things that may panic ... */
684+
# if arg == 0xDEAD_C0DE {
685+
# panic!("oops XtraResource.dosthg panics!");
686+
# }
685687
}
686688
}
687689

@@ -693,57 +695,55 @@ impl Drop for XtraResource {
693695

694696
pub mod c_api {
695697
use super::XtraResource;
696-
use std::panic::catch_unwind;
698+
use std::panic::{catch_unwind, AssertUnwindSafe};
699+
use std::sync::atomic::{AtomicU32, Ordering};
697700

698701
const INVALID_TAG: u32 = 0;
699702
const VALID_TAG: u32 = 0xDEAD_BEEF;
700703
const ERR_TAG: u32 = 0xDEAF_CAFE;
701704

702-
static mut COUNTER: u32 = 0;
703-
704705
pub struct CXtraResource {
705-
tag: u32, // to detect accidental reuse
706-
id: u32,
706+
tag: AtomicU32, // to detect accidental reuse
707707
inner: XtraResource,
708708
}
709709

710710
#[no_mangle]
711-
pub unsafe extern "C" fn xtra_with(cb: extern "C" fn(*mut CXtraResource) -> ()) {
712-
let inner = if let Ok(res) = catch_unwind(XtraResource::new) {
711+
pub unsafe extern "C" fn xtra_with(cb: unsafe extern "C" fn(*mut CXtraResource) -> ()) {
712+
let inner = if let Ok(res) = catch_unwind(AssertUnwindSafe(XtraResource::new)) {
713713
res
714714
} else {
715715
# println!("cannot allocate resource");
716716
return;
717717
};
718-
let id = COUNTER;
719718
let tag = VALID_TAG;
720719

721-
COUNTER = COUNTER.wrapping_add(1);
722-
// Use heap memory and do not provide pointer to stack to C code!
723-
let mut boxed = Box::new(CXtraResource { tag, id, inner });
720+
let mut wrapped = CXtraResource {
721+
tag: AtomicU32::new(tag),
722+
inner
723+
};
724724

725-
# println!("running the callback on {:p}", boxed.as_ref());
726-
cb(boxed.as_mut() as *mut CXtraResource);
725+
# println!("running the callback on {:p}", &wrapped);
726+
cb(&mut wrapped as *mut CXtraResource);
727727

728-
if boxed.id == id && (boxed.tag == VALID_TAG || boxed.tag == ERR_TAG) {
729-
# println!("freeing {:p}", boxed.as_ref());
730-
boxed.tag = INVALID_TAG; // prevent accidental reuse
731-
// implicit boxed drop
728+
// prevent accidental reuse
729+
let new_tag = wrapped.tag.swap(INVALID_TAG, Ordering::SeqCst);
730+
if new_tag == VALID_TAG || new_tag == ERR_TAG {
731+
# println!("freeing {:p}", &wrapped);
732+
// implicit drop of wrappped
732733
} else {
733-
# println!("forgetting {:p}", boxed.as_ref());
734+
# println!("forgetting {:p}", &wrapped);
734735
// (...) error handling (should be fatal)
735-
boxed.tag = INVALID_TAG; // prevent reuse
736-
std::mem::forget(boxed); // boxed is corrupted it should not be
736+
std::mem::forget(wrapped);
737737
}
738738
}
739739

740740
#[no_mangle]
741-
pub unsafe extern "C" fn xtra_dosthg(cxtra: *mut CXtraResource) {
742-
let do_it = || {
741+
pub unsafe extern "C" fn xtra_dosthg(cxtra: *mut CXtraResource, arg: u32) {
742+
let do_it = move || {
743743
if let Some(cxtra) = cxtra.as_mut() {
744-
if cxtra.tag == VALID_TAG {
744+
if cxtra.tag.load(Ordering::SeqCst) == VALID_TAG {
745745
# println!("doing something with {:p}", cxtra);
746-
cxtra.inner.dosthg();
746+
cxtra.inner.dosthg(arg);
747747
return;
748748
}
749749
}
@@ -752,25 +752,40 @@ pub mod c_api {
752752
if catch_unwind(do_it).is_err() {
753753
if let Some(cxtra) = cxtra.as_mut() {
754754
# println!("panicking with {:p}", cxtra);
755-
cxtra.tag = ERR_TAG;
755+
cxtra.tag.store(ERR_TAG, Ordering::SeqCst);
756756
}
757757
};
758758
}
759759
}
760760
#
761-
# fn main() {}
761+
# fn main() {
762+
# // do not use, only for testing purposes
763+
# use c_api::*;
764+
# unsafe {
765+
# unsafe extern "C" fn cb_ok(p: *mut CXtraResource) {
766+
# xtra_dosthg(p, 0);
767+
# }
768+
# unsafe extern "C" fn cb_panic(p: *mut CXtraResource) {
769+
# xtra_dosthg(p, 0xDEADC0DE);
770+
# }
771+
# xtra_with(cb_ok);
772+
# xtra_with(cb_panic);
773+
# }
774+
# }
762775
```
763776

764777
A compatible C call:
765778

766779
```c
780+
#include <stdint.h>
781+
767782
struct XtraResource;
768-
void xtra_with(void (*cb)(XtraResource* xtra));
783+
void xtra_with(void (*cb)(XtraResource* xtra), uint32_t arg);
769784
void xtra_sthg(XtraResource* xtra);
770785

771786
void cb(XtraResource* xtra) {
772787
// ()...) do anything with the proposed C API for XtraResource
773-
xtra_sthg(xtra);
788+
xtra_sthg(xtra, 0);
774789
}
775790

776791
int main() {

src/fr/07_ffi.md

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -720,11 +720,10 @@ En C par exemple, il n'y a pas de moyen simple qui permette de vérifier que le
720720
destructeur correspondant est appelé. Il est possible d'utiliser des _callbacks_
721721
pour assurer que la libération est effectivement faite.
722722

723-
Le code Rust suivant est un exemple **_unsafe_ du point de vue des threads**
724-
d'une API compatible avec le C qui fournit une _callback_ pour assurer la
725-
libération d'une ressource :
723+
Le code Rust suivant est un exemple d'une API compatible avec le C qui exploite
724+
les _callbacks_ pour assurer la libération d'une ressource :
726725

727-
```rust,noplaypen
726+
```rust
728727
# use std::ops::Drop;
729728
#
730729
pub struct XtraResource { /* champs */ }
@@ -733,8 +732,12 @@ impl XtraResource {
733732
pub fn new() -> Self {
734733
XtraResource { /* ... */ }
735734
}
736-
pub fn dosthg(&mut self) {
737-
/* ... */
735+
736+
pub fn dosthg(&mut self, arg: u32) {
737+
/*... des choses qui peuvent paniquer ... */
738+
# if arg == 0xDEAD_C0DE {
739+
# panic!("oups XtraResource.dosthg panique!");
740+
# }
738741
}
739742
}
740743

@@ -746,58 +749,55 @@ impl Drop for XtraResource {
746749

747750
pub mod c_api {
748751
use super::XtraResource;
749-
use std::panic::catch_unwind;
752+
use std::panic::{catch_unwind, AssertUnwindSafe};
753+
use std::sync::atomic::{AtomicU32, Ordering};
750754

751755
const INVALID_TAG: u32 = 0;
752756
const VALID_TAG: u32 = 0xDEAD_BEEF;
753757
const ERR_TAG: u32 = 0xDEAF_CAFE;
754758

755-
static mut COUNTER: u32 = 0;
756-
757759
pub struct CXtraResource {
758-
tag: u32, // pour prévenir d'une réutilisation accidentelle
759-
id: u32,
760+
tag: AtomicU32, // pour prévenir d'une réutilisation accidentelle
760761
inner: XtraResource,
761762
}
762763

763764
#[no_mangle]
764-
pub unsafe extern "C" fn xtra_with(cb: extern "C" fn(*mut CXtraResource) -> ()) {
765-
let inner = if let Ok(res) = catch_unwind(XtraResource::new) {
765+
pub unsafe extern "C" fn xtra_with(cb: unsafe extern "C" fn(*mut CXtraResource) -> ()) {
766+
let inner = if let Ok(res) = catch_unwind(AssertUnwindSafe(XtraResource::new)) {
766767
res
767768
} else {
768769
# println!("impossible d'allouer la ressource");
769770
return;
770771
};
771-
let id = COUNTER;
772772
let tag = VALID_TAG;
773773

774-
COUNTER = COUNTER.wrapping_add(1);
775-
// Utilisation de la mémoire du tas pour ne pas fournir de pointeur de
776-
// pile au code C!
777-
let mut boxed = Box::new(CXtraResource { tag, id, inner });
774+
let mut wrapped = CXtraResource {
775+
tag: AtomicU32::new(tag),
776+
inner
777+
};
778778

779-
# println!("running the callback on {:p}", boxed.as_ref());
780-
cb(boxed.as_mut() as *mut CXtraResource);
779+
# println!("appel du callback sur {:p}", &wrapped);
780+
cb(&mut wrapped as *mut CXtraResource);
781781

782-
if boxed.id == id && (boxed.tag == VALID_TAG || boxed.tag == ERR_TAG) {
783-
# println!("freeing {:p}", boxed.as_ref());
784-
boxed.tag = INVALID_TAG; // prévention d'une réutilisation accidentelle
785-
// drop implicite de la `box`
782+
// pour éviter de réutilisation accidentelle
783+
let new_tag = wrapped.tag.swap(INVALID_TAG, Ordering::SeqCst);
784+
if new_tag == VALID_TAG || new_tag == ERR_TAG {
785+
# println!("libération de {:p}", &wrapped);
786+
// drop implicite de la `box`
786787
} else {
787-
# println!("oubli de {:p}", boxed.as_ref());
788+
# println!("oubli de {:p}", &wrapped);
788789
// (...) gestion des erreurs (partie critique)
789-
boxed.tag = INVALID_TAG; // prévention d'une réutilisation
790-
std::mem::forget(boxed); // boxed is corrupted it should not be
790+
std::mem::forget(wrapped); // la boîte est corrompu, ne pas libérer!
791791
}
792792
}
793793

794794
#[no_mangle]
795-
pub unsafe extern "C" fn xtra_dosthg(cxtra: *mut CXtraResource) {
796-
let do_it = || {
795+
pub unsafe extern "C" fn xtra_dosthg(cxtra: *mut CXtraResource, arg: u32) {
796+
let do_it = move || {
797797
if let Some(cxtra) = cxtra.as_mut() {
798-
if cxtra.tag == VALID_TAG {
799-
# println!("doing something with {:p}", cxtra);
800-
cxtra.inner.dosthg();
798+
if cxtra.tag.load(Ordering::SeqCst) == VALID_TAG {
799+
# println!("fait quelque chose avec {:p}", cxtra);
800+
cxtra.inner.dosthg(arg);
801801
return;
802802
}
803803
}
@@ -806,25 +806,40 @@ pub mod c_api {
806806
if catch_unwind(do_it).is_err() {
807807
if let Some(cxtra) = cxtra.as_mut() {
808808
# println!("panic avec {:p}", cxtra);
809-
cxtra.tag = ERR_TAG;
809+
cxtra.tag.store(ERR_TAG, Ordering::SeqCst);
810810
}
811811
};
812812
}
813813
}
814814
#
815-
# fn main() {}
815+
# fn main() {
816+
# // ne pas utiliser, seulement pour le test
817+
# use c_api::*;
818+
# unsafe {
819+
# unsafe extern "C" fn cb_ok(p: *mut CXtraResource) {
820+
# xtra_dosthg(p, 0);
821+
# }
822+
# unsafe extern "C" fn cb_panic(p: *mut CXtraResource) {
823+
# xtra_dosthg(p, 0xDEADC0DE);
824+
# }
825+
# xtra_with(cb_ok);
826+
# xtra_with(cb_panic);
827+
# }
828+
# }
816829
```
817830

818831
Un appel C compatible :
819832

820833
```c
834+
#include <stdint.h>
835+
821836
struct XtraResource;
822-
void xtra_with(void (*cb)(XtraResource* xtra));
837+
void xtra_with(void (*cb)(XtraResource* xtra), uint32_t arg);
823838
void xtra_sthg(XtraResource* xtra);
824839

825840
void cb(XtraResource* xtra) {
826841
// ()...) do anything with the proposed C API for XtraResource
827-
xtra_sthg(xtra);
842+
xtra_sthg(xtra, 0);
828843
}
829844

830845
int main() {

0 commit comments

Comments
 (0)