anchao opened a new pull request, #21477:
URL: https://github.com/apache/nuttx/pull/21477

   ## Summary
   This PR reduces the poll registration churn in the default epoll 
implementation.
   
   Previously, epoll rebuilt driver poll registrations repeatedly across 
`epoll_wait()` calls: notified fds were torn down with `file_poll(..., false)`, 
and the next wait cycle registered them again with `file_poll(..., true)`. With 
many monitored fds, this makes the wait path spend a significant amount of time 
in driver poll teardown/setup instead of only returning the ready set.
   
   This PR changes epoll to use an internal ready list and only revalidates the 
nodes that were actually delivered to userspace.
   
   The change is split into two commits:
   
   1. `fs/epoll: Avoid rebuilding all poll registrations on every wait`
      - Adds an epoll ready list.
      - Keeps idle driver poll registrations armed after `EPOLL_CTL_ADD`.
      - Enqueues notified nodes from the driver callback.
      - Delivers at most `maxevents` nodes per wait and keeps the remaining 
ready nodes queued for the next wait.
      - Preserves `EPOLLONESHOT` semantics by tearing down one-shot nodes only 
after the event is actually delivered to userspace.
      - Revalidates delivered level-triggered nodes after delivery.
      - Adds a generic `file_poll()` compatibility fallback for legacy poll 
drivers that return `-EBUSY` when an already-bound `pollfd_s` is offered again.
   
   2. `drivers,net,fs: Migrate poll drivers to the refresh contract`
      - Updates in-tree poll lower-half implementations so that `setup == true 
&& fds->priv != NULL` is treated as a refresh instead of a new slot allocation.
      - Prevents persistent epoll users from consuming extra poll slots.
      - Adds guards for socket/protocol poll implementations that cannot 
refresh in-place, allowing the generic fallback to rebuild the binding.
      - Fixes teardown paths where needed so the saved poll slot is released 
precisely.
   
   ## Background
   The old implementation effectively performed repeated unregister/register 
work around epoll waits. For example, with a large epoll set, each wait cycle 
could cause many driver `poll(false)` / `poll(true)` calls even though most fds 
were already registered before.
   
   This is especially expensive for drivers or socket protocols whose poll 
setup touches locks, callback allocation, wait queues, or protocol state.
   
   The new model is:
   - `EPOLL_CTL_ADD` registers the driver poll once.
   - Driver `poll_notify()` enqueues the epoll node into an internal ready list.
   - `epoll_wait()` consumes the ready list.
   - `EPOLLONESHOT` nodes are disabled after delivery until re-armed by 
`EPOLL_CTL_MOD`.
   - Level-triggered nodes are revalidated after delivery so they can be 
reported again if they remain ready.
   
   To avoid races with asynchronous `poll_notify()`, level-triggered 
revalidation tears down the delivered node before clearing `revents`, then sets 
it up again to query current readiness. This still avoids rebuilding the whole 
epoll set on every wait; only delivered nodes are revalidated.
   
   ## Compatibility with existing poll drivers
   The generic `file_poll()` helper now handles legacy drivers that do not yet 
support the refresh contract.
   
   If a persistent user passes an already-bound `pollfd_s` to a driver and the 
driver returns `-EBUSY`, `file_poll()` restores the saved `priv` pointer and 
rebuilds the binding through the legacy teardown + setup cycle.
   
   Other setup errors are not hidden by this fallback, so real failures from 
`EPOLL_CTL_MOD` are propagated instead of silently tearing down an existing 
registration.
   
   This keeps out-of-tree drivers compatible while allowing in-tree drivers to 
migrate incrementally.
   
   ## Driver migration contract
   For poll implementations that support refresh in-place:
   ```c
   if (setup)
   {
     if (fds->priv == NULL)
     {
       /* First-time slot binding */
     }
   
     /* Recompute readiness and poll_notify() if ready */
   }
   else
   {
     /* Teardown using fds->priv / saved slot */
   }
   ```
   For protocols or lower halves that cannot safely refresh in-place, the 
implementation returns `-EBUSY` when `fds->priv != NULL`, so the generic 
fallback can rebuild the binding safely.
   
   ## Notes
   
   - This PR does not implement EPOLLET; existing behavior is unchanged.
   - poll() itself still uses the traditional setup/wait/teardown model.
   - The optimization targets epoll's repeated wait path.
   
   ## Testing
   
   Tested on qemu-armv7a:nsh / sim:nsh


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to