0
\$\begingroup\$

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;
}
\$\endgroup\$
3
  • \$\begingroup\$ Not pushed anywhere yet \$\endgroup\$ Commented Nov 4, 2023 at 1:46
  • 1
    \$\begingroup\$ A definition of struct binary_semaphore would be very helpful. \$\endgroup\$ Commented Nov 4, 2023 at 3:20
  • 1
    \$\begingroup\$ Aren't we missing some header files? \$\endgroup\$ Commented Nov 4, 2023 at 7:43

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.