Skip to main content

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}