Skip to main content

mm1_node/runtime/context/
impl_context_api.rs

1use std::collections::{BTreeMap, HashMap, HashSet};
2use std::time::Duration;
3
4use futures::FutureExt;
5use mm1_address::address::Address;
6use mm1_address::address_range::AddressRange;
7use mm1_address::pool::Lease;
8use mm1_address::subnet::{NetAddress, NetMask};
9use mm1_common::errors::chain::{ExactTypeDisplayChainExt, StdErrorDisplayChainExt};
10use mm1_common::errors::error_of::ErrorOf;
11use mm1_common::futures::timeout::FutureTimeoutExt;
12use mm1_common::log;
13use mm1_common::types::{AnyError, Never};
14use mm1_core::context::{
15    Bind, BindArgs, BindErrorKind, Fork, ForkErrorKind, InitDone, Linking, Messaging, Now, Ping,
16    PingErrorKind, Quit, RecvErrorKind, SendErrorKind, Start, Stop, Watching,
17};
18use mm1_core::envelope::{Envelope, EnvelopeHeader, dispatch};
19use mm1_core::tap;
20use mm1_core::tracing::{TraceId, WithTraceIdExt};
21use mm1_proto_system as sys;
22use mm1_runnable::local::BoxedRunnable;
23use rand::RngCore;
24use tokio::sync::oneshot;
25use tokio::time::Instant;
26use tracing::{trace, warn};
27
28use crate::config::EffectiveActorConfig;
29use crate::registry::{self, MessageWithPermit, MessageWithoutPermit};
30use crate::runtime::container;
31use crate::runtime::context::{ActorContext, ForkEntry, SubnetContext};
32use crate::runtime::rt_api::RtApi;
33use crate::runtime::sys_call::SysCall;
34use crate::runtime::sys_msg::{ExitReason, SysLink, SysMsg};
35
36impl Quit for ActorContext {
37    async fn quit_ok(&mut self) -> Never {
38        self.call.invoke(SysCall::Exit(Ok(()))).await;
39        std::future::pending().await
40    }
41
42    async fn quit_err<E>(&mut self, reason: E) -> Never
43    where
44        E: std::error::Error + Send + Sync + 'static,
45    {
46        self.call.invoke(SysCall::Exit(Err(reason.into()))).await;
47        std::future::pending().await
48    }
49}
50
51impl Fork for ActorContext {
52    async fn fork(&mut self) -> Result<Self, ErrorOf<ForkErrorKind>> {
53        let Self {
54            fork_address: this_address,
55            call,
56            subnet_context,
57            ..
58        } = self;
59
60        let fork_lease = {
61            let mut subnet_context_locked = subnet_context
62                .try_lock()
63                .expect("could not lock subnet_context");
64            let SubnetContext {
65                subnet_pool,
66                fork_entries,
67                ..
68            } = &mut *subnet_context_locked;
69            let fork_lease = subnet_pool.lease(NetMask::MAX).map_err(|lease_error| {
70                ErrorOf::new(ForkErrorKind::ResourceConstraint, lease_error.to_string())
71            })?;
72            let should_be_none = fork_entries.insert(fork_lease.address, Default::default());
73            assert!(should_be_none.is_none());
74            fork_lease
75        };
76        let fork_address = fork_lease.address;
77        trace!(parent = %this_address, child = %fork_address, "forking");
78
79        // Build the fork context now, before the await below. If this future is
80        // dropped at the `ForkAdded` await (e.g. a caller wraps `fork()` in a
81        // timeout or `select!`), the context's `Drop` runs: it undoes the
82        // fork-entry insert and hands the lease back via `ForkDone`, so a
83        // cancelled fork leaks neither the fork entry nor the container's job
84        // entry (#132).
85        let context = Self {
86            subnet_context: subnet_context.clone(),
87            fork_address,
88            fork_lease: Some(fork_lease),
89            ack_to: None,
90            call: call.clone(),
91        };
92
93        call.invoke(SysCall::ForkAdded(fork_address)).await;
94
95        Ok(context)
96    }
97
98    async fn run<F, Fut>(self, fun: F)
99    where
100        F: FnOnce(Self) -> Fut,
101        F: Send + 'static,
102        Fut: Future + Send + 'static,
103    {
104        let call = self.call.clone();
105        let fut = fun(self)
106            .map(|_| ())
107            .with_trace_id(TraceId::current())
108            .boxed();
109        call.invoke(SysCall::Spawn(fut)).await;
110    }
111}
112
113impl Now for ActorContext {
114    type Instant = Instant;
115
116    fn now(&self) -> Self::Instant {
117        Instant::now()
118    }
119}
120
121impl Start<BoxedRunnable<Self>> for ActorContext {
122    fn spawn(
123        &mut self,
124        runnable: BoxedRunnable<Self>,
125        link: bool,
126    ) -> impl Future<Output = Result<Address, ErrorOf<sys::SpawnErrorKind>>> + Send {
127        do_spawn(self, runnable, None, link.then_some(self.fork_address))
128    }
129
130    fn start(
131        &mut self,
132        runnable: BoxedRunnable<Self>,
133        link: bool,
134        start_timeout: Duration,
135    ) -> impl Future<Output = Result<Address, ErrorOf<sys::StartErrorKind>>> + Send {
136        do_start(self, runnable, link, start_timeout)
137    }
138}
139
140impl Stop for ActorContext {
141    fn exit(&mut self, peer: Address) -> impl Future<Output = bool> + Send {
142        let this = self.fork_address;
143        let out = do_exit(self, this, peer).is_ok();
144        std::future::ready(out)
145    }
146
147    fn kill(&mut self, peer: Address) -> impl Future<Output = bool> + Send {
148        let out = do_kill(self, peer).is_ok();
149        std::future::ready(out)
150    }
151}
152
153impl Linking for ActorContext {
154    fn link(&mut self, peer: Address) -> impl Future<Output = ()> + Send {
155        let this = self.fork_address;
156        do_link(self, this, peer)
157    }
158
159    fn unlink(&mut self, peer: Address) -> impl Future<Output = ()> + Send {
160        let this = self.fork_address;
161        do_unlink(self, this, peer)
162    }
163
164    fn set_trap_exit(&mut self, enable: bool) -> impl Future<Output = ()> + Send {
165        do_set_trap_exit(self, enable)
166    }
167}
168
169impl Watching for ActorContext {
170    fn watch(&mut self, peer: Address) -> impl Future<Output = mm1_proto_system::WatchRef> + Send {
171        do_watch(self, peer)
172    }
173
174    fn unwatch(
175        &mut self,
176        watch_ref: mm1_proto_system::WatchRef,
177    ) -> impl Future<Output = ()> + Send {
178        do_unwatch(self, watch_ref)
179    }
180}
181
182impl InitDone for ActorContext {
183    fn init_done(&mut self, address: Address) -> impl Future<Output = ()> + Send {
184        do_init_done(self, address)
185    }
186}
187
188impl Messaging for ActorContext {
189    fn address(&self) -> Address {
190        self.fork_address
191    }
192
193    async fn recv(&mut self) -> Result<Envelope, ErrorOf<RecvErrorKind>> {
194        let ActorContext {
195            fork_address,
196            subnet_context,
197            ..
198        } = self;
199
200        loop {
201            let (inbound_envelope_opt, subnet_notify, fork_notify) = {
202                let mut subnet_context_locked = subnet_context
203                    .try_lock()
204                    .expect("could not lock subnet_context");
205                let SubnetContext {
206                    rt_api,
207                    actor_key,
208                    subnet_address,
209                    subnet_notify,
210                    rx_priority,
211                    rx_regular,
212                    fork_entries,
213                    bound_subnets,
214                    message_tap,
215                    ..
216                } = &mut *subnet_context_locked;
217
218                process_inlets(rt_api, bound_subnets, rx_priority, rx_regular, fork_entries)?;
219
220                let this_fork_entry = fork_entries
221                    .get_mut(fork_address)
222                    .unwrap_or_else(|| panic!("no fork-entry for {}", fork_address));
223                let inbound_envelope_opt = if let Some(priority_message) =
224                    this_fork_entry.inbox_priority.pop_front()
225                {
226                    Some(priority_message.message)
227                } else if let Some(regular_message) = this_fork_entry.inbox_regular.pop_front() {
228                    Some(regular_message.message)
229                } else {
230                    None
231                };
232                if let Some(envelope) = inbound_envelope_opt.as_ref() {
233                    message_tap.on_recv(tap::OnRecv {
234                        recv_by_addr: *fork_address,
235                        recv_by_net: *subnet_address,
236                        recv_by_key: actor_key,
237                        envelope,
238                    });
239                }
240                (
241                    inbound_envelope_opt,
242                    subnet_notify.clone(),
243                    this_fork_entry.fork_notifiy.clone(),
244                )
245            };
246
247            if let Some(inbound_envelope) = inbound_envelope_opt {
248                break Ok(inbound_envelope)
249            } else {
250                let subnet_notified = subnet_notify.notified();
251                let fork_notified = fork_notify.notified();
252
253                tokio::select! {
254                    _ = subnet_notified => (),
255                    _ = fork_notified => (),
256                }
257            }
258        }
259    }
260
261    async fn close(&mut self) {
262        // self.rx_regular.close();
263        // self.rx_priority.close();
264        todo!()
265    }
266
267    fn send(
268        &mut self,
269        envelope: Envelope,
270    ) -> impl Future<Output = Result<(), ErrorOf<SendErrorKind>>> + Send {
271        std::future::ready(do_send(self, envelope))
272    }
273
274    fn forward(
275        &mut self,
276        to: Address,
277        envelope: Envelope,
278    ) -> impl Future<Output = Result<(), ErrorOf<SendErrorKind>>> + Send {
279        std::future::ready(do_forward(self, to, envelope))
280    }
281}
282
283impl Bind<NetAddress> for ActorContext {
284    async fn bind(&mut self, args: BindArgs<NetAddress>) -> Result<(), ErrorOf<BindErrorKind>> {
285        use std::collections::btree_map::Entry::*;
286
287        let BindArgs {
288            bind_to,
289            inbox_size,
290        } = args;
291        let address_range = AddressRange::from(bind_to);
292
293        log::debug!(%bind_to, %inbox_size, "binding");
294
295        let Self {
296            fork_address,
297            subnet_context,
298            ..
299        } = self;
300
301        {
302            let mut subnet_context_locked = subnet_context
303                .try_lock()
304                .expect("could not lock subnet_context");
305
306            let SubnetContext {
307                rt_api,
308                subnet_mailbox_tx,
309                bound_subnets,
310                ..
311            } = &mut *subnet_context_locked;
312
313            let bound_subnet_entry = match bound_subnets.entry(address_range) {
314                Vacant(v) => v,
315                Occupied(o) => {
316                    let previously_bound_to = NetAddress::from(*o.key());
317                    return Err(ErrorOf::new(
318                        BindErrorKind::Conflict,
319                        format!("conflict [requested: {bind_to}; existing: {previously_bound_to}]"),
320                    ))
321                },
322            };
323            let subnet_lease = Lease::trusted(bind_to);
324
325            let subnet_mailbox_tx = subnet_mailbox_tx.upgrade().ok_or(ErrorOf::new(
326                BindErrorKind::Closed,
327                "the actor subnet is probably unregistered",
328            ))?;
329            let bound_subnet_node =
330                registry::Node::new(subnet_lease, inbox_size, subnet_mailbox_tx);
331
332            rt_api
333                .registry()
334                .register(bind_to, bound_subnet_node)
335                .map_err(|_| {
336                    ErrorOf::new(
337                        BindErrorKind::Conflict,
338                        "could not register the subnet-node",
339                    )
340                })?;
341
342            bound_subnet_entry.insert(*fork_address);
343        }
344
345        log::info!(%bind_to, %inbox_size, "bound");
346
347        Ok(())
348    }
349}
350
351impl Ping for ActorContext {
352    async fn ping(
353        &mut self,
354        address: Address,
355        timeout: Duration,
356    ) -> Result<Duration, ErrorOf<PingErrorKind>> {
357        let ping_id = rand::rng().next_u64();
358        let now = Instant::now();
359        let deadline = now.checked_add(timeout).unwrap_or(now);
360
361        let Self {
362            fork_address,
363            subnet_context,
364            ..
365        } = self;
366
367        let (subnet_notify, fork_notify) = {
368            let mut subnet_context_locked = subnet_context
369                .try_lock()
370                .expect("could not lock subnet_context");
371            let SubnetContext {
372                rt_api,
373                fork_entries,
374                subnet_notify,
375                ..
376            } = &mut *subnet_context_locked;
377            let ForkEntry { fork_notifiy, .. } = fork_entries
378                .get_mut(fork_address)
379                .expect("fork_entry missing");
380
381            let ping_msg = sys::Ping {
382                reply_to: Some(*fork_address),
383                id:       ping_id,
384            };
385
386            let ping_header = EnvelopeHeader::to_address(address).with_priority(true);
387            let ping_envelope = Envelope::new(ping_header, ping_msg).into_erased();
388            rt_api
389                .send_to(address, true, ping_envelope)
390                .map_err(|e| ErrorOf::new(PingErrorKind::Send, e.to_string()))?;
391
392            (subnet_notify.clone(), fork_notifiy.clone())
393        };
394
395        loop {
396            let timeout = tokio::time::sleep_until(deadline);
397            tokio::select! {
398                _ = timeout => { return Err(ErrorOf::new(PingErrorKind::Timeout, "timeout elapsed")) },
399                _ = subnet_notify.notified() => (),
400                _ = fork_notify.notified() => (),
401            };
402
403            let mut subnet_context_locked = subnet_context
404                .try_lock()
405                .expect("could not lock subnet_context");
406            let SubnetContext {
407                rt_api,
408                fork_entries,
409                bound_subnets,
410                rx_priority,
411                rx_regular,
412                ..
413            } = &mut *subnet_context_locked;
414
415            process_inlets(rt_api, bound_subnets, rx_priority, rx_regular, fork_entries)
416                .map_err(|e| ErrorOf::new(PingErrorKind::Recv, e.to_string()))?;
417
418            if fork_entries
419                .get(fork_address)
420                .expect("fork_entry_missing")
421                .last_ping_received
422                == Some(ping_id)
423            {
424                break
425            }
426        }
427        Ok(now.elapsed())
428    }
429}
430
431fn process_inlets(
432    rt_api: &RtApi,
433    bound_subnets: &BTreeMap<AddressRange, Address>,
434    rx_priority: &mut kanal::Receiver<MessageWithoutPermit<Envelope>>,
435    rx_regular: &mut kanal::Receiver<MessageWithPermit<Envelope>>,
436    fork_entries: &mut HashMap<Address, ForkEntry>,
437) -> Result<(), ErrorOf<RecvErrorKind>> {
438    let mut notified_forks = HashSet::new();
439    while let Some(message) = rx_priority
440        .try_recv_realtime()
441        .map_err(|e| ErrorOf::new(RecvErrorKind::Closed, e.to_string()))?
442    {
443        let message_to = bound_subnets
444            .get(&AddressRange::from(message.to))
445            .copied()
446            .unwrap_or(message.to);
447        let Some(fork_entry) = fork_entries.get_mut(&message_to) else {
448            warn!(dst = %message_to, "no such fork");
449            continue
450        };
451        let should_notify = if let Some(ping_message) = message.message.peek::<sys::Ping>() {
452            let sys::Ping { reply_to, id } = *ping_message;
453            if let Some(reply_to) = reply_to {
454                trace!(%id, %reply_to, "received a ping request");
455                let pong_header = EnvelopeHeader::to_address(reply_to).with_priority(true);
456                let pong_envelope =
457                    Envelope::new(pong_header, sys::Ping { reply_to: None, id }).into_erased();
458                rt_api
459                    .send_to(reply_to, true, pong_envelope)
460                    .inspect_err(
461                        |e| warn!(reason = %e.as_display_chain(), "can't send ping-response"),
462                    )
463                    .ok();
464                trace!(%id, %reply_to, "send a ping response");
465                false
466            } else {
467                trace!(%id, "received a ping response");
468                fork_entry.last_ping_received = Some(id);
469                true
470            }
471        } else {
472            fork_entry.inbox_priority.push_back(message);
473            true
474        };
475
476        if should_notify && notified_forks.insert(message_to) {
477            trace!(dst = %message_to, "notifying fork");
478            fork_entry.fork_notifiy.notify_one();
479        }
480    }
481
482    while let Some(message) = rx_regular
483        .try_recv_realtime()
484        .map_err(|e| ErrorOf::new(RecvErrorKind::Closed, e.to_string()))?
485    {
486        let message_to = bound_subnets
487            .get(&AddressRange::from(message.to))
488            .copied()
489            .unwrap_or(message.to);
490        let Some(fork_entry) = fork_entries.get_mut(&message_to) else {
491            warn!(dst = %message_to, "no such fork");
492            continue
493        };
494        fork_entry.inbox_regular.push_back(message);
495        trace!(dst = %message_to, "subnet received regular message");
496        if notified_forks.insert(message_to) {
497            trace!(dst = %message_to, "notifying fork");
498            fork_entry.fork_notifiy.notify_one();
499        }
500    }
501
502    Ok(())
503}
504
505async fn do_start(
506    context: &mut ActorContext,
507    runnable: BoxedRunnable<ActorContext>,
508    link: bool,
509    timeout: Duration,
510) -> Result<Address, ErrorOf<sys::StartErrorKind>> {
511    let this_address = context.fork_address;
512
513    let mut fork = context
514        .fork()
515        .await
516        .map_err(|e| ErrorOf::new(sys::StartErrorKind::InternalError, e.to_string()))?;
517
518    let fork_address = fork.fork_address;
519    let spawned_address = do_spawn(&mut fork, runnable, Some(fork_address), Some(fork_address))
520        .await
521        .map_err(|e| e.map_kind(sys::StartErrorKind::Spawn))?;
522
523    let envelope = match fork.recv().timeout(timeout).await {
524        Err(_elapsed) => {
525            do_kill(context, spawned_address)
526                .map_err(|e| ErrorOf::new(sys::StartErrorKind::InternalError, e.to_string()))?;
527
528            // TODO: should we ensure termination with a `system::Watch`?
529
530            return Err(ErrorOf::new(
531                sys::StartErrorKind::Timeout,
532                "no init-ack within timeout",
533            ))
534        },
535        Ok(recv_result) => {
536            recv_result
537                .map_err(|e| ErrorOf::new(sys::StartErrorKind::InternalError, e.to_string()))?
538        },
539    };
540
541    dispatch!(match envelope {
542        sys::InitAck { address } => {
543            // The child was linked to this transient start-fork only to observe
544            // an early death (before the init-ack). Drop that link now, before
545            // the fork ends, so the fork's exit does not deliver a spurious
546            // `Exited` to the now-started child (#149).
547            do_unlink(&mut fork, fork_address, address).await;
548            if link {
549                do_link(context, this_address, address).await;
550            }
551            Ok(address)
552        },
553
554        sys::Exited { .. } => {
555            Err(ErrorOf::new(
556                sys::StartErrorKind::Exited,
557                "exited before init-ack",
558            ))
559        },
560
561        unexpected @ _ => {
562            Err(ErrorOf::new(
563                sys::StartErrorKind::InternalError,
564                format!("unexpected message: {unexpected:?}"),
565            ))
566        },
567    })
568}
569
570async fn do_spawn(
571    context: &mut ActorContext,
572    runnable: BoxedRunnable<ActorContext>,
573    ack_to: Option<Address>,
574    link_to: impl IntoIterator<Item = Address>,
575) -> Result<Address, ErrorOf<sys::SpawnErrorKind>> {
576    let ActorContext { subnet_context, .. } = context;
577
578    let (actor_key, rt_config, rt_api, tx_actor_failure) = {
579        let subnet_context_locked = subnet_context
580            .try_lock()
581            .expect("could not lock subnet_context");
582        let SubnetContext {
583            rt_api,
584            rt_config,
585            actor_key,
586            tx_actor_failure,
587            ..
588        } = &*subnet_context_locked;
589
590        (
591            actor_key.child(runnable.func_name()),
592            rt_config.clone(),
593            rt_api.clone(),
594            tx_actor_failure.clone(),
595        )
596    };
597
598    let actor_config = rt_config.actor_config(&actor_key);
599    let execute_on = rt_api.choose_executor(actor_config.runtime_key());
600    let message_tap = rt_api.message_tap(actor_config.message_tap_key());
601
602    trace!(?ack_to, "starting");
603
604    let subnet_lease = rt_api
605        .request_address(actor_config.netmask())
606        .await
607        .inspect_err(|e| log::error!("lease-error: {}", e))
608        .map_err(|e| ErrorOf::new(sys::SpawnErrorKind::ResourceConstraint, e.to_string()))?;
609
610    trace!(subnet_lease = %subnet_lease.net_address(), "subnet leased");
611
612    let rt_api = rt_api.clone();
613    let rt_config = rt_config.clone();
614    let container = container::Container::create(
615        container::ContainerArgs {
616            ack_to,
617            // FIXME: can we make it IntoIterator too?
618            link_to: link_to.into_iter().collect(),
619            actor_key,
620            trace_id: TraceId::current(),
621
622            subnet_lease,
623            rt_api,
624            rt_config,
625            message_tap,
626            tx_actor_failure: tx_actor_failure.clone(),
627        },
628        runnable,
629    )
630    .map_err(|e| ErrorOf::new(sys::SpawnErrorKind::InternalError, e.to_string()))?;
631    let actor_address = container.actor_address();
632
633    trace!(spawned_address = %actor_address, "about to run spawned actor");
634
635    let tx_actor_failure = tx_actor_failure.clone();
636    // TODO: maybe keep it somewhere too?
637    let _join_handle = execute_on.spawn(async move {
638        match container.run().await {
639            Ok(Ok(())) => (),
640            Ok(Err(actor_failure)) => {
641                let _ = tx_actor_failure.send((actor_address, actor_failure));
642            },
643            Err(container_failure) => {
644                let report = AnyError::from(container_failure);
645                log::error!(
646                    err = %report.as_display_chain(), %actor_address,
647                    "actor container failure"
648                );
649            },
650        }
651    });
652
653    Ok(actor_address)
654}
655
656fn do_exit(context: &mut ActorContext, this: Address, peer: Address) -> Result<(), SendErrorKind> {
657    let ActorContext { subnet_context, .. } = context;
658    let subnet_context_locked = subnet_context
659        .try_lock()
660        .expect("could not lock subnet_context");
661    let SubnetContext { rt_api, .. } = &*subnet_context_locked;
662
663    rt_api.sys_send(
664        peer,
665        SysMsg::Link(SysLink::Exit {
666            sender:   this,
667            receiver: peer,
668            reason:   ExitReason::Terminate,
669        }),
670    )
671}
672
673fn do_kill(context: &mut ActorContext, peer: Address) -> Result<(), SendErrorKind> {
674    let ActorContext { subnet_context, .. } = context;
675    let subnet_context_locked = subnet_context
676        .try_lock()
677        .expect("could not lock subnet_context");
678    let SubnetContext { rt_api, .. } = &*subnet_context_locked;
679
680    rt_api.sys_send(peer, SysMsg::Kill)
681}
682
683async fn do_link(context: &mut ActorContext, this: Address, peer: Address) {
684    let ActorContext { call, .. } = context;
685
686    call.invoke(SysCall::Link {
687        sender:   this,
688        receiver: peer,
689    })
690    .await
691}
692
693async fn do_unlink(context: &mut ActorContext, this: Address, peer: Address) {
694    let ActorContext { call, .. } = context;
695    call.invoke(SysCall::Unlink {
696        sender:   this,
697        receiver: peer,
698    })
699    .await
700}
701
702async fn do_set_trap_exit(context: &mut ActorContext, enable: bool) {
703    let ActorContext { call, .. } = context;
704    call.invoke(SysCall::TrapExit(enable)).await;
705}
706
707async fn do_watch(context: &mut ActorContext, peer: Address) -> sys::WatchRef {
708    let ActorContext {
709        fork_address: this,
710        call,
711        ..
712    } = context;
713    let (reply_tx, reply_rx) = oneshot::channel();
714    call.invoke(SysCall::Watch {
715        sender: *this,
716        receiver: peer,
717        reply_tx,
718    })
719    .await;
720    reply_rx.await.expect("sys-call remained unanswered")
721}
722
723async fn do_unwatch(context: &mut ActorContext, watch_ref: sys::WatchRef) {
724    let ActorContext {
725        fork_address: this,
726        call,
727        ..
728    } = context;
729    call.invoke(SysCall::Unwatch {
730        sender: *this,
731        watch_ref,
732    })
733    .await;
734}
735
736async fn do_init_done(context: &mut ActorContext, address: Address) {
737    let ActorContext {
738        ack_to,
739        subnet_context,
740        ..
741    } = context;
742    let message = sys::InitAck { address };
743    let Some(ack_to_address) = ack_to.take() else {
744        return;
745    };
746    let envelope = Envelope::new(EnvelopeHeader::to_address(ack_to_address), message);
747    let subnet_context_locked = subnet_context
748        .try_lock()
749        .expect("could not lock subnet_context");
750    let SubnetContext { rt_api, .. } = &*subnet_context_locked;
751    let _ = rt_api.send_to(envelope.header().to, true, envelope.into_erased());
752}
753
754fn do_send(context: &mut ActorContext, outbound: Envelope) -> Result<(), ErrorOf<SendErrorKind>> {
755    let sender = context.fork_address;
756    let to = outbound.header().to;
757    {
758        let subnet_context_locked = context
759            .subnet_context
760            .try_lock()
761            .expect("could not lock subnet_context");
762        let SubnetContext {
763            subnet_address,
764            actor_key,
765            message_tap,
766            ..
767        } = &*subnet_context_locked;
768        message_tap.on_send(tap::OnSend {
769            sent_by_addr: sender,
770            sent_by_net:  *subnet_address,
771            sent_by_key:  actor_key,
772            sent_to_addr: to,
773            envelope:     &outbound,
774        });
775    }
776
777    let (message, empty_envelope) = outbound.take();
778    let mut header: EnvelopeHeader = empty_envelope.into();
779
780    let new_ttl = header.ttl.checked_sub(1).ok_or_else(|| {
781        warn!(
782            envelope_header = ?header,
783            "TTL exhausted, dropping message"
784        );
785        ErrorOf::new(SendErrorKind::TtlExhausted, "TTL exhausted")
786    })?;
787    header.ttl = new_ttl;
788
789    let outbound = Envelope::new(header, message);
790
791    trace!(envelope = ?outbound, "sending");
792    let ActorContext { subnet_context, .. } = context;
793    let subnet_context_locked = subnet_context
794        .try_lock()
795        .expect("could not lock subnet_context");
796    let SubnetContext { rt_api, .. } = &*subnet_context_locked;
797    rt_api
798        .send_to(outbound.header().to, outbound.header().priority, outbound)
799        .map_err(|k| ErrorOf::new(k, ""))
800}
801
802fn do_forward(
803    context: &mut ActorContext,
804    to: Address,
805    outbound: Envelope,
806) -> Result<(), ErrorOf<SendErrorKind>> {
807    let sender = context.fork_address;
808    {
809        let subnet_context_locked = context
810            .subnet_context
811            .try_lock()
812            .expect("could not lock subnet_context");
813        let SubnetContext {
814            subnet_address,
815            actor_key,
816            message_tap,
817            ..
818        } = &*subnet_context_locked;
819        message_tap.on_send(tap::OnSend {
820            sent_by_addr: sender,
821            sent_by_net:  *subnet_address,
822            sent_by_key:  actor_key,
823            sent_to_addr: to,
824            envelope:     &outbound,
825        });
826    }
827
828    let (message, empty_envelope) = outbound.take();
829    let mut header: EnvelopeHeader = empty_envelope.into();
830
831    let new_ttl = header.ttl.checked_sub(1).ok_or_else(|| {
832        warn!(
833            forward_to = %to,
834            envelope_header = ?header,
835            "TTL exhausted, dropping message"
836        );
837        ErrorOf::new(SendErrorKind::TtlExhausted, "TTL exhausted")
838    })?;
839    header.ttl = new_ttl;
840
841    let outbound = Envelope::new(header, message);
842
843    trace!(forward_to = %to, envelope = ?outbound, "forwarding");
844    let ActorContext { subnet_context, .. } = context;
845    let subnet_context_locked = subnet_context
846        .try_lock()
847        .expect("could not lock subnet_context");
848    let SubnetContext { rt_api, .. } = &*subnet_context_locked;
849    rt_api
850        .send_to(to, outbound.header().priority, outbound)
851        .map_err(|k| ErrorOf::new(k, ""))
852}