patterncMinor
Lightweight asynchronous event library in C - Threadpool module
Viewed 0 times
threadpoolasynchronouslightweightmodulelibraryevent
Problem
I have finished writing a C library whose purpose is to provide a simple API for asynchronously executing functions, waiting for events on file descriptors and waiting for timeouts. The whole library is published here https://github.com/ernacktob/asyncio. The entire library is probably too large to be reviewed in a single question, so I am trying to split things up into the separate modules.
In this question I am interested in feedback for the threadpool module. The purpose of this module is to implement an API for submitting functions to be executed by a fixed pool of worker threads, or optionally, spawn a separate thread just for this function.
The overall structure of this module consists of a global queue of tasks to be executed by a fixed set of worker threads, and another global queue for "contractor" threads (which are threads spawned for an individual task). The user calls threadpool_dispatch to submit a function to the thread pool, and a structure containing all the relevant information is stored in the appropriate queue. The worker threads wait on the queue, and the first to wake will take the first task from the queue and execute it. The purpose of contractor threads is to handle cases where the task might take a long time to complete, and would stall the worker queue. So this provides the user the option to spawn a dedicated thread for that task, bypassing the worker queue. The module uses the underlying POSIX pthread library for actually creating/cancelling threads, as well as mutex and condition variables.
The API provides a threadpool_handle_t, which is an opaque pointer to a threadpool_handle struct. This handle is used for the user to do things such as blocking until the task is completed (threadpool_join) or cancelling the task (threadpool_cancel).
The public interface to this module is the following (taken from the include/threadpool.h header). The public functions are all defined at the bottom of the source file, with the rest being internal
In this question I am interested in feedback for the threadpool module. The purpose of this module is to implement an API for submitting functions to be executed by a fixed pool of worker threads, or optionally, spawn a separate thread just for this function.
The overall structure of this module consists of a global queue of tasks to be executed by a fixed set of worker threads, and another global queue for "contractor" threads (which are threads spawned for an individual task). The user calls threadpool_dispatch to submit a function to the thread pool, and a structure containing all the relevant information is stored in the appropriate queue. The worker threads wait on the queue, and the first to wake will take the first task from the queue and execute it. The purpose of contractor threads is to handle cases where the task might take a long time to complete, and would stall the worker queue. So this provides the user the option to spawn a dedicated thread for that task, bypassing the worker queue. The module uses the underlying POSIX pthread library for actually creating/cancelling threads, as well as mutex and condition variables.
The API provides a threadpool_handle_t, which is an opaque pointer to a threadpool_handle struct. This handle is used for the user to do things such as blocking until the task is completed (threadpool_join) or cancelling the task (threadpool_cancel).
The public interface to this module is the following (taken from the include/threadpool.h header). The public functions are all defined at the bottom of the source file, with the rest being internal
Solution
After some testing with Valgrind, I discovered a few memory leaks in the threadpool module.
To explain, the threadpool handles are reference counted using the
But there is a case where references are not decremented properly and the handle refcount remains at 1 even after caller has called
I fixed this issue in the latest version of the source published in GitHub.
There is another memory leak I discovered which is that
To explain, the threadpool handles are reference counted using the
refcount field in the struct. When the refcount reaches 0, the structure is freed (threadpool_release_handle deals with decrementing the refcount and freeing when 0). The threadpool_dispatch function sets the initial refcount of the returned handle to 2, one for the caller, and one for the worker/contractor threads.But there is a case where references are not decremented properly and the handle refcount remains at 1 even after caller has called
threadpool_release_handle and the task has completed/cancelled. This happens if the user calls threadpool_cancel while the handle is still in the workers/contractors queue. The threadpool_cancel function takes it out of the queue, but I forgot to also call threadpool_release_handle to release the worker's reference to handle.I fixed this issue in the latest version of the source published in GitHub.
There is another memory leak I discovered which is that
pthread_create allocates some internal memory which remains until someone calls pthread_join or pthread_detach. But there are cases where the thread terminates and pthread_detach is not called. I have not yet figured out how to fix this, so this issue is still present but I will be working to find a fix.Context
StackExchange Code Review Q#156595, answer score: 2
Revisions (0)
No revisions yet.