-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Translate MPI thread support levels into Rust Send and Sync #12
Comments
Could perhaps add dummy types to represent thread-safety guarantees: pub mod thread {
// !Send + !Sync
pub struct Funneled(PhantomData<*const ()>);
// Send + !Sync
pub struct Serialized(PhantomData<Cell<()>>);
// Send + Sync
pub struct Multiple(());
}
// marker trait that promises Self has correct Send/Sync semantics
pub unsafe trait Thread {
fn raw_value() -> libc::c_int;
}
unsafe impl Thread for thread::Funneled { /* ... */ }
unsafe impl Thread for thread::Serialized { /* ... */ }
unsafe impl Thread for thread::Multiple { /* ... */ } The rest of the API would then parametrize over these dummy types, e.g. pub struct Universe<M> {
buffer: Option<Vec<u8>>,
thread: Phantom<M>,
}
pub enum InitializeError {
AlreadyInitialized,
InsufficientThreadSupport,
}
pub fn initialize<M: Thread>() -> Result<Universe<M>, InitializeError> {
if is_initialized() {
Err(AlreadyInitialized)
} else {
let mut provided: c_int = unsafe { mem::uninitialized() };
let required = M::raw_value();
unsafe {
ffi::MPI_Init_thread(ptr::null_mut(),
ptr::null_mut(),
required,
&mut provided);
}
if provided >= required {
Ok(Universe { buffer: None, thread: PhantomData })
} else {
unsafe {
ffi::MPI_Finalize();
}
Err(InsufficientThreadSupport)
}
}
}
impl<M> Universe<M> {
fn world(&self) -> SystemCommunicator<&Self> { /* ... */ }
} |
It seems apparent to me that a properly sound way to do this will require massive restructuring of the API and careful consideration of every facet: If we want If we want Any available methods of producing MPI types out of thin air must be incapable of producing more than one instance wrapping the same raw value, or else it circumvents the lack of Utilizing the full potential of
|
I've been thinking about this a bit. I think, without a doubt, addressing this strictly for However, it seems to me like the only way to expose Additionally, I think we should provide an optional streamlined approach to a couple things. First, it would be useful to expose a dynamically checked "Universe" implementation. i.e. a universe that appears to Secondly, and perhaps could be folded into the prior feature, it would be useful if we could give users the ability to instantiate an optional, global, library-managed universe. This would make it possible to assume a |
Possibly could do something like this only for "handle objects" returned by a let universe = tokio::sync::Mutex::new(mpi::initialize_with_threading::<Serialized>()?);
// borrow the universe mutex for passing to tokio tasks
let universe = &universe;
// get request objects some way
let requests: Vec<mpi::serialized::Request> = ...;
// using tokio as an example - imagine async/await is supported. tokio::spawn requires a `Send` future.
// This works because mpi::serialized::Request is Send, even though universe is !Send. It no
// longer carries an implied access to the Universe.
requests.into_iter().map(|r| tokio::spawn(async move {
// attach the universe to the request, which get us the normal Request we expect
let universe = universe.lock().await;
let r = r.with_universe(universe);
// wait for the request to complete
r.await;
// universe released and another task can acquire it now
}); I don't love this because it causes a divergence between the Any thoughts? |
I think the type-state approach to Concerning the (It should be possible to reduce the overhead as we do not need full locking, we just need to detect concurrent access and panic, we do not need to enforce it via synchronisation. All "dependent" handle types could also access that shared state as global data since there can only be one universe per process. This would however still mean that their type needs to be parameterised over the threading level AFAIU.) One other wrinkle I see is the Finally, the API should probably encourage making an explicit choice here, probably suggesting |
MPI defines several levels of thread support that indicate "how thread safe" the currently initialized MPI environment is. This level is only known at run-time and could be changed by re-initializing MPI (?).
In Rust, the thread safety of an API is defined by having its types implement the
Send
andSync
traits, which is a compile-time contract.These traits are automatically derived by the compiler which currently leads to wrong results for
rsmpi
, e.g.:Send
andSync
,Send
andSync
.Plan: Add markers to rsmpi types so they can have variants that statically are or are not thread safe and have sources such as
world()
orequivalent_type()
return enum values that return one of those variants depending on the current level of thread support of the environment.The text was updated successfully, but these errors were encountered: