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