async_register_event has asymmetric ownership semantics on failure:
- async_nonblock fails → caller retains ownership (no destroy_async_event called inside)
- epoll_ctl/kevent fails → function destroys event (even for non-sentinel events)
async_register then unconditionally calls destroy_async_event(event) after any failure:
If the epoll_ctl/kevent branch fires, the event has already been freed inside async_register_event, and this second call is a use-after-free. This path is uncommon in practice (ENOMEM/EMFILE during EPOLL_CTL_ADD).
The ownership contract of async_register_event should be clarified or made consistent.
async_register_event has asymmetric ownership semantics on failure:
async_register then unconditionally calls destroy_async_event(event) after any failure:
If the epoll_ctl/kevent branch fires, the event has already been freed inside async_register_event, and this second call is a use-after-free. This path is uncommon in practice (ENOMEM/EMFILE during EPOLL_CTL_ADD).
The ownership contract of async_register_event should be clarified or made consistent.