Can someone see if have not made some stupid mistakes in the binary semaphore implementation? I know that some more checks have to be added but I have eventually lost the initial idea in the details.
/**
* @brief Print an error message with additional information about the source code location.
*
* This function formats and prints an error message to the standard error stream (stderr) with
* additional information about the function and line where the error occurred.
*
* @param message A null-terminated string containing the main error message to be printed.
* @param line An integer representing the line number where the error occurred.
* @param function A null-terminated string containing the name of the function where the error occurred.
*
* @note This function makes use of the `perror` function to print the error message along with the
* additional information.
*
* @warning Make sure to use this function in places where the error handling and reporting are required.
* It provides valuable information about the source code location of the error.
*
* @code
* // Example usage:
* if (error_condition) {
* printError("An error occurred in my_function.", __LINE__, __FUNCTION__);
* }
* @endcode
*
* @see perror()
*
* @par Example:
* @code
* printError("An error occurred in my_function.", __LINE__, __FUNCTION__);
* @endcode
*
* @note It's important to note that the `message` parameter should be a format string with placeholders
* for the function name and line number. For example: "An error occurred in %s (Function:%s Line:%d)".
* The `function` and `line` parameters should be appropriately provided to replace the placeholders.
* The format string is internally used with `snprintf` to create the final error message.
*
* @note The function calculates the necessary buffer size for the error message dynamically,
* so it is important to ensure that the memory for the error message is allocated and freed properly.
* The function allocates memory for the error message using the stack. The buffer size is calculated
* based on the length of the message, function name, and line number, so there's no risk of buffer overflow.
*
* @warning Using this function with a format string containing placeholders is essential for the function to work as expected.
*
* @attention The provided format string should contain placeholders for the function name and line number,
* and the function and line parameters should be correctly provided to replace these placeholders.
* Failure to do so may result in incorrect error messages.
*
* @see snprintf()
*
* @return void
*/
static void printError(const char *message, int line, const char *function)
{
int numChars = snprintf(NULL, 0, "%s (Function:%s Line:%d)", message, function, line);
char m[numChars + 1];
snprintf(m, numChars + 1, message, function, line);
perror(m);
}
/**
* @brief Add milliseconds to a struct timespec.
*
* This function adds the specified number of milliseconds to a given struct timespec.
*
* @param ts A pointer to the struct timespec to which the milliseconds will be added.
* @param ms An unsigned integer representing the number of milliseconds to add.
*
* @return A pointer to the updated struct timespec. If `ts` is NULL, the function returns NULL.
*
* @note The function modifies the `ts` parameter to reflect the addition of milliseconds.
* @note The `timespec` structure is commonly used for representing time intervals, and it has fields for seconds and nanoseconds.
*
* @code
* // Example usage:
* struct timespec myTime = {0, 0};
* struct timespec* result = timespecAddMs(&myTime, 500);
* @endcode
*
* @see timespec, struct timespec
*
* @par Example:
* @code
* struct timespec myTime = {0, 0};
* struct timespec* result = timespecAddMs(&myTime, 500);
* @endcode
*
* @note The function performs the addition of milliseconds by updating the `tv_sec` (seconds) and `tv_nsec` (nanoseconds) fields of the timespec structure.
*
* @attention It is essential to ensure that the `ts` parameter points to a valid struct timespec before calling this function. If `ts` is NULL, the function returns NULL without any modification.
*
* @attention The function assumes that `ms` is a non-negative integer value. Negative values for `ms` may result in unexpected behavior.
*
* @see struct timespec
*
* @return A pointer to the updated struct timespec, or NULL if `ts` is NULL.
*/
static struct timespec *timespec_addms(struct timespec *ts, unsigned ms)
{
uint64_t nsec;
if(ts)
{
ts -> tv_sec += ms / 1000;
nsec = ts -> nsec + (ms % 1000) * 1000000ULL;
ts -> tv_sec += msec / 1000000000ULL;
ts -> tv_nsec = msec % 1000000000ULL;
}
return ts;
}
/**
* @brief Lock a pthread mutex with a specified timeout.
*
* This function attempts to lock a pthread mutex with an optional timeout. It allows for immediate
* (non-blocking), maximum wait (blocking), or timed blocking lock operations.
*
* @param mutex A pointer to the pthread mutex to be locked.
* @param wait An unsigned integer indicating the type of lock operation. Use SEMAPHORE_NOWAIT for immediate,
* SEMAPHORE_MAXWAIT for maximum wait, or any other positive value for a timed lock with the specified timeout in milliseconds.
*
* @return 0 if the mutex is successfully locked, or an error code if the lock operation fails.
*
* @note The function makes use of the `clock_gettime` and `timespecAddMs` functions to calculate the timeout
* based on the current time and the specified wait duration.
*
* @code
* // Example usage:
* pthread_mutex_t myMutex;
* int result = mutex_lockwait(&myMutex, SEMAPHORE_MAXWAIT);
* @endcode
*
* @see pthread_mutex_t, clock_gettime(), timespecAddMs()
*
* @par Example:
* @code
* pthread_mutex_t myMutex;
* int result = mutex_lockwait(&myMutex, SEMAPHORE_MAXWAIT);
* @endcode
*
* @attention It is essential to ensure that the `mutex` parameter points to a valid pthread mutex before calling this function.
* Using an uninitialized or invalid mutex may lead to undefined behavior.
*
* @attention The `wait` parameter must be set to one of the predefined values (SEMAPHORE_NOWAIT, SEMAPHORE_MAXWAIT) or a positive integer representing the timeout in milliseconds. Using negative values may result in unexpected behavior.
*
* @attention The function handles error conditions and, in the case of a lock failure, it can call `printError` to print an error message with information about the error location.
*
* @note The function checks if `mutex` is not NULL and returns EFAULT if it is. This ensures proper error handling for invalid or uninitialized mutexes.
*
* @see pthread_mutex_t, printError()
*
* @return 0 if the mutex is successfully locked, or an error code if the lock operation fails.
*/
static int mutex_lockwait(pthread_mutex_t *mutex, unsigned wait)
{
int result = 0;
int line;
struct timespec timeOut;
if(mutex)
{
if(result = clock_gettime(CLOCK_REALTIME, &timeOut))
{
line = __LINE__;
goto exit_cleanup;
}
timespec_addms(&timeOut, wait);
switch(wait)
{
case SEMAPHORE_NOWAIT:
result = pthread_mutex_trylock(mutex);
line = __LINE__;
break;
case SEMAPHORE_MAXWAIT:
result = pthread_mutex_lock(mutex);
line = __LINE__;
break;
default:
result = pthread_mutex_timedlock(mutex, &timeOut);
line = __LINE__;
break;
}
}
else result = EFAULT;
exit_cleanup;
if(result)
{
if(result != EBUSY)
{
printError("[MUTEX]", line, __FUNCTION__);
}
}
return result;
}
void binarySemaphoreDestroy(binary_semaphore_t *s)
{
if(s)
{
pthread_cond_destroy(&s -> cvar);
pthread_mutex_destroy(&s -> mutex);
free(s);
}
}
/**
* @brief Calculate the time difference between two struct timespec values.
*
* This function calculates the time difference in nanoseconds between two struct timespec values (t1 - t2).
*
* @param t1 A pointer to the first struct timespec value (later time).
* @param t2 A pointer to the second struct timespec value (earlier time).
*
* @return An int64_t representing the time difference in nanoseconds. If t1 is not later than t2, the function returns -1.
*
* @note The function assumes that t1 represents a later time than t2.
*
* @code
* // Example usage:
* struct timespec startTime, endTime;
* int64_t timeDifference = timespec_diff(&endTime, &startTime);
* @endcode
*
* @see struct timespec
*
* @par Example:
* @code
* struct timespec startTime, endTime;
* int64_t timeDifference = timespec_diff(&endTime, &startTime);
* @endcode
*
* @attention It is important to ensure that the provided struct timespec values are valid and represent time correctly. The function assumes that t1 represents a later time than t2.
*
* @attention The function returns -1 if t1 is not later than t2, indicating an invalid input.
*
* @return An int64_t representing the time difference in nanoseconds, or -1 if t1 is not later than t2.
*/
static int64_t timespec_diff(const struct timespec *t1, const struct timespec *t2)
{
int64_t nanodiff = -1;
//t1 has to be later than t2
if(t1 -> tv_sec >= t2 -> tv_sec)
{
nanodiff = (t1 -> tv_sec - t2 -> tv_sec) * 1000000000ULL;
if(t1 -> tv_nsec > t2 -> ntv_sec)
{
nanodiff += t1 -> tv_nsec - t2 -> ntv_sec;
}
else
{
if(!nanodiff)
{
nanodiff = -1;
}
else
{
nanodiff -= 1000000000ULL + (t1 -> tv_nsec - t2 -> ntv_sec);
}
}
}
return nanodiff;
}
/**
* @brief Convert a duration in nanoseconds to a struct timespec.
*
* This function converts a duration in nanoseconds to a struct timespec value, which represents time in seconds and nanoseconds.
*
* @param nanosec An int64_t value representing the duration in nanoseconds to be converted.
*
* @return A struct timespec value representing the equivalent time duration in seconds and nanoseconds.
*
* @code
* // Example usage:
* int64_t durationInNanoseconds = 1500000000LL;
* struct timespec timeValue = nanoToTimespec(durationInNanoseconds);
* @endcode
*
* @see struct timespec
*
* @par Example:
* @code
* int64_t durationInNanoseconds = 1500000000LL;
* struct timespec timeValue = nanoToTimespec(durationInNanoseconds);
* @endcode
*
* @attention It is important to ensure that the `nanosec` parameter is a non-negative int64_t value.
*
* @return A struct timespec value representing the equivalent time duration in seconds and nanoseconds.
*/
static struct timespec nanoToTimespec(int64_t nanosec)
{
return (struct timespec){.tv_sec = nanosec / 1000000000ULL, .tv_nsec = nanosec % 1000000000ULL};
}
/**
* @brief Wait for a binary semaphore with an optional timeout.
*
* This function waits for a binary semaphore, allowing a thread to block until the semaphore is posted (set).
*
* @param p A pointer to the binary semaphore structure.
* @param wait An unsigned integer indicating the type of wait operation:
* - Set to SEMAPHORE_NOWAIT for immediate (non-blocking) wait.
* - Set to SEMAPHORE_MAXWAIT for maximum wait (blocking).
* - Set to any other positive value for a timed wait with the specified timeout in milliseconds.
*
* @return 0 if the binary semaphore is successfully acquired, or an error code if the operation fails or times out.
*
* @note The function first locks the binary semaphore's associated mutex and checks if the semaphore is already set (v is true).
* If it's set, the function clears the semaphore (v becomes false) and unlocks the mutex, allowing the caller to proceed.
* If not set, the function enters a loop to potentially wait for the semaphore to be set. If `wait` is SEMAPHORE_NOWAIT, the function immediately returns an error if the semaphore is not set. If `wait` is SEMAPHORE_MAXWAIT, it blocks indefinitely until the semaphore is set. If `wait` is a positive value, it waits for the specified timeout and returns an error if the semaphore is not set within the specified time.
*
* @code
* // Example usage:
* struct binary_semaphore mySemaphore;
* int result = binarySemaphoreWait(&mySemaphore, SEMAPHORE_MAXWAIT);
* @endcode
*
* @see binary_semaphore, mutex_lockwait(), timespec_diff(), timespec_addms(), pthread_cond_timedwait(), pthread_mutex_unlock()
*
* @par Example:
* @code
* struct binary_semaphore mySemaphore;
* int result = binarySemaphoreWait(&mySemaphore, SEMAPHORE_MAXWAIT);
* @endcode
*
* @attention It is essential to ensure that the `p` parameter points to a valid binary semaphore structure before calling this function.
* Using an uninitialized or invalid structure may lead to undefined behavior.
*
* @attention The `wait` parameter must be set to one of the predefined values (SEMAPHORE_NOWAIT, SEMAPHORE_MAXWAIT) or a positive integer representing the timeout in milliseconds. Using negative values may result in unexpected behavior.
*
* @return 0 if the binary semaphore is successfully acquired, or an error code if the operation fails or times out.
*/
int binarySemaphoreWait(struct binary_semaphore *p, unsigned wait)
{
//todo ADD timeouts.
int result;
struct timespec ts, ts1;
clock_gettime(CLOCK_REALTIME, &ts);
if((result = mutex_lockwait(&p -> mutex, wait))) goto cleanup_exit;
while (!p->v)
{
clock_gettime(CLOCK_REALTIME, &ts1);
int64_t nandiff = timespec_diff(&ts1, &ts);
if(nanodiff > 0 && (nanodiff / 1000000ULL) < wait)
{
timespec_addms(&ts, wait - nanodiff / 1000000ULL);
pthread_cond_timedwait(&p->cvar, &p->mutex, &ts);
}
}
p->v = false;
pthread_mutex_unlock(&p->mutex);
cleanup_exit:
return result;
}
/**
* @brief Post (signal) a binary semaphore.
*
* This function posts (signals) a binary semaphore, allowing another thread to acquire it if it was in a wait state.
*
* @param p A pointer to the binary semaphore structure.
*
* @return 0 if the binary semaphore is successfully posted, or an error code if the operation fails.
*
* @note The function first locks the binary semaphore's associated mutex and checks if the semaphore is already set (v is true).
* If it's set, the function returns EAGAIN to indicate that the semaphore is already posted. If not, it sets the semaphore (v becomes true)
* and signals the associated condition variable (cvar) to potentially wake up a waiting thread.
*
* @code
* // Example usage:
* struct binary_semaphore mySemaphore;
* int result = binarySemaphorePost(&mySemaphore);
* @endcode
*
* @see binary_semaphore, pthread_mutex_lock(), pthread_cond_signal(), pthread_mutex_unlock()
*
* @par Example:
* @code
* struct binary_semaphore mySemaphore;
* int result = binarySemaphorePost(&mySemaphore);
* @endcode
*
* @attention It is essential to ensure that the `p` parameter points to a valid binary semaphore structure before calling this function.
* Using an uninitialized or invalid structure may lead to undefined behavior.
*
* @return 0 if the binary semaphore is successfully posted, or an error code if the operation fails.
*/
int binarySemaphorePost(struct binary_semaphore *p)
{
int result = mutex_lock(&p -> mutex);
if(result) goto cleanup_exit;
if (p->v)
{
result = EAGAIN;
goto cleanup_exit;
}
p->v = true;
if((result = pthread_cond_signal(&p->cvar))) goto cleanup_exit;
if((result = pthread_mutex_unlock(&p->mutex))) goto cleanup_exit;
cleanup_exit:
return result;
}
struct binary_semaphorewould be very helpful. \$\endgroup\$