1use crate::domain::{Domain, LocalMode};
2#[cfg(feature = "rayon")]
3use crate::local::{in_rayon_worker_context, run_in_current_pool, run_in_pool};
4use crate::map::truncate_message;
5#[cfg(feature = "rayon")]
6use crate::mpi_backend;
7use crate::mpi_backend::collective::SystemOperation;
8use crate::mpi_backend::traits::*;
9use crate::mpi_check::mpi_call;
10use crate::scheduler::{BatchId, Coordinator, Dispatch, ItemResult, SchedulerError};
11use crate::wire::{self, ErrorClass, ErrorKey, Header, MessageKind, MessageStatus};
12#[cfg(feature = "rayon")]
13use rayon::Scope;
14use serde::{de::DeserializeOwned, Deserialize, Serialize};
15use std::cell::Cell;
16use std::fmt::{self, Display, Formatter};
17use std::num::NonZeroUsize;
18#[cfg(all(test, not(feature = "rayon")))]
19use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
20#[cfg(feature = "rayon")]
21use std::sync::mpsc::{self, Receiver, SyncSender, TryRecvError};
22
23const HEADER_TAG: i32 = 0;
24const PAYLOAD_TAG: i32 = 1;
25
26#[cfg(all(test, not(feature = "rayon")))]
27static CORRUPT_NEXT_RESULT: AtomicBool = AtomicBool::new(false);
28#[cfg(all(test, not(feature = "rayon")))]
29static FAULT_PHASE: AtomicBool = AtomicBool::new(false);
30#[cfg(all(test, not(feature = "rayon")))]
31static TEST_ASSIGNED_REMOTES: AtomicU64 = AtomicU64::new(0);
32#[cfg(all(test, not(feature = "rayon")))]
33static TEST_USER_RESULTS: AtomicUsize = AtomicUsize::new(0);
34#[cfg(all(test, not(feature = "rayon")))]
35static TEST_DECODE_FAILURES: AtomicUsize = AtomicUsize::new(0);
36
37#[cfg(all(test, not(feature = "rayon")))]
38fn reset_fault_trace() {
39 CORRUPT_NEXT_RESULT.store(false, Ordering::SeqCst);
40 FAULT_PHASE.store(false, Ordering::SeqCst);
41 TEST_ASSIGNED_REMOTES.store(0, Ordering::SeqCst);
42 TEST_USER_RESULTS.store(0, Ordering::SeqCst);
43 TEST_DECODE_FAILURES.store(0, Ordering::SeqCst);
44}
45
46#[cfg(all(test, not(feature = "rayon")))]
47fn wait_for_marker(path: &std::path::Path) {
48 let observed = (0..500).any(|_| {
49 if path.exists() {
50 true
51 } else {
52 std::thread::sleep(std::time::Duration::from_millis(10));
53 false
54 }
55 });
56 assert!(observed, "timed out waiting for fault-test marker");
57}
58
59type LocalError = (PmapErrorKind, String);
60type RootOutcome<U> = (Vec<U>, i64, Option<LocalError>);
61
62#[cfg(feature = "rayon")]
63#[derive(Debug, Copy, Clone, Eq, PartialEq)]
64enum RootEvent {
65 Local,
66 Remote,
67 Neither,
68}
69
70#[cfg(feature = "rayon")]
71fn choose_root_event(
72 local_ready: bool,
73 remote_ready: bool,
74 prefer_local: bool,
75) -> (RootEvent, bool) {
76 match (local_ready, remote_ready) {
77 (true, true) if prefer_local => (RootEvent::Local, false),
78 (true, true) => (RootEvent::Remote, true),
79 (true, false) => (RootEvent::Local, prefer_local),
80 (false, true) => (RootEvent::Remote, prefer_local),
81 (false, false) => (RootEvent::Neither, prefer_local),
82 }
83}
84
85#[derive(Debug, Copy, Clone, Eq, PartialEq)]
87pub struct PmapOptions {
88 pub root: i32,
90 pub batch_size: NonZeroUsize,
92 pub local_mode: LocalMode,
94 pub prefetch: bool,
99}
100
101impl Default for PmapOptions {
102 fn default() -> Self {
103 Self {
104 root: 0,
105 batch_size: NonZeroUsize::new(1).unwrap(),
106 local_mode: LocalMode::Sequential,
107 prefetch: false,
108 }
109 }
110}
111
112#[derive(Debug, Copy, Clone, Eq, PartialEq)]
114pub enum PmapErrorKind {
115 Reentrant,
116 Preflight,
117 User,
118 Wire,
119 Protocol,
120}
121
122#[derive(Debug, Clone, Eq, PartialEq)]
124pub struct PmapError {
125 kind: PmapErrorKind,
126 key: Option<i64>,
127 message: String,
128}
129
130impl PmapError {
131 fn new(kind: PmapErrorKind, message: impl Display) -> Self {
132 Self {
133 kind,
134 key: None,
135 message: truncate_message(message.to_string()),
136 }
137 }
138
139 fn keyed(kind: PmapErrorKind, key: i64, message: impl Display) -> Self {
140 Self {
141 kind,
142 key: Some(key),
143 message: truncate_message(message.to_string()),
144 }
145 }
146
147 pub fn kind(&self) -> PmapErrorKind {
148 self.kind
149 }
150
151 pub fn key(&self) -> Option<i64> {
152 self.key
153 }
154
155 pub fn message(&self) -> &str {
156 &self.message
157 }
158}
159
160impl Display for PmapError {
161 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
162 formatter.write_str(self.message())
163 }
164}
165
166impl std::error::Error for PmapError {}
167
168thread_local! {
169 static ACTIVE: Cell<bool> = const { Cell::new(false) };
170}
171
172pub struct Active;
173
174impl Active {
175 fn acquire() -> Result<Self, PmapError> {
176 ACTIVE.with(|active| {
177 if active.replace(true) {
178 Err(PmapError::new(
179 PmapErrorKind::Reentrant,
180 "pmap is already active on this thread",
181 ))
182 } else {
183 Ok(Self)
184 }
185 })
186 }
187}
188
189impl Drop for Active {
190 fn drop(&mut self) {
191 ACTIVE.with(|active| active.set(false));
192 }
193}
194
195fn wire_error(error: impl Display) -> PmapError {
196 PmapError::new(PmapErrorKind::Wire, error)
197}
198
199fn protocol_error(error: impl Display) -> PmapError {
200 PmapError::new(PmapErrorKind::Protocol, error)
201}
202
203fn scheduler_error(error: SchedulerError) -> PmapError {
204 protocol_error(error)
205}
206
207fn header(
208 kind: MessageKind,
209 status: MessageStatus,
210 batch_id: u64,
211 item_count: u64,
212 payload_len: usize,
213) -> Result<Header, PmapError> {
214 Header::new(kind, status, batch_id, item_count, payload_len as u64).map_err(wire_error)
215}
216
217fn send_header<C: Communicator>(comm: &C, rank: i32, value: Header) {
218 mpi_call!(comm
219 .process_at_rank(rank)
220 .send_with_tag(&value.encode(), HEADER_TAG));
221}
222
223fn receive_header<C: Communicator>(comm: &C, rank: i32) -> Result<Header, PmapError> {
224 let (bytes, _) = mpi_call!(comm
225 .process_at_rank(rank)
226 .receive_vec_with_tag::<u8>(HEADER_TAG));
227 Header::decode(&bytes).map_err(wire_error)
228}
229
230#[cfg(not(feature = "rayon"))]
231fn receive_any_header<C: Communicator>(comm: &C) -> Result<(i32, Header), PmapError> {
232 let (bytes, status) = mpi_call!(comm.any_process().receive_vec_with_tag::<u8>(HEADER_TAG));
233 Header::decode(&bytes)
234 .map(|value| (status.source_rank(), value))
235 .map_err(wire_error)
236}
237
238fn receive_payload<C: Communicator, T: DeserializeOwned>(
239 comm: &C,
240 rank: i32,
241 value: Header,
242) -> Result<T, PmapError> {
243 let count = wire::checked_mpi_count(value.payload_len()).map_err(wire_error)?;
244 let _length = wire::checked_usize_length(value.payload_len()).map_err(wire_error)?;
245 let (bytes, _) = mpi_call!(comm
246 .process_at_rank(rank)
247 .receive_vec_with_tag::<u8>(PAYLOAD_TAG));
248 if bytes.len() != count as usize {
249 return Err(protocol_error("payload length does not match its header"));
250 }
251 wire::decode_payload(&bytes).map_err(wire_error)
252}
253
254fn send_frame<C: Communicator, T: Serialize>(
255 comm: &C,
256 rank: i32,
257 kind: MessageKind,
258 status: MessageStatus,
259 batch_id: u64,
260 item_count: usize,
261 payload: &T,
262) -> Result<(), PmapError> {
263 let bytes = wire::encode_payload(payload).map_err(wire_error)?;
264 #[cfg(all(test, not(feature = "rayon")))]
265 let mut bytes = bytes;
266 #[cfg(all(test, not(feature = "rayon")))]
267 if kind == MessageKind::Result
268 && mpi_call!(comm.rank()) == 1
269 && CORRUPT_NEXT_RESULT.swap(false, Ordering::SeqCst)
270 {
271 bytes = vec![0xff];
272 }
273 let value = header(kind, status, batch_id, item_count as u64, bytes.len())?;
274 send_header(comm, rank, value);
275 mpi_call!(comm
276 .process_at_rank(rank)
277 .send_with_tag(bytes.as_slice(), PAYLOAD_TAG));
278 Ok(())
279}
280
281fn send_empty<C: Communicator>(
282 comm: &C,
283 rank: i32,
284 kind: MessageKind,
285 batch_id: u64,
286) -> Result<(), PmapError> {
287 let value = header(kind, MessageStatus::None, batch_id, 0, 0)?;
288 send_header(comm, rank, value);
289 Ok(())
290}
291
292#[derive(Serialize, Deserialize)]
293struct CallbackFailure {
294 index: usize,
295 kind: i32,
296 message: String,
297}
298
299#[derive(Serialize, Deserialize)]
300enum ResultPayload<U> {
301 Values(Vec<(usize, U)>),
302 Error(CallbackFailure),
303}
304
305#[cfg(feature = "rayon")]
306enum LocalBatchResult<U> {
307 Values(Vec<ItemResult<U>>),
308 Error(CallbackFailure),
309 Panic,
310}
311
312#[cfg(feature = "rayon")]
313struct LocalBatchOutcome<U> {
314 batch_id: BatchId,
315 result: LocalBatchResult<U>,
316}
317
318#[cfg(feature = "rayon")]
319enum WorkerBatchResult<U> {
320 Values(Vec<(usize, U)>),
321 Error {
322 failure: CallbackFailure,
323 class: ErrorClass,
324 kind: PmapErrorKind,
325 },
326 Panic,
327}
328
329#[cfg(feature = "rayon")]
330struct WorkerBatchOutcome<U> {
331 batch_id: u64,
332 item_count: usize,
333 result: WorkerBatchResult<U>,
334}
335
336#[cfg(feature = "rayon")]
337enum PrefetchedBatch<T, U> {
338 Work {
339 batch_id: u64,
340 item_count: usize,
341 items: Vec<(usize, T)>,
342 },
343 Ready(WorkerBatchOutcome<U>),
344 Stop,
345}
346
347#[cfg(feature = "rayon")]
348enum NextWorkerBatch<U> {
349 Running(Receiver<WorkerBatchOutcome<U>>),
350 Ready(WorkerBatchOutcome<U>),
351 Stop,
352}
353
354#[cfg(feature = "rayon")]
355fn spawn_local_batch<'scope, T, U, E, F>(
356 scope: &Scope<'scope>,
357 mode: LocalMode,
358 batch: crate::scheduler::Batch<T>,
359 f: &'scope F,
360 sender: SyncSender<LocalBatchOutcome<U>>,
361) where
362 T: Send + 'scope,
363 U: Send + 'scope,
364 E: Display + Send,
365 F: Fn(T) -> Result<U, E> + Send + Sync,
366{
367 let batch_id = batch.id();
368 let (indices, values): (Vec<_>, Vec<_>) = batch
369 .into_items()
370 .into_iter()
371 .map(|item| (item.original_index(), item.into_value()))
372 .unzip();
373 scope.spawn(move |_| {
374 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
375 run_in_current_pool(mode, values, f)
376 }));
377 let result = match result {
378 Ok(Ok(values)) => LocalBatchResult::Values(
379 indices
380 .into_iter()
381 .zip(values)
382 .map(|(index, value)| ItemResult::new(index, value))
383 .collect(),
384 ),
385 Ok(Err(error)) => LocalBatchResult::Error(CallbackFailure {
386 index: indices[error.index()],
387 kind: PmapErrorKind::User.code(),
388 message: error.message().to_owned(),
389 }),
390 Err(_) => LocalBatchResult::Panic,
391 };
392 let _ = sender.send(LocalBatchOutcome { batch_id, result });
393 });
394}
395
396#[cfg(feature = "rayon")]
397fn spawn_worker_batch<'scope, T, U, E, F>(
398 scope: &Scope<'scope>,
399 mode: LocalMode,
400 batch_id: u64,
401 item_count: usize,
402 items: Vec<(usize, T)>,
403 f: &'scope F,
404 sender: SyncSender<WorkerBatchOutcome<U>>,
405) where
406 T: Send + 'scope,
407 U: Send + 'scope,
408 E: Display + Send,
409 F: Fn(T) -> Result<U, E> + Send + Sync,
410{
411 let (indices, values): (Vec<_>, Vec<_>) = items.into_iter().unzip();
412 scope.spawn(move |_| {
413 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
414 run_in_current_pool(mode, values, f)
415 }));
416 let result = match result {
417 Ok(Ok(values)) => WorkerBatchResult::Values(indices.into_iter().zip(values).collect()),
418 Ok(Err(error)) => {
419 let index = indices[error.index()];
420 WorkerBatchResult::Error {
421 failure: CallbackFailure {
422 index,
423 kind: PmapErrorKind::User.code(),
424 message: error.message().to_owned(),
425 },
426 class: ErrorClass::Callback,
427 kind: PmapErrorKind::User,
428 }
429 }
430 Err(_) => WorkerBatchResult::Panic,
431 };
432 let _ = sender.send(WorkerBatchOutcome {
433 batch_id,
434 item_count,
435 result,
436 });
437 });
438}
439
440#[cfg(feature = "rayon")]
441fn receive_prefetched_batch<C, T, U>(
442 comm: &C,
443 root: i32,
444) -> Result<PrefetchedBatch<T, U>, PmapError>
445where
446 C: Communicator,
447 T: DeserializeOwned,
448{
449 let value = receive_header(comm, root)?;
450 match value.kind() {
451 MessageKind::Task => {
452 let batch_id = value.batch_id();
453 match receive_payload::<_, Vec<(usize, T)>>(comm, root, value) {
454 Ok(items) => Ok(PrefetchedBatch::Work {
455 batch_id,
456 item_count: items.len(),
457 items,
458 }),
459 Err(error) => Ok(PrefetchedBatch::Ready(WorkerBatchOutcome {
460 batch_id,
461 item_count: 1,
462 result: WorkerBatchResult::Error {
463 failure: CallbackFailure {
464 index: usize::try_from(value.item_count()).unwrap_or(usize::MAX),
465 kind: PmapErrorKind::Wire.code(),
466 message: error.message().to_owned(),
467 },
468 class: ErrorClass::WireProtocol,
469 kind: PmapErrorKind::Wire,
470 },
471 })),
472 }
473 }
474 MessageKind::Stop => Ok(PrefetchedBatch::Stop),
475 _ => Err(protocol_error("worker received an unexpected message")),
476 }
477}
478
479#[cfg(feature = "rayon")]
480fn send_worker_outcome<C, U>(
481 comm: &C,
482 root: i32,
483 rank: i32,
484 size: i32,
485 outcome: WorkerBatchOutcome<U>,
486 local_key: &mut i64,
487 local_error: &mut Option<LocalError>,
488) -> Result<(), PmapError>
489where
490 C: Communicator,
491 U: Serialize,
492{
493 match outcome.result {
494 WorkerBatchResult::Values(values) => send_frame(
495 comm,
496 root,
497 MessageKind::Result,
498 MessageStatus::Ok,
499 outcome.batch_id,
500 values.len(),
501 &ResultPayload::Values(values),
502 ),
503 WorkerBatchResult::Error {
504 failure,
505 class,
506 kind,
507 } => {
508 let key = error_key(failure.index, class, rank, size)?;
509 if key < *local_key {
510 *local_key = key;
511 *local_error = Some((kind, failure.message.clone()));
512 }
513 send_frame(
514 comm,
515 root,
516 MessageKind::Result,
517 MessageStatus::Error,
518 outcome.batch_id,
519 outcome.item_count,
520 &ResultPayload::<U>::Error(failure),
521 )
522 }
523 WorkerBatchResult::Panic => mpi_call!(comm.abort(71)),
524 }
525}
526
527#[cfg(feature = "rayon")]
528fn spawn_root_batch<'scope, T, U, E, F>(
529 scope: &Scope<'scope>,
530 scheduler: &mut Coordinator<T, U>,
531 mode: LocalMode,
532 f: &'scope F,
533) -> Result<Option<Receiver<LocalBatchOutcome<U>>>, PmapError>
534where
535 T: Send + 'scope,
536 U: Send + 'scope,
537 E: Display + Send,
538 F: Fn(T) -> Result<U, E> + Send + Sync,
539{
540 let Some(batch) = scheduler.next_root_batch().map_err(scheduler_error)? else {
541 return Ok(None);
542 };
543 let (sender, receiver) = mpsc::sync_channel(1);
544 spawn_local_batch(scope, mode, batch, f, sender);
545 Ok(Some(receiver))
546}
547
548fn error_key(
549 task_key: usize,
550 class: ErrorClass,
551 rank: i32,
552 world_size: i32,
553) -> Result<i64, PmapError> {
554 ErrorKey::new(task_key as u64, class, rank, world_size)
555 .map(|key| key.get())
556 .map_err(wire_error)
557}
558
559fn convergence<C: Communicator>(comm: &C, local_key: i64) -> i64 {
560 let mut selected = local_key;
561 mpi_call!(comm.all_reduce_into(&local_key, &mut selected, SystemOperation::min()));
562 selected
563}
564
565fn broadcast_error<C: Communicator>(
566 comm: &C,
567 selected: i64,
568 local_key: i64,
569 local: Option<(PmapErrorKind, String)>,
570) -> Result<Option<(PmapErrorKind, String)>, PmapError> {
571 if selected == ErrorKey::NO_ERROR_KEY {
572 return Ok(None);
573 }
574 if selected < 0 || mpi_call!(comm.size()) <= 0 {
575 return Err(protocol_error("invalid selected error key"));
576 }
577 let winner = (selected % i64::from(mpi_call!(comm.size()))) as i32;
578 let is_winner = mpi_call!(comm.rank()) == winner;
579 let winner_local = if is_winner && local_key == selected {
580 local
581 } else if is_winner {
582 return Err(protocol_error("winner does not own selected error"));
583 } else {
584 None
585 };
586
587 let mut kind = winner_local
588 .as_ref()
589 .map_or(PmapErrorKind::Protocol.code(), |(kind, _)| kind.code());
590 let mut key = selected;
591 mpi_call!(comm.process_at_rank(winner).broadcast_into(&mut kind));
592 mpi_call!(comm.process_at_rank(winner).broadcast_into(&mut key));
593 if key != selected {
594 return Err(protocol_error("broadcast error key mismatch"));
595 }
596
597 let mut message = winner_local
598 .map(|(_, message)| message.into_bytes())
599 .unwrap_or_default();
600 let mut length = i32::try_from(message.len())
601 .map_err(|_| protocol_error("error message length overflow"))?;
602 mpi_call!(comm.process_at_rank(winner).broadcast_into(&mut length));
603 if length < 0 || length as usize > crate::map::MAX_ERROR_MESSAGE_BYTES {
604 return Err(protocol_error("invalid broadcast error message length"));
605 }
606 if !is_winner {
607 message.resize(length as usize, 0);
608 }
609 mpi_call!(comm
610 .process_at_rank(winner)
611 .broadcast_into(message.as_mut_slice()));
612
613 let kind =
614 PmapErrorKind::from_code(kind).ok_or_else(|| protocol_error("invalid error kind"))?;
615 let message = String::from_utf8(message).map_err(protocol_error)?;
616 Ok(Some((kind, message)))
617}
618
619fn preflight<C, T>(
620 comm: &C,
621 options: &PmapOptions,
622 root_items: Option<&Vec<T>>,
623 execution_valid: bool,
624) -> Result<(), PmapError>
625where
626 C: Communicator,
627{
628 let rank = mpi_call!(comm.rank());
629 let size = mpi_call!(comm.size());
630 let local_batch = options.batch_size.get() as u64;
631 let local_mode = match options.local_mode {
632 LocalMode::Sequential => 0_i32,
633 LocalMode::Outer => 1,
634 LocalMode::Inner => 2,
635 };
636 let local_prefetch = i32::from(options.prefetch);
637
638 let mut min_root = options.root;
639 let mut max_root = options.root;
640 let mut min_batch = local_batch;
641 let mut max_batch = local_batch;
642 let mut min_mode = local_mode;
643 let mut max_mode = local_mode;
644 let mut min_prefetch = local_prefetch;
645 let mut max_prefetch = local_prefetch;
646 mpi_call!(comm.all_reduce_into(&options.root, &mut min_root, SystemOperation::min()));
647 mpi_call!(comm.all_reduce_into(&options.root, &mut max_root, SystemOperation::max()));
648 mpi_call!(comm.all_reduce_into(&local_batch, &mut min_batch, SystemOperation::min()));
649 mpi_call!(comm.all_reduce_into(&local_batch, &mut max_batch, SystemOperation::max()));
650 mpi_call!(comm.all_reduce_into(&local_mode, &mut min_mode, SystemOperation::min()));
651 mpi_call!(comm.all_reduce_into(&local_mode, &mut max_mode, SystemOperation::max()));
652 mpi_call!(comm.all_reduce_into(&local_prefetch, &mut min_prefetch, SystemOperation::min()));
653 mpi_call!(comm.all_reduce_into(&local_prefetch, &mut max_prefetch, SystemOperation::max()));
654
655 let local_valid = mpi_call!(crate::mpi_check::is_thread_main())
656 && options.root >= 0
657 && options.root < size
658 && ((rank == options.root) == root_items.is_some())
659 && execution_valid
660 && min_root == max_root
661 && min_batch == max_batch
662 && min_mode == max_mode
663 && min_prefetch == max_prefetch;
664 let candidate = wire::preflight_candidate(!local_valid, rank, size).map_err(wire_error)?;
665 let mut failing_rank = candidate;
666 mpi_call!(comm.all_reduce_into(&candidate, &mut failing_rank, SystemOperation::min()));
667 if failing_rank != size {
668 return Err(PmapError::new(
669 PmapErrorKind::Preflight,
670 format!("pmap preflight failed on rank {failing_rank}"),
671 ));
672 }
673 Ok(())
674}
675
676#[cfg(not(feature = "rayon"))]
677fn worker_loop<C, T, U, E, F>(
678 comm: &C,
679 root: i32,
680 mut f: F,
681) -> Result<(i64, Option<LocalError>), PmapError>
682where
683 C: Communicator,
684 T: Serialize + DeserializeOwned,
685 U: Serialize + DeserializeOwned,
686 E: Display,
687 F: FnMut(T) -> Result<U, E>,
688{
689 let rank = mpi_call!(comm.rank());
690 let size = mpi_call!(comm.size());
691 let mut local_key = ErrorKey::NO_ERROR_KEY;
692 let mut local_error = None;
693
694 loop {
695 #[cfg(all(test, not(feature = "rayon")))]
696 if FAULT_PHASE.load(Ordering::SeqCst) && rank >= 2 {
697 if let Some(base) = std::env::var_os("HATAORI_FAULT_MARKER") {
698 let assigned = std::path::PathBuf::from(base).with_extension("assigned");
699 wait_for_marker(&assigned);
700 }
701 }
702 send_empty(comm, root, MessageKind::Ready, 0)?;
703 let value = receive_header(comm, root)?;
704 match value.kind() {
705 MessageKind::Task => {
706 let items: Vec<(usize, T)> = match receive_payload(comm, root, value) {
707 Ok(items) => items,
708 Err(error) => {
709 let index = usize::try_from(value.item_count()).unwrap_or(usize::MAX);
710 let key = error_key(index, ErrorClass::WireProtocol, rank, size)?;
711 local_key = local_key.min(key);
712 local_error = Some((PmapErrorKind::Wire, error.message().to_owned()));
713 let failure = CallbackFailure {
714 index,
715 kind: PmapErrorKind::Wire.code(),
716 message: error.message().to_owned(),
717 };
718 send_frame(
719 comm,
720 root,
721 MessageKind::Result,
722 MessageStatus::Error,
723 value.batch_id(),
724 1,
725 &ResultPayload::<U>::Error(failure),
726 )?;
727 continue;
728 }
729 };
730 let mut results = Vec::with_capacity(items.len());
731 let mut failure = None;
732 for (index, item) in items {
733 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(item))) {
734 Ok(Ok(result)) => results.push((index, result)),
735 Ok(Err(error)) => {
736 failure = Some(CallbackFailure {
737 index,
738 kind: PmapErrorKind::User.code(),
739 message: truncate_message(error.to_string()),
740 });
741 break;
742 }
743 Err(_) => mpi_call!(comm.abort(71)),
744 }
745 }
746 match failure {
747 Some(failure) => {
748 let key = error_key(failure.index, ErrorClass::Callback, rank, size)?;
749 local_key = local_key.min(key);
750 local_error = Some((PmapErrorKind::User, failure.message.clone()));
751 send_frame(
752 comm,
753 root,
754 MessageKind::Result,
755 MessageStatus::Error,
756 value.batch_id(),
757 value.item_count() as usize,
758 &ResultPayload::<U>::Error(failure),
759 )?;
760 }
761 None => send_frame(
762 comm,
763 root,
764 MessageKind::Result,
765 MessageStatus::Ok,
766 value.batch_id(),
767 results.len(),
768 &ResultPayload::Values(results),
769 )?,
770 }
771 }
772 MessageKind::Stop => {
773 send_empty(comm, root, MessageKind::Drain, 0)?;
774 return Ok((local_key, local_error));
775 }
776 _ => {
777 return Err(protocol_error("worker received an unexpected message"));
778 }
779 }
780 }
781}
782
783#[cfg(feature = "rayon")]
784fn hybrid_worker_loop_no_prefetch<C, T, U, E, F>(
785 comm: &C,
786 root: i32,
787 mode: LocalMode,
788 pool: &rayon::ThreadPool,
789 f: F,
790) -> Result<(i64, Option<LocalError>), PmapError>
791where
792 C: Communicator,
793 T: Serialize + DeserializeOwned + Send,
794 U: Serialize + DeserializeOwned + Send,
795 E: Display + Send,
796 F: Fn(T) -> Result<U, E> + Send + Sync,
797{
798 let rank = mpi_call!(comm.rank());
799 let size = mpi_call!(comm.size());
800 let mut local_key = ErrorKey::NO_ERROR_KEY;
801 let mut local_error = None;
802
803 loop {
804 send_empty(comm, root, MessageKind::Ready, 0)?;
805 let value = receive_header(comm, root)?;
806 match value.kind() {
807 MessageKind::Task => {
808 let items: Vec<(usize, T)> = match receive_payload(comm, root, value) {
809 Ok(items) => items,
810 Err(error) => {
811 let index = usize::try_from(value.item_count()).unwrap_or(usize::MAX);
812 let key = error_key(index, ErrorClass::WireProtocol, rank, size)?;
813 local_key = local_key.min(key);
814 local_error = Some((PmapErrorKind::Wire, error.message().to_owned()));
815 let failure = CallbackFailure {
816 index,
817 kind: PmapErrorKind::Wire.code(),
818 message: error.message().to_owned(),
819 };
820 send_frame(
821 comm,
822 root,
823 MessageKind::Result,
824 MessageStatus::Error,
825 value.batch_id(),
826 1,
827 &ResultPayload::<U>::Error(failure),
828 )?;
829 continue;
830 }
831 };
832 let (indices, values): (Vec<_>, Vec<_>) = items.into_iter().unzip();
833 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
834 run_in_pool(pool, mode, values, &f)
835 }));
836 let result = match outcome {
837 Ok(Ok(values)) => {
838 ResultPayload::Values(indices.into_iter().zip(values).collect())
839 }
840 Ok(Err(error)) => {
841 let index = indices[error.index()];
842 let failure = CallbackFailure {
843 index,
844 kind: PmapErrorKind::User.code(),
845 message: error.message().to_owned(),
846 };
847 let key = error_key(index, ErrorClass::Callback, rank, size)?;
848 local_key = local_key.min(key);
849 local_error = Some((PmapErrorKind::User, failure.message.clone()));
850 ResultPayload::Error(failure)
851 }
852 Err(_) => mpi_call!(comm.abort(71)),
853 };
854 match result {
855 ResultPayload::Values(values) => {
856 let item_count = values.len();
857 let payload = ResultPayload::Values(values);
858 send_frame(
859 comm,
860 root,
861 MessageKind::Result,
862 MessageStatus::Ok,
863 value.batch_id(),
864 item_count,
865 &payload,
866 )?;
867 }
868 ResultPayload::Error(failure) => {
869 let payload = ResultPayload::<U>::Error(failure);
870 send_frame(
871 comm,
872 root,
873 MessageKind::Result,
874 MessageStatus::Error,
875 value.batch_id(),
876 value.item_count() as usize,
877 &payload,
878 )?;
879 }
880 }
881 }
882 MessageKind::Stop => {
883 send_empty(comm, root, MessageKind::Drain, 0)?;
884 return Ok((local_key, local_error));
885 }
886 _ => return Err(protocol_error("worker received an unexpected message")),
887 }
888 }
889}
890
891#[cfg(feature = "rayon")]
892fn hybrid_worker_loop_prefetch<C, T, U, E, F>(
893 comm: &C,
894 root: i32,
895 mode: LocalMode,
896 pool: &rayon::ThreadPool,
897 f: F,
898) -> Result<(i64, Option<LocalError>), PmapError>
899where
900 C: Communicator,
901 T: Serialize + DeserializeOwned + Send,
902 U: Serialize + DeserializeOwned + Send,
903 E: Display + Send,
904 F: Fn(T) -> Result<U, E> + Send + Sync,
905{
906 let rank = mpi_call!(comm.rank());
907 let size = mpi_call!(comm.size());
908 let mut local_key = ErrorKey::NO_ERROR_KEY;
909 let mut local_error = None;
910
911 pool.in_place_scope(|scope| 'idle: loop {
912 send_empty(comm, root, MessageKind::Ready, 0)?;
913 let first = receive_prefetched_batch::<_, T, U>(comm, root)?;
914 let (batch_id, item_count, items) = match first {
915 PrefetchedBatch::Work {
916 batch_id,
917 item_count,
918 items,
919 } => (batch_id, item_count, items),
920 PrefetchedBatch::Ready(outcome) => {
921 send_worker_outcome(
922 comm,
923 root,
924 rank,
925 size,
926 outcome,
927 &mut local_key,
928 &mut local_error,
929 )?;
930 continue;
931 }
932 PrefetchedBatch::Stop => {
933 send_empty(comm, root, MessageKind::Drain, 0)?;
934 return Ok((local_key, local_error));
935 }
936 };
937
938 let (sender, mut receiver) = mpsc::sync_channel(1);
939 spawn_worker_batch::<T, U, E, F>(scope, mode, batch_id, item_count, items, &f, sender);
940
941 loop {
942 if send_empty(comm, root, MessageKind::Ready, 0).is_err() {
943 mpi_call!(comm.abort(75));
944 }
945 let next = match receive_prefetched_batch::<_, T, U>(comm, root) {
946 Ok(next) => next,
947 Err(_) => mpi_call!(comm.abort(75)),
948 };
949 let outcome = match receiver.recv() {
950 Ok(outcome) => outcome,
951 Err(_) => mpi_call!(comm.abort(71)),
952 };
953
954 let next = match next {
955 PrefetchedBatch::Work {
956 batch_id,
957 item_count,
958 items,
959 } => {
960 let (sender, receiver) = mpsc::sync_channel(1);
961 spawn_worker_batch::<T, U, E, F>(
962 scope, mode, batch_id, item_count, items, &f, sender,
963 );
964 NextWorkerBatch::Running(receiver)
965 }
966 PrefetchedBatch::Ready(outcome) => NextWorkerBatch::Ready(outcome),
967 PrefetchedBatch::Stop => NextWorkerBatch::Stop,
968 };
969
970 if send_worker_outcome(
971 comm,
972 root,
973 rank,
974 size,
975 outcome,
976 &mut local_key,
977 &mut local_error,
978 )
979 .is_err()
980 {
981 mpi_call!(comm.abort(75));
982 }
983
984 match next {
985 NextWorkerBatch::Running(next_receiver) => receiver = next_receiver,
986 NextWorkerBatch::Ready(outcome) => {
987 send_worker_outcome(
988 comm,
989 root,
990 rank,
991 size,
992 outcome,
993 &mut local_key,
994 &mut local_error,
995 )?;
996 continue 'idle;
997 }
998 NextWorkerBatch::Stop => {
999 send_empty(comm, root, MessageKind::Drain, 0)?;
1000 return Ok((local_key, local_error));
1001 }
1002 }
1003 }
1004 })
1005}
1006
1007#[cfg(feature = "rayon")]
1008fn hybrid_worker_loop<C, T, U, E, F>(
1009 comm: &C,
1010 root: i32,
1011 mode: LocalMode,
1012 prefetch: bool,
1013 pool: &rayon::ThreadPool,
1014 f: F,
1015) -> Result<(i64, Option<LocalError>), PmapError>
1016where
1017 C: Communicator,
1018 T: Serialize + DeserializeOwned + Send,
1019 U: Serialize + DeserializeOwned + Send,
1020 E: Display + Send,
1021 F: Fn(T) -> Result<U, E> + Send + Sync,
1022{
1023 if prefetch {
1024 hybrid_worker_loop_prefetch(comm, root, mode, pool, f)
1025 } else {
1026 hybrid_worker_loop_no_prefetch(comm, root, mode, pool, f)
1027 }
1028}
1029
1030#[cfg(not(feature = "rayon"))]
1031fn execute_root_batch<C, T, U, E, F>(
1032 comm: &C,
1033 scheduler: &mut Coordinator<T, U>,
1034 f: &mut F,
1035 rank: i32,
1036 size: i32,
1037 local_key: &mut i64,
1038 local_error: &mut Option<LocalError>,
1039) -> Result<bool, PmapError>
1040where
1041 C: Communicator,
1042 E: Display,
1043 F: FnMut(T) -> Result<U, E>,
1044{
1045 let Some(batch) = scheduler.next_root_batch().map_err(scheduler_error)? else {
1046 return Ok(false);
1047 };
1048 let batch_id = batch.id();
1049 let mut results = Vec::new();
1050 let mut failure = None;
1051 for item in batch.into_items() {
1052 let index = item.original_index();
1053 let callback =
1054 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(item.into_value())));
1055 match callback {
1056 Ok(Ok(result)) => results.push(ItemResult::new(index, result)),
1057 Ok(Err(error)) => {
1058 failure = Some((index, truncate_message(error.to_string())));
1059 break;
1060 }
1061 Err(_) => mpi_call!(comm.abort(70)),
1062 }
1063 }
1064 match failure {
1065 Some((index, message)) => {
1066 let key = error_key(index, ErrorClass::Callback, rank, size)?;
1067 *local_key = (*local_key).min(key);
1068 *local_error = Some((PmapErrorKind::User, message));
1069 scheduler.on_root_error(batch_id).map_err(scheduler_error)?;
1070 }
1071 None => {
1072 scheduler
1073 .on_root_success(batch_id, results)
1074 .map_err(scheduler_error)?;
1075 }
1076 }
1077 Ok(true)
1078}
1079
1080struct RootErrorState<'a> {
1081 call_task_key: usize,
1082 rank: i32,
1083 size: i32,
1084 local_key: &'a mut i64,
1085 local_error: &'a mut Option<LocalError>,
1086}
1087
1088fn process_remote_header<C, T, U>(
1089 comm: &C,
1090 scheduler: &mut Coordinator<T, U>,
1091 source: i32,
1092 value: Header,
1093 errors: &mut RootErrorState<'_>,
1094) -> Result<(), PmapError>
1095where
1096 C: Communicator,
1097 T: Serialize + DeserializeOwned,
1098 U: Serialize + DeserializeOwned,
1099{
1100 let call_task_key = errors.call_task_key;
1101 let rank = errors.rank;
1102 let size = errors.size;
1103 let local_key = &mut *errors.local_key;
1104 let local_error = &mut *errors.local_error;
1105 match value.kind() {
1106 MessageKind::Ready => match scheduler.on_remote_ready(source).map_err(scheduler_error)? {
1107 Dispatch::Task(batch) => {
1108 #[cfg(all(test, not(feature = "rayon")))]
1109 if (0..64).contains(&source) {
1110 TEST_ASSIGNED_REMOTES.fetch_or(1_u64 << source, Ordering::SeqCst);
1111 }
1112 let batch_id = batch.id();
1113 let items: Vec<(usize, T)> = batch
1114 .into_items()
1115 .into_iter()
1116 .map(|item| (item.original_index(), item.into_value()))
1117 .collect();
1118 send_frame(
1119 comm,
1120 source,
1121 MessageKind::Task,
1122 MessageStatus::None,
1123 batch_id.get(),
1124 items.len(),
1125 &items,
1126 )?;
1127 }
1128 Dispatch::Stop => send_empty(comm, source, MessageKind::Stop, 0)?,
1129 },
1130 MessageKind::Result => {
1131 let payload: ResultPayload<U> = match receive_payload(comm, source, value) {
1132 Ok(payload) => payload,
1133 Err(error) => {
1134 #[cfg(all(test, not(feature = "rayon")))]
1136 TEST_DECODE_FAILURES.fetch_add(1, Ordering::SeqCst);
1137 let key = error_key(call_task_key, ErrorClass::WireProtocol, rank, size)?;
1138 *local_key = (*local_key).min(key);
1139 *local_error = Some((error.kind(), error.message().to_owned()));
1140 scheduler
1141 .on_remote_protocol_error(source)
1142 .map_err(scheduler_error)?;
1143 return Ok(());
1144 }
1145 };
1146 match (value.status(), payload) {
1147 (MessageStatus::Ok, ResultPayload::Values(values)) => {
1148 let results = values
1149 .into_iter()
1150 .map(|(index, result)| ItemResult::new(index, result))
1151 .collect();
1152 scheduler
1153 .on_remote_success(source, BatchId::from_raw(value.batch_id()), results)
1154 .map_err(scheduler_error)?;
1155 }
1156 (MessageStatus::Error, ResultPayload::Error(_failure)) => {
1157 #[cfg(all(test, not(feature = "rayon")))]
1158 TEST_USER_RESULTS.fetch_add(1, Ordering::SeqCst);
1159 scheduler
1160 .on_remote_error(source, BatchId::from_raw(value.batch_id()))
1161 .map_err(scheduler_error)?;
1162 }
1163 _ => {
1164 let key = error_key(call_task_key, ErrorClass::WireProtocol, rank, size)?;
1165 *local_key = (*local_key).min(key);
1166 *local_error =
1167 Some((PmapErrorKind::Protocol, "invalid result status".to_owned()));
1168 scheduler
1169 .on_remote_protocol_error(source)
1170 .map_err(scheduler_error)?;
1171 }
1172 }
1173 }
1174 MessageKind::Drain => scheduler.on_remote_drain(source).map_err(scheduler_error)?,
1175 _ => {
1176 let key = error_key(call_task_key, ErrorClass::WireProtocol, rank, size)?;
1177 *local_key = (*local_key).min(key);
1178 *local_error = Some((
1179 PmapErrorKind::Protocol,
1180 "unexpected message from worker".to_owned(),
1181 ));
1182 scheduler
1183 .on_remote_protocol_error(source)
1184 .map_err(scheduler_error)?;
1185 }
1186 }
1187 Ok(())
1188}
1189
1190#[cfg(not(feature = "rayon"))]
1191fn root_loop<C, T, U, E, F>(
1192 comm: &C,
1193 options: &PmapOptions,
1194 root_items: Vec<T>,
1195 mut f: F,
1196) -> Result<RootOutcome<U>, PmapError>
1197where
1198 C: Communicator,
1199 T: Serialize + DeserializeOwned,
1200 U: Serialize + DeserializeOwned,
1201 E: Display,
1202 F: FnMut(T) -> Result<U, E>,
1203{
1204 let root = options.root;
1205 let rank = mpi_call!(comm.rank());
1206 let size = mpi_call!(comm.size());
1207 let remotes: Vec<i32> = (0..size).filter(|candidate| *candidate != root).collect();
1208 let call_task_key = root_items.len();
1209 let mut scheduler = Coordinator::<T, U>::new_with_prefetch(
1210 root,
1211 remotes,
1212 root_items,
1213 options.batch_size,
1214 options.prefetch,
1215 )
1216 .map_err(scheduler_error)?;
1217 let mut local_key = ErrorKey::NO_ERROR_KEY;
1218 let mut local_error = None;
1219
1220 loop {
1221 if size == 1 {
1222 if !execute_root_batch(
1223 comm,
1224 &mut scheduler,
1225 &mut f,
1226 rank,
1227 size,
1228 &mut local_key,
1229 &mut local_error,
1230 )? {
1231 break;
1232 }
1233 continue;
1234 }
1235
1236 if scheduler.is_finished() && scheduler.is_quiescent() {
1237 break;
1238 }
1239
1240 let mut served = 0usize;
1250 while mpi_call!(comm.any_process().immediate_probe_with_tag(HEADER_TAG)).is_some() {
1251 let (source, value) = receive_any_header(comm)?;
1252 process_remote_header(
1253 comm,
1254 &mut scheduler,
1255 source,
1256 value,
1257 &mut RootErrorState {
1258 call_task_key,
1259 rank,
1260 size,
1261 local_key: &mut local_key,
1262 local_error: &mut local_error,
1263 },
1264 )?;
1265 served += 1;
1266 }
1267 if scheduler.is_finished() {
1271 continue;
1272 }
1273 if served == 0 {
1274 let _ = execute_root_batch(
1275 comm,
1276 &mut scheduler,
1277 &mut f,
1278 rank,
1279 size,
1280 &mut local_key,
1281 &mut local_error,
1282 )?;
1283 if scheduler.is_finished() {
1288 continue;
1289 }
1290 let (source, value) = receive_any_header(comm)?;
1291 process_remote_header(
1292 comm,
1293 &mut scheduler,
1294 source,
1295 value,
1296 &mut RootErrorState {
1297 call_task_key,
1298 rank,
1299 size,
1300 local_key: &mut local_key,
1301 local_error: &mut local_error,
1302 },
1303 )?;
1304 continue;
1305 }
1306 }
1307
1308 let results = if scheduler.failed() {
1309 Vec::new()
1310 } else {
1311 scheduler.into_results().map_err(scheduler_error)?
1312 };
1313 Ok((results, local_key, local_error))
1314}
1315
1316#[cfg(feature = "rayon")]
1317fn hybrid_root_loop<C, T, U, E, F>(
1318 comm: &C,
1319 options: &PmapOptions,
1320 root_items: Vec<T>,
1321 pool: &rayon::ThreadPool,
1322 f: &F,
1323) -> Result<RootOutcome<U>, PmapError>
1324where
1325 C: Communicator,
1326 T: Serialize + DeserializeOwned + Send,
1327 U: Serialize + DeserializeOwned + Send,
1328 E: Display + Send,
1329 F: Fn(T) -> Result<U, E> + Send + Sync,
1330{
1331 let root = options.root;
1332 let rank = mpi_call!(comm.rank());
1333 let size = mpi_call!(comm.size());
1334 let remotes: Vec<i32> = (0..size).filter(|candidate| *candidate != root).collect();
1335 let call_task_key = root_items.len();
1336 let mut scheduler = Coordinator::<T, U>::new_with_prefetch(
1337 root,
1338 remotes,
1339 root_items,
1340 options.batch_size,
1341 options.prefetch,
1342 )
1343 .map_err(scheduler_error)?;
1344 let mut local_key = ErrorKey::NO_ERROR_KEY;
1345 let mut local_error = None;
1346
1347 pool.in_place_scope(|scope| {
1348 let mut receiver: Option<Receiver<LocalBatchOutcome<U>>> = None;
1349 let mut pending_local: Option<LocalBatchOutcome<U>> = None;
1350 let mut prefer_local = true;
1351
1352 loop {
1353 if receiver.is_none() {
1354 receiver =
1355 spawn_root_batch::<T, U, E, F>(scope, &mut scheduler, options.local_mode, f)?;
1356 }
1357 if receiver.is_none() && scheduler.is_finished() && scheduler.is_quiescent() {
1358 break;
1359 }
1360
1361 let mut local_disconnected = false;
1362 if pending_local.is_none() {
1363 if let Some(local) = receiver.as_ref() {
1364 match local.try_recv() {
1365 Ok(outcome) => pending_local = Some(outcome),
1366 Err(TryRecvError::Empty) => {}
1367 Err(TryRecvError::Disconnected) => local_disconnected = true,
1368 }
1369 }
1370 }
1371 let remote_source = if size > 1 {
1372 mpi_call!(comm.any_process().immediate_probe_with_tag(HEADER_TAG))
1373 .map(|status| status.source_rank())
1374 } else {
1375 None
1376 };
1377 let local_ready = pending_local.is_some() || local_disconnected;
1378 let (event, next_preference) =
1379 choose_root_event(local_ready, remote_source.is_some(), prefer_local);
1380 prefer_local = next_preference;
1381
1382 match event {
1383 RootEvent::Local => {
1384 if local_disconnected {
1385 mpi_call!(comm.abort(70));
1386 }
1387 let outcome = pending_local
1388 .take()
1389 .ok_or_else(|| protocol_error("missing root-local outcome"))?;
1390 receiver = None;
1391 match outcome.result {
1392 LocalBatchResult::Values(values) => {
1393 scheduler
1394 .on_root_success(outcome.batch_id, values)
1395 .map_err(scheduler_error)?;
1396 }
1397 LocalBatchResult::Error(failure) => {
1398 let key = error_key(failure.index, ErrorClass::Callback, rank, size)?;
1399 local_key = local_key.min(key);
1400 local_error = Some((PmapErrorKind::User, failure.message.clone()));
1401 scheduler
1402 .on_root_error(outcome.batch_id)
1403 .map_err(scheduler_error)?;
1404 }
1405 LocalBatchResult::Panic => mpi_call!(comm.abort(70)),
1406 }
1407 }
1408 RootEvent::Remote => {
1409 let source = remote_source
1410 .ok_or_else(|| protocol_error("missing probed remote source"))?;
1411 let value = receive_header(comm, source)?;
1412 process_remote_header(
1413 comm,
1414 &mut scheduler,
1415 source,
1416 value,
1417 &mut RootErrorState {
1418 call_task_key,
1419 rank,
1420 size,
1421 local_key: &mut local_key,
1422 local_error: &mut local_error,
1423 },
1424 )?;
1425 }
1426 RootEvent::Neither => std::thread::yield_now(),
1427 }
1428 }
1429
1430 let results = if scheduler.failed() {
1431 Vec::new()
1432 } else {
1433 scheduler.into_results().map_err(scheduler_error)?
1434 };
1435 Ok((results, local_key, local_error))
1436 })
1437}
1438
1439impl PmapErrorKind {
1440 fn code(self) -> i32 {
1441 match self {
1442 Self::Reentrant => 0,
1443 Self::Preflight => 1,
1444 Self::User => 2,
1445 Self::Wire => 3,
1446 Self::Protocol => 4,
1447 }
1448 }
1449
1450 fn from_code(code: i32) -> Option<Self> {
1451 Some(match code {
1452 0 => Self::Reentrant,
1453 1 => Self::Preflight,
1454 2 => Self::User,
1455 3 => Self::Wire,
1456 4 => Self::Protocol,
1457 _ => return None,
1458 })
1459 }
1460}
1461
1462#[cfg(not(feature = "rayon"))]
1474pub fn pmap<C, T, U, E, F>(
1475 world: &C,
1476 domain: &Domain,
1477 options: PmapOptions,
1478 root_items: Option<Vec<T>>,
1479 f: F,
1480) -> Result<Option<Vec<U>>, PmapError>
1481where
1482 C: Communicator,
1483 T: Serialize + DeserializeOwned,
1484 U: Serialize + DeserializeOwned,
1485 E: Display,
1486 F: FnMut(T) -> Result<U, E>,
1487{
1488 let _active = Active::acquire()?;
1489 let admission = domain.try_admit();
1490 let comm = mpi_call!(world.duplicate());
1491 preflight(
1492 &comm,
1493 &options,
1494 root_items.as_ref(),
1495 admission.is_ok()
1496 && domain.id().get() == 0
1497 && domain.worker_count() == 1
1498 && options.local_mode == LocalMode::Sequential
1499 && !options.prefetch,
1500 )?;
1501 let _admission = admission.expect("successful preflight guarantees domain admission");
1502
1503 let rank = mpi_call!(comm.rank());
1504 let outcome = if rank == options.root {
1505 root_loop(&comm, &options, root_items.unwrap(), f)
1506 } else {
1507 worker_loop(&comm, options.root, f).map(|(key, error)| (Vec::new(), key, error))
1508 };
1509 let (results, local_key, local_error) = match outcome {
1510 Ok(outcome) => outcome,
1511 Err(_) => mpi_call!(comm.abort(72)),
1512 };
1513
1514 let selected = convergence(&comm, local_key);
1515 let error = match broadcast_error(&comm, selected, local_key, local_error) {
1516 Ok(error) => error,
1517 Err(_) => mpi_call!(comm.abort(73)),
1518 };
1519 match error {
1520 Some((kind, message)) => Err(PmapError::keyed(kind, selected, message)),
1521 None if rank == options.root => Ok(Some(results)),
1522 None => Ok(None),
1523 }
1524}
1525
1526#[cfg(feature = "rayon")]
1540pub fn pmap<C, T, U, E, F>(
1541 world: &C,
1542 domain: &Domain,
1543 options: PmapOptions,
1544 root_items: Option<Vec<T>>,
1545 f: F,
1546) -> Result<Option<Vec<U>>, PmapError>
1547where
1548 C: Communicator,
1549 T: Serialize + DeserializeOwned + Send,
1550 U: Serialize + DeserializeOwned + Send,
1551 E: Display + Send,
1552 F: Fn(T) -> Result<U, E> + Send + Sync,
1553{
1554 if in_rayon_worker_context() {
1555 return Err(PmapError::new(
1556 PmapErrorKind::Preflight,
1557 "hybrid pmap cannot enter from a Rayon worker",
1558 ));
1559 }
1560 let _active = Active::acquire()?;
1561 let admission = domain.try_admit();
1562 let pool = domain.rayon_pool().cloned();
1563 let execution_valid = admission.is_ok()
1564 && pool.is_some()
1565 && domain.id().get() == 0
1566 && rayon::current_thread_index().is_none()
1567 && mpi_call!(mpi_backend::environment::threading_support())
1568 >= mpi_backend::environment::Threading::Funneled;
1569 let comm = mpi_call!(world.duplicate());
1570 preflight(&comm, &options, root_items.as_ref(), execution_valid)?;
1571 let _admission = admission.expect("successful preflight guarantees domain admission");
1572 let pool = pool.expect("successful preflight guarantees a Rayon pool");
1573
1574 let rank = mpi_call!(comm.rank());
1575 let outcome = if rank == options.root {
1576 hybrid_root_loop::<_, T, U, E, F>(
1577 &comm,
1578 &options,
1579 root_items.expect("successful preflight guarantees root input"),
1580 pool.as_ref(),
1581 &f,
1582 )
1583 } else {
1584 hybrid_worker_loop(
1585 &comm,
1586 options.root,
1587 options.local_mode,
1588 options.prefetch,
1589 pool.as_ref(),
1590 f,
1591 )
1592 .map(|(key, error)| (Vec::new(), key, error))
1593 };
1594 let (results, local_key, local_error) = match outcome {
1595 Ok(outcome) => outcome,
1596 Err(_) => mpi_call!(comm.abort(72)),
1597 };
1598
1599 let selected = convergence(&comm, local_key);
1600 let error = match broadcast_error(&comm, selected, local_key, local_error) {
1601 Ok(error) => error,
1602 Err(_) => mpi_call!(comm.abort(73)),
1603 };
1604 match error {
1605 Some((kind, message)) => Err(PmapError::keyed(kind, selected, message)),
1606 None if rank == options.root => Ok(Some(results)),
1607 None => Ok(None),
1608 }
1609}
1610
1611#[cfg(all(test, not(feature = "rayon")))]
1612mod mpi_fault_tests {
1613 use super::*;
1614 use serde::{Deserialize, Serialize, Serializer};
1615 use std::path::PathBuf;
1616
1617 #[derive(Debug, Deserialize)]
1618 struct NoCodec(i32);
1619
1620 impl Serialize for NoCodec {
1621 fn serialize<S: Serializer>(&self, _serializer: S) -> Result<S::Ok, S::Error> {
1622 Err(serde::ser::Error::custom(
1623 "codec must not run at world size one",
1624 ))
1625 }
1626 }
1627
1628 #[test]
1629 fn size_one_root_paths_bypass_codec() {
1630 if std::env::var_os("HATAORI_CODEC_TEST").is_none() {
1631 return;
1632 }
1633 let universe =
1634 crate::mpi_backend::initialize().expect("codec test owns MPI initialization");
1635 let world = universe.world();
1636 assert_eq!(mpi_call!(world.size()), 1);
1637 crate::wire::reset_codec_counts();
1638
1639 let domain = Domain::sequential();
1640 let mapped = pmap(
1641 &world,
1642 &domain,
1643 PmapOptions::default(),
1644 Some(vec![NoCodec(1)]),
1645 |value| Ok::<_, String>(NoCodec(value.0 + 1)),
1646 )
1647 .unwrap()
1648 .unwrap();
1649 assert_eq!(mapped[0].0, 2);
1650 assert_eq!(crate::broadcast(&world, 0, Some(NoCodec(3))).unwrap().0, 3);
1651 assert_eq!(
1652 crate::scatter(&world, 0, Some(vec![NoCodec(4)])).unwrap().0,
1653 4
1654 );
1655 assert_eq!(
1656 crate::gather(&world, 0, NoCodec(5)).unwrap().unwrap()[0].0,
1657 5
1658 );
1659 assert_eq!(crate::wire::codec_counts(), (0, 0));
1660 }
1661
1662 #[test]
1663 fn simultaneous_user_and_decode_faults_drain_and_reuse() {
1664 let Some(base) = std::env::var_os("HATAORI_FAULT_MARKER") else {
1665 return;
1666 };
1667 let universe =
1668 crate::mpi_backend::initialize().expect("fault test owns MPI initialization");
1669 let world = universe.world();
1670 let rank = mpi_call!(world.rank());
1671 let size = mpi_call!(world.size());
1672 assert!(size >= 4, "fault test requires at least four ranks");
1673 let base = PathBuf::from(base);
1674 let assigned = base.with_extension("assigned");
1675 let errored = base.with_extension("error");
1676 if rank == 0 {
1677 let _ = std::fs::remove_file(&assigned);
1678 let _ = std::fs::remove_file(&errored);
1679 }
1680 mpi_call!(world.barrier());
1681
1682 reset_fault_trace();
1683 let domain = Domain::sequential();
1684 let options = PmapOptions::default();
1685 let control = pmap(
1686 &world,
1687 &domain,
1688 options,
1689 (rank == 0).then(|| (0..12).collect::<Vec<i32>>()),
1690 |item| Ok::<_, String>(item * 2),
1691 )
1692 .unwrap();
1693 if rank == 0 {
1694 assert_eq!(
1695 control.unwrap(),
1696 (0..12).map(|item| item * 2).collect::<Vec<_>>()
1697 );
1698 } else {
1699 assert!(control.is_none());
1700 }
1701
1702 reset_fault_trace();
1703 FAULT_PHASE.store(true, Ordering::SeqCst);
1704 if rank == 1 {
1705 CORRUPT_NEXT_RESULT.store(true, Ordering::SeqCst);
1706 }
1707 mpi_call!(world.barrier());
1708 let assigned_for_callback = assigned.clone();
1709 let error_for_callback = errored.clone();
1710 let failure = pmap(
1711 &world,
1712 &domain,
1713 options,
1714 (rank == 0).then(|| (0..32).collect::<Vec<i32>>()),
1715 move |item| {
1716 match rank {
1717 0 => std::thread::sleep(std::time::Duration::from_millis(100)),
1718 1 => {
1719 std::fs::write(&assigned_for_callback, b"rank1-running").unwrap();
1720 wait_for_marker(&error_for_callback);
1721 }
1722 2 => {
1723 std::fs::write(&error_for_callback, b"rank2-error").unwrap();
1724 return Err::<i32, _>("simultaneous user failure".to_owned());
1725 }
1726 _ => {}
1727 }
1728 Ok(item)
1729 },
1730 )
1731 .unwrap_err();
1732 assert_eq!(failure.kind(), PmapErrorKind::User);
1733 assert_eq!(failure.message(), "simultaneous user failure");
1734 assert_eq!(failure.key().unwrap() % i64::from(size), 2);
1735 if rank == 0 {
1736 let assigned_mask = TEST_ASSIGNED_REMOTES.load(Ordering::SeqCst);
1737 assert_ne!(assigned_mask & (1_u64 << 1), 0);
1738 assert_ne!(assigned_mask & (1_u64 << 2), 0);
1739 assert_eq!(TEST_USER_RESULTS.load(Ordering::SeqCst), 1);
1740 assert_eq!(TEST_DECODE_FAILURES.load(Ordering::SeqCst), 1);
1741 }
1742
1743 FAULT_PHASE.store(false, Ordering::SeqCst);
1744 mpi_call!(world.barrier());
1745 if rank == 0 {
1746 mpi_call!(world.process_at_rank(1).send_with_tag(&[77_u8], 77));
1747 } else if rank == 1 {
1748 let (message, _) = mpi_call!(world.process_at_rank(0).receive_vec_with_tag::<u8>(77));
1749 assert_eq!(message, vec![77]);
1750 }
1751 mpi_call!(world.barrier());
1752
1753 let reused = pmap(
1754 &world,
1755 &domain,
1756 options,
1757 (rank == 0).then(|| vec![3_i32, 1, 4]),
1758 |item| Ok::<_, String>(item + 1),
1759 )
1760 .unwrap();
1761 if rank == 0 {
1762 assert_eq!(reused.unwrap(), vec![4, 2, 5]);
1763 std::fs::remove_file(&assigned).unwrap();
1764 std::fs::remove_file(&errored).unwrap();
1765 }
1766 mpi_call!(world.barrier());
1767 }
1768}
1769
1770#[cfg(all(test, feature = "rayon"))]
1771mod tests {
1772 #[test]
1773 fn prefetch_is_opt_in() {
1774 assert!(!super::PmapOptions::default().prefetch);
1775 }
1776
1777 #[test]
1778 fn root_event_chooser_alternates_when_both_sources_are_ready() {
1779 let mut prefer_local = true;
1780 let mut events = Vec::new();
1781 for _ in 0..4 {
1782 let (event, next) = super::choose_root_event(true, true, prefer_local);
1783 events.push(event);
1784 prefer_local = next;
1785 }
1786 assert_eq!(
1787 events,
1788 [
1789 super::RootEvent::Local,
1790 super::RootEvent::Remote,
1791 super::RootEvent::Local,
1792 super::RootEvent::Remote
1793 ]
1794 );
1795 }
1796
1797 #[test]
1798 fn disconnected_local_sender_is_selected_for_abort() {
1799 let (sender, receiver) = std::sync::mpsc::sync_channel::<()>(1);
1800 drop(sender);
1801 assert!(matches!(
1802 receiver.try_recv(),
1803 Err(std::sync::mpsc::TryRecvError::Disconnected)
1804 ));
1805 let (event, _) = super::choose_root_event(true, false, false);
1806 assert_eq!(event, super::RootEvent::Local);
1807 }
1808
1809 #[test]
1810 fn root_event_chooser_keeps_one_sided_progress() {
1811 let (local, turn) = super::choose_root_event(true, false, true);
1812 assert_eq!(local, super::RootEvent::Local);
1813 let (remote, _) = super::choose_root_event(false, true, turn);
1814 assert_eq!(remote, super::RootEvent::Remote);
1815 }
1816}