From 291199f833f7f22ec0082f7c8dada3fae600cb4e Mon Sep 17 00:00:00 2001 From: dongjiuzhu1 Date: Mon, 8 Dec 2025 22:19:54 +0800 Subject: [PATCH] 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 --- fs/vfs/fs_epoll.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/fs/vfs/fs_epoll.c b/fs/vfs/fs_epoll.c index 42448071276..5cc98bd1281 100644 --- a/fs/vfs/fs_epoll.c +++ b/fs/vfs/fs_epoll.c @@ -42,6 +42,7 @@ #include #include #include +#include #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;