fs/epoll: add TLS cleanup handler to release epoll fd reference on thread exit

When a thread is terminated via pthread_exit() while blocked in epoll_wait(),
the file reference taken at the beginning of epoll_wait() is not properly
released, leading to resource leaks.

Problem scenario found during libuv test:
1. Echo server thread is blocked in epoll_wait()
2. Main task sends pthread_kill signal to the server thread
3. Signal handler calls pthread_exit() to terminate the thread
4. epoll_wait() is interrupted before reaching the file_put() call
5. The epoll fd reference count remains elevated
6. epoll_do_close() is never called, leaving fds in internal queues
7. File descriptors leak

Solution:
Register a TLS (Thread Local Storage) cleanup handler using tls_cleanup_push()
at the beginning of epoll_wait() blocking section. This ensures that if the
thread exits abnormally (via pthread_exit, pthread_cancel, etc.), the cleanup
handler (epoll_cleanup) will be called automatically to release the file
reference via file_put().

The cleanup handler is properly paired with tls_cleanup_pop() when epoll_wait()
completes normally, ensuring the handler is only invoked on abnormal exit.

This fix is applied to both epoll_wait() code paths (with and without extended
mode) to ensure consistent behavior.

Impact:
- Prevents epoll fd reference count leaks on thread cancellation
- Ensures proper cleanup even when epoll_wait() is interrupted by pthread_exit
- Critical for multi-threaded applications using signals and thread termination
- Works together with previous fix for teardown/oneshot list cleanup

Signed-off-by: dongjiuzhu1 <dongjiuzhu1@xiaomi.com>
This commit is contained in:
dongjiuzhu1
2025-12-08 22:19:54 +08:00
committed by Alan C. Assis
parent 61a2ab98ef
commit 291199f833
+34
View File
@@ -42,6 +42,7 @@
#include <nuttx/list.h>
#include <nuttx/mutex.h>
#include <nuttx/signal.h>
#include <nuttx/tls.h>
#include "inode/inode.h"
#include "fs_heap.h"
@@ -394,6 +395,19 @@ static int epoll_teardown(FAR epoll_head_t *eph, FAR struct epoll_event *evs,
return i;
}
/****************************************************************************
* Name: epoll_cleanup
*
* Description:
* Cleanup the epoll operation.
*
****************************************************************************/
static void epoll_cleanup(FAR void *arg)
{
file_put(arg);
}
/****************************************************************************
* Name: epoll_default_cb
*
@@ -754,6 +768,12 @@ retry:
nxsig_procmask(SIG_SETMASK, sigmask, &oldsigmask);
/* Push a cancellation point onto the stack. This will be called if
* the thread is canceled.
*/
tls_cleanup_push(tls_get_info(), epoll_cleanup, filep);
if (timeout == 0)
{
ret = -ETIMEDOUT;
@@ -767,6 +787,10 @@ retry:
ret = nxsem_wait(&eph->sem);
}
/* Pop the cancellation point */
tls_cleanup_pop(tls_get_info(), 0);
nxsig_procmask(SIG_SETMASK, &oldsigmask, NULL);
if (ret < 0 && ret != -ETIMEDOUT)
{
@@ -825,6 +849,12 @@ retry:
goto err;
}
/* Push a cancellation point onto the stack. This will be called if
* the thread is canceled.
*/
tls_cleanup_push(tls_get_info(), epoll_cleanup, filep);
/* Wait the poll ready */
if (timeout == 0)
@@ -840,6 +870,10 @@ retry:
ret = nxsem_wait(&eph->sem);
}
/* Pop the cancellation point */
tls_cleanup_pop(tls_get_info(), 0);
if (ret < 0 && ret != -ETIMEDOUT)
{
goto err;