Manually calling wake in non-async context

Hi everyone,

I'm writing an EtherCAT master which targets no_std systems.

One part of the EtherCAT protocol involves sending an EtherCAT packet with a given index, then waiting for a response from a peripheral device with that same index.

Because I want to be able to handle parsing for incoming frames separately (and asynchronously) from the sending of the initial packet, I've come up with a design that uses futures and wakes the future from a sync context. Here's a heavily reduced example demonstrating the only working approach I've stumbled upon so far:

use core::task::Poll;
use core::task::Waker;
use core::time::Duration;
use smol::LocalExecutor;
use std::cell::Cell;
use std::rc::Rc;

struct Client {
    waker: Rc<Cell<Option<Waker>>>,
    frame: Rc<Cell<Option<u8>>>,

impl Default for Client {
    fn default() -> Self {
        Self {
            waker: Rc::new(Cell::new(None)),
            frame: Rc::new(Cell::new(None)),

impl Client {
    /// This will send an EtherCAT frame over the PHY and wait for a response.
    /// All response handling is omitted for brevity.
    pub async fn brd<T>(&self, _address: u16) -> () {

        futures_lite::future::poll_fn(|ctx| {

            if let Some(frame) = self.frame.take() {
                println!("poll_fn -> Ready");
            } else {

                println!("poll_fn -> Pending");



    /// Parse EtherCAT packet(s) from incoming Ethernet frames.
    /// Parsing and storage of the frames is omitted for brevity.
    pub fn parse_response_ethernet_frame(&mut self, _ethernet_frame_payload: &[u8]) {
        println!("Frame received");

        // Frame is ready; tell everyone about it
        if let Some(waker) = self.waker.take() {
            println!("I have a waker");

fn main() {
    let local_ex = LocalExecutor::new();

    futures_lite::future::block_on( {
        let client = Client::default();

        let mut client2 = client.clone();

            .spawn(async move {


                let data_from_ethernet_dma = &[ /* ... */ ];    



This solution feels very unclean to me, but it seems to work in the contrived case. Is there a better way to architect my code?


  1. Calling waker.wake() in a sync context in parse_response_ethernet_frame feels very odd. Is this at least safe?
  2. Is there a better way of asynchronously waiting for the response in brd() where parse_response_ethernet_frame() controls when the future completes?

Please note that parse_response_ethernet_frame and brd will be called from different tasks in an embedded context, or potentially different threads if running on a desktop PC, so they can't be combined. I also don't want to block in brd() because this would prevent other packets being sent from other threads.

Your code looks fine to me, though it's a bit low level.

Thanks for taking a look! Glad to hear I'm on the right track :slight_smile: