Skip to content

Commit

Permalink
Fix #1455, High-resolution timed ops
Browse files Browse the repository at this point in the history
Add API calls equivalent to existing calls that use an absolute timeout
as opposed to a relative timeout.  The absolute timeout can support
resolution of 0.1 usec in the default configuration.

Internally this primarily affects the underlying call to select().

Note that per the definition of select() in POSIX, it uses a
"struct timeval" which has a resolution of 1 usec.
  • Loading branch information
jphickey committed May 21, 2024
1 parent 372ea65 commit ec30aa7
Show file tree
Hide file tree
Showing 30 changed files with 893 additions and 193 deletions.
108 changes: 108 additions & 0 deletions src/os/inc/osapi-clock.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,29 @@ typedef struct
int64 ticks; /**< Ticks elapsed since reference point */
} OS_time_t;

/**
* @brief The maximum value for OS_time_t
*
* This is the largest positive (future) time that is representable
* in an OS_time_t value.
*/
#define OS_TIME_MAX ((OS_time_t) {INT64_MAX})

/**
* @brief The zero value for OS_time_t
*
* This is a reasonable initializer/placeholder value for an OS_time_t
*/
#define OS_TIME_ZERO ((OS_time_t) {0})

/**
* @brief The minimum value for OS_time_t
*
* This is the largest negative (past) time that is representable
* in an OS_time_t value.
*/
#define OS_TIME_MIN ((OS_time_t) {INT64_MIN})

/**
* @brief Multipliers/divisors to convert ticks into standardized units
*
Expand Down Expand Up @@ -102,6 +125,45 @@ int32 OS_GetLocalTime(OS_time_t *time_struct);
*/
int32 OS_SetLocalTime(const OS_time_t *time_struct);

/*-------------------------------------------------------------------------------------*/
/**
* @brief Gets an absolute time value relative to the current time
*
* This function adds the given interval, expressed in milliseconds, to the
* current clock and returns the result.
*
* @note This is intended to ease transitioning from a relative timeout value to
* and absolute timeout value. The result can be passed to any function
* that accepts an absolute timeout, to mimic the behavior of a relative timeout.
*
* @param[in] relative_msec A relative time interval, in milliseconds
*
* @returns Absolute time value after adding interval
*/
OS_time_t OS_TimeFromRelativeMilliseconds(int32 relative_msec);

/*-------------------------------------------------------------------------------------*/
/**
* @brief Gets a relative time value from an absolute time
*
* This function computes the number of milliseconds until the given
* absolute time value is reached in the system clock.
*
* @note This is intended to ease transitioning from a relative timeout value to
* and absolute timeout value. The result can be passed to any function
* that accepts a relative timeout, to mimic the behavior of an absolute timeout.
*
* The return value of this function is intended to be compatible with the relative
* timeout parameter of various OSAL APIs e.g. OS_TimedRead() / OS_TimedWrite()
*
* @param[in] time An absolute time value
*
* @returns Milliseconds until time value will be reached
* @retval OS_CHECK (0) if time is the current time or is in the past
* @retval OS_PEND (-1) if time is far in the future (not expressable as an int32)
*/
int32 OS_TimeToRelativeMilliseconds(OS_time_t time);

/*-------------------------------------------------------------------------------------*/
/*
* Accessor / Unit Conversion routines for OS_time_t
Expand Down Expand Up @@ -485,6 +547,52 @@ static inline OS_time_t OS_TimeSubtract(OS_time_t time1, OS_time_t time2)
return ostm;
}

/*-------------------------------------------------------------------------------------*/
/**
* @brief Checks if two time values are equal
*
* @param[in] time1 The first time value
* @param[in] time2 The second time value
*
* @retval true if the two values are equal
* @retval false if the two values are not equal
*/
static inline bool OS_TimeEqual(OS_time_t time1, OS_time_t time2)
{
return (time1.ticks == time2.ticks);
}

/*-------------------------------------------------------------------------------------*/
/**
* @brief Checks the sign of the time value
*
* @param[in] time The time to check
*
* @retval -1 if the time value is negative / below 0
* @retval 0 if the time value is 0
* @retval 1 if the time value is positive / above 0
*/
static inline int8_t OS_TimeGetSign(OS_time_t time)
{
return (time.ticks > 0) - (time.ticks < 0);
}

/*-------------------------------------------------------------------------------------*/
/**
* @brief Compares two time values
*
* @param[in] time1 The first time
* @param[in] time2 The second time
*
* @retval -1 if the time1 < time2
* @retval 0 if the times are equal
* @retval 1 if the time1 > time2
*/
static inline int8_t OS_TimeCompare(OS_time_t time1, OS_time_t time2)
{
return OS_TimeGetSign(OS_TimeSubtract(time1, time2));
}

/**@}*/

#endif /* OSAPI_CLOCK_H */
81 changes: 79 additions & 2 deletions src/os/inc/osapi-file.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,49 @@ int32 OS_write(osal_id_t filedes, const void *buffer, size_t nbytes);
* @param[in] filedes The handle ID to operate on
* @param[out] buffer Storage location for file data @nonnull
* @param[in] nbytes Maximum number of bytes to read @nonzero
* @param[in] timeout Maximum time to wait, in milliseconds (OS_PEND = forever)
* @param[in] abstime Absolute time at which this function should return, if no data is readable
*
* @returns Byte count on success or appropriate error code, see @ref OSReturnCodes
* @retval #OS_ERROR_TIMEOUT if no data became available during timeout period
* @retval #OS_ERR_INVALID_ID if the file descriptor passed in is invalid
* @retval #OS_ERR_INVALID_SIZE if the passed-in size is not valid
* @retval #OS_INVALID_POINTER if the passed-in buffer is not valid
* @retval 0 if at end of file/stream data
*/
int32 OS_TimedReadAbs(osal_id_t filedes, void *buffer, size_t nbytes, OS_time_t abstime);

/*-------------------------------------------------------------------------------------*/
/**
* @brief File/Stream input read with a timeout
*
* This implements a time-limited read and is primarily intended for use with
* sockets but may also work with any other stream-like resource that the underlying
* OS supports, such as pipes or special devices.
*
* If data is immediately available on the file/socket, this will return that data
* along with the actual number of bytes that were immediately available. It will
* not block.
*
* If the file position is at the end of file or end of stream data (e.g. if the remote
* end has closed the connection), then this function will immediately return 0 without
* blocking for the timeout period.
*
* If no data is immediately available, but the underlying resource/stream is still
* connected to a peer, this will wait up to the given timeout for additional
* data to appear. If no data appears within the timeout period, then this returns
* the #OS_ERROR_TIMEOUT status code. This allows the caller to differentiate
* an open (but idle) socket connection from a connection which has been closed
* by the remote peer.
*
* In all cases this will return successfully as soon as at least 1 byte of actual
* data is available. It will not attempt to read the entire input buffer.
*
* If an EOF condition occurs prior to timeout, this function returns zero.
*
* @param[in] filedes The handle ID to operate on
* @param[out] buffer Storage location for file data @nonnull
* @param[in] nbytes Maximum number of bytes to read @nonzero
* @param[in] timeout Maximum time to wait, in milliseconds, relative to current time (OS_PEND = forever)
*
* @returns Byte count on success or appropriate error code, see @ref OSReturnCodes
* @retval #OS_ERROR_TIMEOUT if no data became available during timeout period
Expand Down Expand Up @@ -272,7 +314,42 @@ int32 OS_TimedRead(osal_id_t filedes, void *buffer, size_t nbytes, int32 timeout
* @param[in] filedes The handle ID to operate on
* @param[in] buffer Source location for file data @nonnull
* @param[in] nbytes Maximum number of bytes to read @nonzero
* @param[in] timeout Maximum time to wait, in milliseconds (OS_PEND = forever)
* @param[in] abstime Absolute time at which this function should return, if no data is readable
*
* @return A non-negative byte count or appropriate error code, see @ref OSReturnCodes
* @retval #OS_ERROR_TIMEOUT if no data became available during timeout period
* @retval #OS_ERR_INVALID_ID if the file descriptor passed in is invalid
* @retval #OS_ERR_INVALID_SIZE if the passed-in size is not valid
* @retval #OS_INVALID_POINTER if the passed-in buffer is not valid
* @retval 0 if file/stream cannot accept any more data
*/
int32 OS_TimedWriteAbs(osal_id_t filedes, const void *buffer, size_t nbytes, OS_time_t abstime);

/*-------------------------------------------------------------------------------------*/
/**
* @brief File/Stream output write with a timeout
*
* This implements a time-limited write and is primarily intended for use with
* sockets but may also work with any other stream-like resource that the underlying
* OS supports.
*
* If output buffer space is immediately available on the file/socket, this will
* place data into the buffer and return the actual number of bytes that were
* queued for output. It will not block.
*
* If no output buffer space is immediately available, this will wait up to the
* given timeout for space to become available. If no space becomes available within
* the timeout period, then this returns an error code (not zero).
*
* In all cases this will return successfully as soon as at least 1 byte of actual
* data is output. It will _not_ attempt to write the entire output buffer.
*
* If an EOF condition occurs prior to timeout, this function returns zero.
*
* @param[in] filedes The handle ID to operate on
* @param[in] buffer Source location for file data @nonnull
* @param[in] nbytes Maximum number of bytes to read @nonzero
* @param[in] timeout Maximum time to wait, in milliseconds, relative to current time (OS_PEND = forever)
*
* @return A non-negative byte count or appropriate error code, see @ref OSReturnCodes
* @retval #OS_ERROR_TIMEOUT if no data became available during timeout period
Expand Down
94 changes: 94 additions & 0 deletions src/os/inc/osapi-select.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "osconfig.h"
#include "common_types.h"
#include "osapi-clock.h"

/**
* @brief An abstract structure capable of holding several OSAL IDs
Expand Down Expand Up @@ -83,6 +84,52 @@ typedef enum
* If the timeout occurs this returns an error code and all output sets
* should be empty.
*
* This API is identical to OS_SelectMultiple() except for the timeout parameter. In
* this call, timeout is expressed as an absolute value of the OS clock, in the same
* time domain as obtained via OS_GetLocalTime(). This allows for a more precise
* timeout than what is possible via the normal OS_SelectMultiple().
*
* @note This does not lock or otherwise protect the file handles in the
* given sets. If a filehandle supplied via one of the FdSet arguments
* is closed or modified by another while this function is in progress,
* the results are undefined. Because of this limitation, it is recommended
* to use OS_SelectSingle() whenever possible.
*
* @param[in,out] ReadSet Set of handles to check/wait to become readable
* @param[in,out] WriteSet Set of handles to check/wait to become writable
* @param[in] abs_timeout The absolute time that the call may block until
*
* @sa OS_SelectMultiple()
*
* @return Execution status, see @ref OSReturnCodes
* @retval #OS_SUCCESS If any handle in the ReadSet or WriteSet is readable or writable, respectively
* @retval #OS_ERROR_TIMEOUT If no handles in the ReadSet or WriteSet became readable or writable within the timeout
* @retval #OS_ERR_OPERATION_NOT_SUPPORTED if a specified handle does not support select
* @retval #OS_ERR_INVALID_ID if no valid handles were contained in the ReadSet/WriteSet
*/
int32 OS_SelectMultipleAbs(OS_FdSet *ReadSet, OS_FdSet *WriteSet, OS_time_t abs_timeout);

/*-------------------------------------------------------------------------------------*/
/**
* @brief Wait for events across multiple file handles
*
* Wait for any of the given sets of IDs to become readable or writable
*
* This function will block until any of the following occurs:
* - At least one OSAL ID in the ReadSet is readable
* - At least one OSAL ID in the WriteSet is writable
* - The timeout has elapsed
*
* The sets are input/output parameters. On entry, these indicate the
* file handle(s) to wait for. On exit, these are set to the actual
* file handle(s) that have activity.
*
* If the timeout occurs this returns an error code and all output sets
* should be empty.
*
* The timeout is expressed in milliseconds, relative to the time that the API was
* invoked. Use OS_SelectMultipleAbs() for higher timing precision.
*
* @note This does not lock or otherwise protect the file handles in the
* given sets. If a filehandle supplied via one of the FdSet arguments
* is closed or modified by another while this function is in progress,
Expand All @@ -94,6 +141,8 @@ typedef enum
* @param[in] msecs Indicates the timeout. Positive values will wait up to that many milliseconds. Zero will not wait
* (poll). Negative values will wait forever (pend)
*
* @sa OS_SelectMultipleAbs()
*
* @return Execution status, see @ref OSReturnCodes
* @retval #OS_SUCCESS If any handle in the ReadSet or WriteSet is readable or writable, respectively
* @retval #OS_ERROR_TIMEOUT If no handles in the ReadSet or WriteSet became readable or writable within the timeout
Expand Down Expand Up @@ -123,11 +172,56 @@ int32 OS_SelectMultiple(OS_FdSet *ReadSet, OS_FdSet *WriteSet, int32 msecs);
* To mitigate this risk the application may prefer to use
* the OS_TimedRead/OS_TimedWrite calls.
*
* This API is identical to OS_SelectSingle() except for the timeout parameter. In
* this call, timeout is expressed as an absolute value of the OS clock, in the same
* time domain as obtained via OS_GetLocalTime(). This allows for a more precise
* timeout than what is possible via the normal OS_SelectSingle().
*
* @param[in] objid The handle ID to select on
* @param[in,out] StateFlags State flag(s) (readable or writable) @nonnull
* @param[in] abs_timeout The absolute time that the call may block until
*
* @sa OS_SelectSingle()
*
* @return Execution status, see @ref OSReturnCodes
* @retval #OS_SUCCESS If the handle is readable and/or writable, as requested
* @retval #OS_ERROR_TIMEOUT If the handle did not become readable or writable within the timeout
* @retval #OS_INVALID_POINTER if argument is NULL
* @retval #OS_ERR_INVALID_ID if the objid is not a valid handle
*/
int32 OS_SelectSingleAbs(osal_id_t objid, uint32 *StateFlags, OS_time_t abs_timeout);

/*-------------------------------------------------------------------------------------*/
/**
* @brief Wait for events on a single file handle
*
* Wait for a single OSAL filehandle to change state
*
* This function can be used to wait for a single OSAL stream ID
* to become readable or writable. On entry, the "StateFlags"
* parameter should be set to the desired state (OS_STREAM_STATE_READABLE
* and/or OS_STREAM_STATE_WRITABLE) and upon return the flags
* will be set to the state actually detected.
*
* As this operates on a single ID, the filehandle is protected
* during this call, such that another thread accessing the same
* handle will return an error. However, it is important to note that
* once the call returns then other threads may then also read/write
* and affect the state before the current thread can service it.
*
* To mitigate this risk the application may prefer to use
* the OS_TimedRead/OS_TimedWrite calls.
*
* The timeout is expressed in milliseconds, relative to the time that the API was
* invoked. Use OS_SelectSingleAbs() for higher timing precision.
*
* @param[in] objid The handle ID to select on
* @param[in,out] StateFlags State flag(s) (readable or writable) @nonnull
* @param[in] msecs Indicates the timeout. Positive values will wait up to that many milliseconds. Zero will not wait
* (poll). Negative values will wait forever (pend)
*
* @sa OS_SelectSingleAbs()
*
* @return Execution status, see @ref OSReturnCodes
* @retval #OS_SUCCESS If the handle is readable and/or writable, as requested
* @retval #OS_ERROR_TIMEOUT If the handle did not become readable or writable within the timeout
Expand Down
Loading

0 comments on commit ec30aa7

Please sign in to comment.