Waker with data

So I am currently in the process of extending my executor for my embedded project, and want to enable it to wait on more than a single future.

In order to make this decently efficient I would like my executor to not have to poll every task once it is woken up but, ideally, know which future it got woken by. I looked around a bit and found either very simple wakers that do not carry any data, or tutorials for wakers containing a pointer to an Arc<T>. If possible I would prefer t not require Arc for my solution as the platform does not provide efficient Arc (nor threads, so an Rc would actually be sufficient).

My plan would now be to "misuse" the data pointer of the waker and instead just interpret it as a usize directly. Surprisingly all functions required for the RawWakerVTable can still be safe, just building the Waker is unsafe. I would appreciate it if someone could have a quick look at the following short snippet, and check that I did not overlook something?

fn clone(data: *const ()) -> RawWaker {
    let data = data as usize;
    event_raw_waker(data)
}

fn wake(data: *const ()) {
    let data = data as usize;
    safe_placeholder_wake(data);
}

fn wake_by_ref(data: *const ()) {
    let data = data as usize;
    safe_placeholder_wake(data);
}

fn drop(_p: *const ()) {}

static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);

fn event_raw_waker(data: usize) -> RawWaker {
    let data: *const () = data as *const ();
    RawWaker::new(data, &VTABLE)
}

fn event_waker(data: usize) -> Waker {
    let waker = event_raw_waker(data);
    // I am relatively sure this is safe because:
    // the raw waker methods themselfes are all safe
    // the raw pointer is never dereferenced, it is just used a a "direct" value
    // there is no interior mutability, or memory that must be synchronized whatsoever
    unsafe {
        Waker::from_raw(waker)
    }
}

Ultimately I want my executor to have a list of Task, each with a unique number to identify it. That ID would be then passed to the waker as a usize and be used by the executor to wake up the corresponding task again.

PS. the executor context is going to be static so I don't require a pointer to the executor from within the waker.

1 Like

Pretty interesting what you're doing, especially since I am trying something very similar. So no_std, currently using alloc and having a hrad time sharing all the Rc's I currently need.

I find your solution with using data in the waker as an Id or index to get the to be woken very nice.

From what I see, I think what you're doing seems okay, but I am not 100% sure. Obviously you should not derefence the data pointer.

If you're interested, we could share our knowledge, just let me know.

32bit risks wraparound, there is no telling how long a Waker will exist in code (and no requirement to be still relevant to tasks the executor has.) If your not embedding in nuclear power, more than likely you won't care.

Uh, I didn't specify u32 anywhere?
I mean I guess you assume my embedded target as sizeof<usize> == 4?

Anyway, I'm not sure why that is particular relevant in this case, preventing wraparound and other problems would have to be prevented by the executor, and is not really a concern of the waker?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.