mpi/environment.rs
1//! Environmental management
2//!
3//! This module provides ways for an MPI program to interact with its environment.
4//!
5//! # Unfinished features
6//!
7//! - **8.1.2**: `MPI_TAG_UB`, ...
8//! - **8.2**: Memory allocation
9//! - **8.3, 8.4, and 8.5**: Error handling
10
11use std::{
12 cmp::Ordering,
13 os::raw::{c_char, c_double, c_int, c_void},
14 ptr,
15 string::FromUtf8Error,
16 sync::RwLock,
17 thread::{self, ThreadId},
18};
19
20use conv::ConvUtil;
21use once_cell::sync::Lazy;
22
23use crate::{
24 attribute::{AppNum, UniverseSize},
25 ffi,
26 topology::{traits::AnyCommunicator, Communicator, InterCommunicator, SimpleCommunicator},
27 traits::{AsRaw, FromRaw},
28 with_uninitialized, with_uninitialized2,
29};
30
31/// Internal data structure used to uphold certain MPI invariants.
32/// State is currently only used with the derive feature.
33pub(crate) struct UniverseState {
34 #[allow(unused)]
35 pub main_thread: ThreadId,
36}
37
38pub(crate) static UNIVERSE_STATE: Lazy<RwLock<Option<UniverseState>>> =
39 Lazy::new(|| RwLock::new(None));
40
41/// Global context
42pub struct Universe {
43 buffer: Option<Vec<u8>>,
44}
45
46impl Universe {
47 /// The 'world communicator'
48 ///
49 /// Contains all processes initially partaking in the computation.
50 ///
51 /// # Examples
52 /// See `examples/simple.rs`
53 pub fn world(&self) -> SimpleCommunicator {
54 SimpleCommunicator::world()
55 }
56
57 /// Total number of "slots" that can reasonably be filled in the environment
58 ///
59 /// This can be larger or smaller than the world (e.g., when
60 /// oversubscribed). The universe size is generally not a hard limit. A
61 /// universe size need not be set. To specify universe size, MPICH mpiexec
62 /// supports the command line option `-usize 123` and Open MPI supports the
63 /// environment variable `OMPI_UNIVERSE_SIZE=123`.
64 ///
65 /// # Standard section(s)
66 ///
67 /// 11.10.1
68 pub fn size(&self) -> Option<usize> {
69 self.world()
70 .get_attr::<UniverseSize>()
71 .map(|s| usize::try_from(s).expect("universe size must be non-negative"))
72 }
73
74 /// The app number is the invocation number of the current app in a call to
75 /// `MPI_Comm_spawn_multiple()` or colon-delimited `mpiexec`.
76 ///
77 /// # Standard section
78 ///
79 /// 11.10.3
80 pub fn appnum(&self) -> Option<isize> {
81 self.world().get_attr::<AppNum>().map(isize::from)
82 }
83
84 /// The size in bytes of the buffer used for buffered communication.
85 pub fn buffer_size(&self) -> usize {
86 self.buffer.as_ref().map_or(0, Vec::len)
87 }
88
89 /// Set the size in bytes of the buffer used for buffered communication.
90 pub fn set_buffer_size(&mut self, size: usize) {
91 self.detach_buffer();
92
93 if size > 0 {
94 let mut buffer = vec![0; size];
95 unsafe {
96 ffi::MPI_Buffer_attach(
97 buffer.as_mut_ptr() as _,
98 buffer
99 .len()
100 .value_as()
101 .expect("Buffer length exceeds the range of a C int."),
102 );
103 }
104 self.buffer = Some(buffer);
105 }
106 }
107
108 /// Detach the buffer used for buffered communication.
109 pub fn detach_buffer(&mut self) {
110 if let Some(buffer) = self.buffer.take() {
111 let mut addr: *const c_void = ptr::null();
112 let addr_ptr: *mut *const c_void = &mut addr;
113 let mut size: c_int = 0;
114 unsafe {
115 ffi::MPI_Buffer_detach(addr_ptr as *mut c_void, &mut size);
116 assert_eq!(addr, buffer.as_ptr() as _);
117 }
118 assert_eq!(
119 size,
120 buffer
121 .len()
122 .value_as()
123 .expect("Buffer length exceeds the range of a C int.")
124 );
125 }
126 }
127
128 /// Disconnect parent
129 ///
130 /// MPICH can be configured to print leaked MPI objects. At this time (circa
131 /// 4.1b1), it reports a leak if a child process exits without freeing the
132 /// parent. This seems overly aggressive given the following.
133 ///
134 /// Advice to users.
135 /// MPI_COMM_GET_PARENT returns a handle to a single inter-
136 /// communicator. Calling MPI_COMM_GET_PARENT a second time returns a handle
137 /// to the same inter-communicator. Freeing the handle with MPI_COMM_DISCONNECT
138 /// or MPI_COMM_FREE will cause other references to the inter-communicator to become
139 /// invalid (dangling). Note that calling MPI_COMM_FREE on the parent communicator
140 /// is not useful.
141 ///
142 /// # Standard section(s)
143 ///
144 /// 11.8.2
145 pub fn disconnect_parent(&mut self) {
146 if let Some(parent) = self.world().parent() {
147 // Make it look like a user communicator so it can be dropped
148 let _p = unsafe { InterCommunicator::from_raw(parent.as_raw()) };
149 }
150 }
151
152 fn free_attribute_keys(&mut self) {
153 let mut comm_attrs = crate::attribute::COMM_ATTRS.write().unwrap();
154 for (_, v) in comm_attrs.drain() {
155 let mut k = v.as_raw();
156 unsafe { ffi::MPI_Comm_free_keyval(&mut k) };
157 }
158 }
159}
160
161impl Drop for Universe {
162 fn drop(&mut self) {
163 // This can only ever be called once since it's only possible to initialize a single
164 // Universe per application run.
165 //
166 // NOTE: The write lock is taken to prevent racing with `#[derive(Equivalence)]`
167 let mut _universe_state = UNIVERSE_STATE
168 .write()
169 .expect("rsmpi internal error: UNIVERSE_STATE lock poisoned");
170
171 self.detach_buffer();
172 self.disconnect_parent();
173 self.free_attribute_keys();
174 unsafe {
175 ffi::MPI_Finalize();
176 }
177 }
178}
179
180/// Describes the various levels of multithreading that can be supported by an MPI library.
181///
182/// # Examples
183/// See `examples/init_with_threading.rs`
184///
185/// # Standard section(s)
186///
187/// 12.4.3
188#[derive(Copy, Clone, PartialEq, Eq, Debug)]
189pub enum Threading {
190 /// All processes partaking in the computation are single-threaded.
191 Single,
192 /// Processes may be multi-threaded, but MPI functions will only ever be called from the main
193 /// thread.
194 Funneled,
195 /// Processes may be multi-threaded, but calls to MPI functions will not be made concurrently.
196 /// The user is responsible for serializing the calls.
197 Serialized,
198 /// Processes may be multi-threaded with no restrictions on the use of MPI functions from the
199 /// threads.
200 Multiple,
201}
202
203impl Threading {
204 /// The raw value understood by the MPI C API
205 fn as_raw(self) -> c_int {
206 match self {
207 Threading::Single => unsafe { ffi::RSMPI_THREAD_SINGLE },
208 Threading::Funneled => unsafe { ffi::RSMPI_THREAD_FUNNELED },
209 Threading::Serialized => unsafe { ffi::RSMPI_THREAD_SERIALIZED },
210 Threading::Multiple => unsafe { ffi::RSMPI_THREAD_MULTIPLE },
211 }
212 }
213}
214
215impl PartialOrd<Threading> for Threading {
216 fn partial_cmp(&self, other: &Threading) -> Option<Ordering> {
217 Some(self.cmp(other))
218 }
219}
220
221impl Ord for Threading {
222 fn cmp(&self, other: &Threading) -> Ordering {
223 self.as_raw().cmp(&other.as_raw())
224 }
225}
226
227impl From<c_int> for Threading {
228 fn from(i: c_int) -> Threading {
229 if i == unsafe { ffi::RSMPI_THREAD_SINGLE } {
230 return Threading::Single;
231 } else if i == unsafe { ffi::RSMPI_THREAD_FUNNELED } {
232 return Threading::Funneled;
233 } else if i == unsafe { ffi::RSMPI_THREAD_SERIALIZED } {
234 return Threading::Serialized;
235 } else if i == unsafe { ffi::RSMPI_THREAD_MULTIPLE } {
236 return Threading::Multiple;
237 }
238 panic!("Unknown threading level: {}", i)
239 }
240}
241
242/// Whether the MPI library has been initialized.
243///
244/// This function can be called at any time, including before initialization and after finalization.
245/// If the goal is to initialize MPI only if it has not been initialized yet, prefer calling
246/// [`initialize`] or [`initialize_with_threading`] directly — they return `None` rather than
247/// failing when MPI is already initialized, and they hold an internal lock that prevents two
248/// threads from both calling `MPI_Init_thread` at the same time.
249///
250/// Using `is_initialized()` as a condition before calling `initialize()` in a multithreaded
251/// program introduces a time-of-check/time-of-use race: another thread (or external C code)
252/// could initialize MPI in the window between the check and the call, causing `MPI_Init_thread`
253/// to fail. This is not a soundness issue, but it means the pattern
254/// `if !is_initialized() { initialize() }` is not useful under concurrent use.
255///
256/// `is_initialized()` is appropriate for diagnostics, assertions, and guard checks in contexts
257/// where no concurrent initialization will be attempted.
258///
259/// # Standard section(s)
260///
261/// 11.2, 11.6
262pub fn is_initialized() -> bool {
263 unsafe { with_uninitialized(|initialized| ffi::MPI_Initialized(initialized)).1 != 0 }
264}
265
266/// Whether the MPI library has been finalized.
267///
268/// This function can be called at any time, including before initialization and after finalization.
269/// Once MPI is finalized, it cannot be reinitialized; calling [`initialize`] or
270/// [`initialize_with_threading`] after finalization is erroneous per the MPI standard.
271///
272/// See [`is_initialized`] for a discussion of the race condition that applies when using
273/// these status functions to guard initialization in a multithreaded program.
274///
275/// # Standard section(s)
276///
277/// 11.2, 11.6
278pub fn is_finalized() -> bool {
279 unsafe { with_uninitialized(|finalized| ffi::MPI_Finalized(finalized)).1 != 0 }
280}
281
282/// Initialize MPI.
283///
284/// If the MPI library has not been initialized so far, initializes and returns a representation
285/// of the MPI communication `Universe` which provides access to additional functions.
286/// Otherwise returns `None`.
287///
288/// Equivalent to: `initialize_with_threading(Threading::Single)`
289///
290/// # Examples
291/// See `examples/simple.rs`
292///
293/// # Standard section(s)
294///
295/// 8.7
296pub fn initialize() -> Option<Universe> {
297 initialize_with_threading(Threading::Single).map(|x| x.0)
298}
299
300/// Initialize MPI with desired level of multithreading support.
301///
302/// If the MPI library has not been initialized so far, tries to initialize with the desired level
303/// of multithreading support and returns the MPI communication `Universe` with access to
304/// additional functions as well as the level of multithreading actually supported by the
305/// implementation. Otherwise returns `None`.
306///
307/// # Examples
308/// See `examples/init_with_threading.rs`
309///
310/// # Standard section(s)
311///
312/// 12.4.3
313pub fn initialize_with_threading(threading: Threading) -> Option<(Universe, Threading)> {
314 // Takes the lock before checking if MPI is initialized to prevent a race condition
315 // leading to two threads both calling `MPI_Init_thread` at the same time.
316 //
317 // NOTE: This is necessary even without the derive feature - we use this `Mutex` to ensure
318 // no race in initializing MPI.
319 let mut universe_state = UNIVERSE_STATE
320 .write()
321 .expect("rsmpi internal error: UNIVERSE_STATE lock poisoned");
322
323 if is_initialized() {
324 return None;
325 }
326
327 let (_, provided) = unsafe {
328 with_uninitialized(|provided| {
329 ffi::MPI_Init_thread(
330 ptr::null_mut(),
331 ptr::null_mut(),
332 threading.as_raw(),
333 provided,
334 )
335 })
336 };
337
338 // No need to check if UNIVERSE_STATE has already been set - only one thread can enter this
339 // code section per MPI run thanks to the `is_initialized()` check before.
340 *universe_state = Some(UniverseState {
341 main_thread: thread::current().id(),
342 });
343
344 Some((Universe { buffer: None }, provided.into()))
345}
346
347/// Level of multithreading supported by this MPI universe
348///
349/// See the `Threading` enum.
350///
351/// # Examples
352/// See `examples/init_with_threading.rs`
353pub fn threading_support() -> Threading {
354 unsafe {
355 with_uninitialized(|threading| ffi::MPI_Query_thread(threading))
356 .1
357 .into()
358 }
359}
360
361/// Identifies the version of the MPI standard implemented by the library.
362///
363/// Returns a tuple of `(version, subversion)`, e.g. `(3, 0)`.
364///
365/// Can be called without initializing MPI.
366pub fn version() -> (c_int, c_int) {
367 let (_, version, subversion) = unsafe {
368 with_uninitialized2(|version, subversion| ffi::MPI_Get_version(version, subversion))
369 };
370 (version, subversion)
371}
372
373/// Describes the version of the MPI library itself.
374///
375/// Can return an `Err` if the description of the MPI library is not a UTF-8 string.
376///
377/// Can be called without initializing MPI.
378pub fn library_version() -> Result<String, FromUtf8Error> {
379 let bufsize = unsafe { ffi::RSMPI_MAX_LIBRARY_VERSION_STRING }
380 .value_as()
381 .unwrap_or_else(|_| {
382 panic!(
383 "MPI_MAX_LIBRARY_SIZE ({}) cannot be expressed as a usize.",
384 unsafe { ffi::RSMPI_MAX_LIBRARY_VERSION_STRING }
385 )
386 });
387 let mut buf = vec![0u8; bufsize];
388 let mut len: c_int = 0;
389
390 unsafe {
391 ffi::MPI_Get_library_version(buf.as_mut_ptr() as *mut c_char, &mut len);
392 }
393 buf.truncate(len.value_as().unwrap_or_else(|_| {
394 panic!(
395 "Length of library version string ({}) cannot \
396 be expressed as a usize.",
397 len
398 )
399 }));
400 String::from_utf8(buf)
401}
402
403/// Names the processor that the calling process is running on.
404///
405/// Can return an `Err` if the processor name is not a UTF-8 string.
406pub fn processor_name() -> Result<String, FromUtf8Error> {
407 let bufsize = unsafe { ffi::RSMPI_MAX_PROCESSOR_NAME }
408 .value_as()
409 .unwrap_or_else(|_| {
410 panic!(
411 "MPI_MAX_LIBRARY_SIZE ({}) \
412 cannot be expressed as a \
413 usize.",
414 unsafe { ffi::RSMPI_MAX_PROCESSOR_NAME }
415 )
416 });
417 let mut buf = vec![0u8; bufsize];
418 let mut len: c_int = 0;
419
420 unsafe {
421 ffi::MPI_Get_processor_name(buf.as_mut_ptr() as *mut c_char, &mut len);
422 }
423 buf.truncate(len.value_as().unwrap_or_else(|_| {
424 panic!(
425 "Length of processor name string ({}) cannot be \
426 expressed as a usize.",
427 len
428 )
429 }));
430 String::from_utf8(buf)
431}
432
433/// Time in seconds since an arbitrary time in the past.
434///
435/// The cheapest high-resolution timer available will be used.
436pub fn time() -> c_double {
437 unsafe { ffi::RSMPI_Wtime() }
438}
439
440/// Resolution of timer used in `time()` in seconds
441pub fn time_resolution() -> c_double {
442 unsafe { ffi::RSMPI_Wtick() }
443}