1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! Integration test for the `MPI_COMM_NULL` guard in
//! `ferrompi_comm_create_from_group` (MPI-4 path).
//!
//! Calls `ferrompi_comm_create_from_group` directly via `extern "C"` with a
//! group that excludes some ranks, verifying that those ranks receive a
//! non-`MPI_SUCCESS` return code (previously they would receive a corrupt
//! handle because the NULL guard was missing).
//!
//! The test is skipped gracefully when MPI_VERSION < 4 (the underlying
//! `MPI_Comm_create_from_group` is not available on older MPI runtimes and
//! the C shim returns `MPI_ERR_OTHER` unconditionally in that case).
//!
//! Run with: mpiexec -n 2 ./target/debug/examples/test_create_from_group_null_handle
use ferrompi::{Error, Mpi, MpiErrorClass, ReduceOp};
// Raw FFI declaration for the C-side shim under test.
//
// We use the Rust API to build the MPI_Group (to keep the test simple), then
// pass its raw handle directly to the C shim to bypass the Rust-side
// Result<Communicator> wrapping and observe the raw return code.
//
// SAFETY invariants for the call below:
// - group_h is a valid ferrompi group handle obtained from the Rust API.
// - stringtag is a valid null-terminated C string on the stack.
// - out_h points to a valid i32 on the stack.
// - On MPI < 4 the shim returns MPI_ERR_OTHER immediately; no group state
// is modified.
#[allow(dead_code)]
extern "C" {
fn ferrompi_comm_create_from_group(
group_h: i32,
stringtag: *const std::ffi::c_char,
out_h: *mut i32,
) -> std::ffi::c_int;
}
fn main() {
let mpi = Mpi::init().expect("MPI init failed");
let world = mpi.world();
let rank = world.rank();
let size = world.size();
assert!(
size == 2,
"test_create_from_group_null_handle requires exactly 2 processes, got {size}"
);
// ========================================================================
// Build a group containing only rank 0. Rank 1 is excluded and will
// receive MPI_COMM_NULL from MPI_Comm_create_from_group.
// ========================================================================
let world_group = world.group().expect("world.group() failed");
let rank0_group = world_group
.include(&[0])
.expect("group.include(&[0]) failed");
let group_h = rank0_group.raw_handle();
let tag = b"test_null_guard\0";
let mut out_h: i32 = -2;
let raw_ret = unsafe {
// SAFETY: see the invariant comment on the extern "C" block above.
ferrompi_comm_create_from_group(
group_h,
tag.as_ptr().cast::<std::ffi::c_char>(),
std::ptr::addr_of_mut!(out_h),
)
};
if rank == 1 {
// Rank 1 is not in the group; MPI_Comm_create_from_group returns
// MPI_COMM_NULL for this rank. The new guard must convert that to a
// non-SUCCESS return code rather than storing MPI_COMM_NULL in the
// handle table.
if raw_ret == 0 {
// raw_ret == MPI_SUCCESS — the guard failed to fire; the
// out_h slot may now hold MPI_COMM_NULL (corrupt state).
//
// On MPI < 4 the shim returns MPI_ERR_OTHER immediately (the
// #else branch), so a SUCCESS here on rank 1 with MPI >= 4
// is definitely a bug.
eprintln!(
"rank {rank}: FAIL: create_from_group returned MPI_SUCCESS for excluded rank — \
MPI_COMM_NULL guard is missing or not compiled in"
);
// Participate in the sentinel barrier before exiting.
let _ = world.allreduce_scalar(0i32, ReduceOp::Min);
std::process::exit(1);
}
let err = Error::from_code(raw_ret);
println!("PASS rank {rank}: create_from_group for excluded rank returned: {err:?}");
} else {
// Rank 0 is in the group; it must either succeed or return MPI_ERR_OTHER
// (on MPI < 4 the shim returns MPI_ERR_OTHER immediately).
if raw_ret != 0 {
let err = Error::from_code(raw_ret);
match &err {
Error::Mpi {
class: MpiErrorClass::Other,
..
} => {
// MPI < 4: the shim returns MPI_ERR_OTHER for the
// unsupported path. Skip gracefully.
println!(
"rank {rank}: SKIP: MPI_Comm_create_from_group not available \
(MPI_ERR_OTHER returned — MPI < 4.0 runtime)"
);
}
_ => {
eprintln!(
"rank {rank}: FAIL: create_from_group for member rank returned \
unexpected error: {err:?}"
);
let _ = world.allreduce_scalar(0i32, ReduceOp::Min);
std::process::exit(1);
}
}
} else {
println!("PASS rank {rank}: create_from_group succeeded for member rank");
}
}
// Sentinel barrier: all ranks must reach this point.
world
.allreduce_scalar(1i32, ReduceOp::Min)
.expect("sentinel allreduce failed");
if rank == 0 {
println!("\n========================================");
println!("All create_from_group_null_handle tests passed!");
println!("========================================");
}
}