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    let spawn_permit = rt_api.reserve_spawn().ok_or_else(|| {
602        ErrorOf::new(
603            sys::SpawnErrorKind::ResourceConstraint,
604            "node shutdown is in progress",
605        )
606    })?;
607
608    trace!(?ack_to, "starting");
609
610    let subnet_lease = rt_api
611        .request_address(actor_config.netmask())
612        .await
613        .inspect_err(|e| log::error!("lease-error: {}", e))
614        .map_err(|e| ErrorOf::new(sys::SpawnErrorKind::ResourceConstraint, e.to_string()))?;
615
616    trace!(subnet_lease = %subnet_lease.net_address(), "subnet leased");
617
618    let rt_api = rt_api.clone();
619    let rt_config = rt_config.clone();
620    let container = container::Container::create(
621        container::ContainerArgs {
622            ack_to,
623            // FIXME: can we make it IntoIterator too?
624            link_to: link_to.into_iter().collect(),
625            actor_key,
626            trace_id: TraceId::current(),
627
628            subnet_lease,
629            rt_api,
630            rt_config,
631            message_tap,
632            tx_actor_failure: tx_actor_failure.clone(),
633        },
634        runnable,
635    )
636    .map_err(|e| ErrorOf::new(sys::SpawnErrorKind::InternalError, e.to_string()))?;
637    let actor_address = container.actor_address();
638
639    trace!(spawned_address = %actor_address, "about to run spawned actor");
640
641    let tx_actor_failure = tx_actor_failure.clone();
642    spawn_permit
643        .spawn_on(actor_address, execute_on, async move {
644            match container.run().await {
645                Ok(Ok(())) => (),
646                Ok(Err(actor_failure)) => {
647                    let _ = tx_actor_failure.send((actor_address, actor_failure));
648                },
649                Err(container_failure) => {
650                    let report = AnyError::from(container_failure);
651                    log::error!(
652                        err = %report.as_display_chain(), %actor_address,
653                        "actor container failure"
654                    );
655                },
656            }
657        })
658        .map_err(|_| {
659            ErrorOf::new(
660                sys::SpawnErrorKind::ResourceConstraint,
661                "node shutdown is in progress",
662            )
663        })?;
664
665    Ok(actor_address)
666}
667
668fn do_exit(context: &mut ActorContext, this: Address, peer: Address) -> Result<(), SendErrorKind> {
669    let ActorContext { subnet_context, .. } = context;
670    let subnet_context_locked = subnet_context
671        .try_lock()
672        .expect("could not lock subnet_context");
673    let SubnetContext { rt_api, .. } = &*subnet_context_locked;
674
675    rt_api.sys_send(
676        peer,
677        SysMsg::Link(SysLink::Exit {
678            sender:   this,
679            receiver: peer,
680            reason:   ExitReason::Terminate,
681        }),
682    )
683}
684
685fn do_kill(context: &mut ActorContext, peer: Address) -> Result<(), SendErrorKind> {
686    let ActorContext { subnet_context, .. } = context;
687    let subnet_context_locked = subnet_context
688        .try_lock()
689        .expect("could not lock subnet_context");
690    let SubnetContext { rt_api, .. } = &*subnet_context_locked;
691
692    rt_api.sys_send(peer, SysMsg::Kill)
693}
694
695async fn do_link(context: &mut ActorContext, this: Address, peer: Address) {
696    let ActorContext { call, .. } = context;
697
698    call.invoke(SysCall::Link {
699        sender:   this,
700        receiver: peer,
701    })
702    .await
703}
704
705async fn do_unlink(context: &mut ActorContext, this: Address, peer: Address) {
706    let ActorContext { call, .. } = context;
707    call.invoke(SysCall::Unlink {
708        sender:   this,
709        receiver: peer,
710    })
711    .await
712}
713
714async fn do_set_trap_exit(context: &mut ActorContext, enable: bool) {
715    let ActorContext { call, .. } = context;
716    call.invoke(SysCall::TrapExit(enable)).await;
717}
718
719async fn do_watch(context: &mut ActorContext, peer: Address) -> sys::WatchRef {
720    let ActorContext {
721        fork_address: this,
722        call,
723        ..
724    } = context;
725    let (reply_tx, reply_rx) = oneshot::channel();
726    call.invoke(SysCall::Watch {
727        sender: *this,
728        receiver: peer,
729        reply_tx,
730    })
731    .await;
732    reply_rx.await.expect("sys-call remained unanswered")
733}
734
735async fn do_unwatch(context: &mut ActorContext, watch_ref: sys::WatchRef) {
736    let ActorContext {
737        fork_address: this,
738        call,
739        ..
740    } = context;
741    call.invoke(SysCall::Unwatch {
742        sender: *this,
743        watch_ref,
744    })
745    .await;
746}
747
748async fn do_init_done(context: &mut ActorContext, address: Address) {
749    let ActorContext {
750        ack_to,
751        subnet_context,
752        ..
753    } = context;
754    let message = sys::InitAck { address };
755    let Some(ack_to_address) = ack_to.take() else {
756        return;
757    };
758    let envelope = Envelope::new(EnvelopeHeader::to_address(ack_to_address), message);
759    let subnet_context_locked = subnet_context
760        .try_lock()
761        .expect("could not lock subnet_context");
762    let SubnetContext { rt_api, .. } = &*subnet_context_locked;
763    let _ = rt_api.send_to(envelope.header().to, true, envelope.into_erased());
764}
765
766fn do_send(context: &mut ActorContext, outbound: Envelope) -> Result<(), ErrorOf<SendErrorKind>> {
767    let sender = context.fork_address;
768    let to = outbound.header().to;
769    {
770        let subnet_context_locked = context
771            .subnet_context
772            .try_lock()
773            .expect("could not lock subnet_context");
774        let SubnetContext {
775            subnet_address,
776            actor_key,
777            message_tap,
778            ..
779        } = &*subnet_context_locked;
780        message_tap.on_send(tap::OnSend {
781            sent_by_addr: sender,
782            sent_by_net:  *subnet_address,
783            sent_by_key:  actor_key,
784            sent_to_addr: to,
785            envelope:     &outbound,
786        });
787    }
788
789    let (message, empty_envelope) = outbound.take();
790    let mut header: EnvelopeHeader = empty_envelope.into();
791
792    let new_ttl = header.ttl.checked_sub(1).ok_or_else(|| {
793        warn!(
794            envelope_header = ?header,
795            "TTL exhausted, dropping message"
796        );
797        ErrorOf::new(SendErrorKind::TtlExhausted, "TTL exhausted")
798    })?;
799    header.ttl = new_ttl;
800
801    let outbound = Envelope::new(header, message);
802
803    trace!(envelope = ?outbound, "sending");
804    let ActorContext { subnet_context, .. } = context;
805    let subnet_context_locked = subnet_context
806        .try_lock()
807        .expect("could not lock subnet_context");
808    let SubnetContext { rt_api, .. } = &*subnet_context_locked;
809    rt_api
810        .send_to(outbound.header().to, outbound.header().priority, outbound)
811        .map_err(|k| ErrorOf::new(k, ""))
812}
813
814fn do_forward(
815    context: &mut ActorContext,
816    to: Address,
817    outbound: Envelope,
818) -> Result<(), ErrorOf<SendErrorKind>> {
819    let sender = context.fork_address;
820    {
821        let subnet_context_locked = context
822            .subnet_context
823            .try_lock()
824            .expect("could not lock subnet_context");
825        let SubnetContext {
826            subnet_address,
827            actor_key,
828            message_tap,
829            ..
830        } = &*subnet_context_locked;
831        message_tap.on_send(tap::OnSend {
832            sent_by_addr: sender,
833            sent_by_net:  *subnet_address,
834            sent_by_key:  actor_key,
835            sent_to_addr: to,
836            envelope:     &outbound,
837        });
838    }
839
840    let (message, empty_envelope) = outbound.take();
841    let mut header: EnvelopeHeader = empty_envelope.into();
842
843    let new_ttl = header.ttl.checked_sub(1).ok_or_else(|| {
844        warn!(
845            forward_to = %to,
846            envelope_header = ?header,
847            "TTL exhausted, dropping message"
848        );
849        ErrorOf::new(SendErrorKind::TtlExhausted, "TTL exhausted")
850    })?;
851    header.ttl = new_ttl;
852
853    let outbound = Envelope::new(header, message);
854
855    trace!(forward_to = %to, envelope = ?outbound, "forwarding");
856    let ActorContext { subnet_context, .. } = context;
857    let subnet_context_locked = subnet_context
858        .try_lock()
859        .expect("could not lock subnet_context");
860    let SubnetContext { rt_api, .. } = &*subnet_context_locked;
861    rt_api
862        .send_to(to, outbound.header().priority, outbound)
863        .map_err(|k| ErrorOf::new(k, ""))
864}