Skip to content

napi: uv_async_send doesn't keep event loop alive #32614

@kajukitli

Description

@kajukitli

Summary

uv_async_send spawns work to the main thread via async_work_sender.spawn() without calling ref_op()/unref_op() to keep the event loop alive. This differs from real libuv behavior where an active uv_async_t handle keeps the loop running until uv_close is called.

Current behavior

// ext/napi/uv.rs
unsafe extern "C" fn uv_async_send(handle: *mut uv_async_t) -> c_int {
  unsafe {
    let env = &mut *(*handle).r#loop;
    let handle = SendPtr(handle as *const uv_async_t);
    env.async_work_sender.spawn(move |_| {
      let handle = handle.take() as *mut uv_async_t;
      ((*handle).async_cb)(handle);
    });
  }
  0
}

No tracker.ref_op() before spawn or tracker.unref_op() after callback completion.

Expected behavior

In libuv, uv_async_init starts the handle immediately and keeps the event loop alive until uv_close is called. The Deno implementation should either:

  1. Add ref_op()/unref_op() around the spawned callback (like napi_queue_async_work does), or
  2. Ref the event loop in uv_async_init and unref in uv_close

Impact

If uv_async_send is called when nothing else keeps the event loop alive, the callback could be silently dropped. This is likely rare in practice since typical usage patterns have other things keeping the loop alive.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions