Solution and analysis of the problem that sem_timedwait has been blocked by modifying system time
introduce
When recently fixing a project problem, it was found that when the system time was modified forward, the sem_timedwait function would be blocked all the time. By searching, it is found that int sem_time dwait (sem_t*sem, const struct timespec*abs_timeout); the second blocking time parameter passed in is absolute timestamp, so the function is defective.
Reasons for defects in sem_timedwait:
Assuming that the current system time is 1565 000 000 (2019-08-05 18:13:20) and the time stamp of the blocking waiting of sem_timedwait is 1565 000 100 (2019-08-05 18:15:00), then sem_timedwait needs to block for 1 minute and 40 seconds (100 seconds). In the process of sem_timedwait blocking, the system time is modified to 1500000 (201 000) in the middle. 7-07-14 10:40:00), then sem_time dwait will be blocked for more than two years! This is the defect of sem_timedwait!!
Introduction of sem_timedwait function
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
- If the semaphore is greater than 0, the semaphore is degraded and returned to normal immediately.
- If the semaphore is less than 0, the blocking waits and returns to failure when the blocking timeout occurs (errno is set to ETIMEDOUT)
The second parameter, abs_timeout, points to a structure that specifies an absolute timeout, which is composed of seconds and nanoseconds from Epoch, 1970-01:00:00+0000 (UTC). This structure is defined as follows
struct timespec { time_t tv_sec; /* second */ long tv_nsec; /* nanosecond */ };
Solution
Similar to sem_time dwaim function can be achieved by sem_trywait + usleep, and there will be no blocking problem due to the change of system time forward.
Introduction of sem_trywait function
The function sem_trywait() differs from sem_wait() in that if the current value of the semaphore is 0, an error is returned instead of a blocking call. Error value errno is set to EAGAIN. Sem_trywait () is actually a non-blocking version of sem_wait ().
int sem_trywait(sem_t *sem)
The successful execution returns 0, the failed execution returns - 1, and the semaphore value remains unchanged.
Implementation of sem_trywait + usleep
The main ideas of realization are as follows:
The sem_trywait function returns immediately regardless of whether the semaphore is 0 or not, and it does not usleep when the function returns normally. When the function returns abnormally, it uses usleep to realize the delay, which is realized by bool Wait (size_t timeout) function in the following code:
#include <string> #include<iostream> #include<semaphore.h> #include <time.h> sem_t g_sem; // Get the incremental time of the call from system startup inline uint64_t GetTimeConvSeconds( timespec* curTime, uint32_t factor ) { // CLOCK_MONOTONIC: Timing from the moment the system starts, unaffected by user changes in system time clock_gettime( CLOCK_MONOTONIC, curTime ); return static_cast<uint64_t>(curTime->tv_sec) * factor; } // Get the incremental time of the call from system startup -- converted to microseconds uint64_t GetMonnotonicTime() { timespec curTime; uint64_t result = GetTimeConvSeconds( &curTime, 1000000 ); result += static_cast<uint32_t>(curTime.tv_nsec) / 1000; return result; } // Implementation of sem_trywait + usleep // If the semaphore is greater than 0, reduce the semaphore and return to true immediately // If the semaphore is less than 0, the block waits and returns false when the block timeout occurs bool Wait( size_t timeout ) { const size_t timeoutUs = timeout * 1000; // Delay time from millimeter to microsecond const size_t maxTimeWait = 10000; // The maximum sleep time is 10,000 microseconds, or 10 milliseconds. size_t timeWait = 1; // Sleep time, default 1 microsecond size_t delayUs = 0; // Remaining Sleep Delays const uint64_t startUs = GetMonnotonicTime(); // The start time before the loop, in microseconds uint64_t elapsedUs = 0; // Expiration time, unit microsecond int ret = 0; do { // If the semaphore is greater than 0, reduce the semaphore and return to true immediately if( sem_trywait( &g_sem ) == 0 ) { return true; } // The system signal immediately returns false. if( errno != EAGAIN ) { return false; } // delayUs must be greater than or equal to zero, because the condition for do-while is elapsed Us <= timeout Us. delayUs = timeoutUs - elapsedUs; // Minimum Sleep Time timeWait = std::min( delayUs, timeWait ); // The unit of sleep is microseconds. ret = usleep( timeWait ); if( ret != 0 ) { return false; } // Sleep delay doubles timeWait *= 2; // Sleep delay should not exceed maximum timeWait = std::min( timeWait, maxTimeWait ); // Calculating the start time is now run in microseconds elapsedUs = GetMonnotonicTime() - startUs; } while( elapsedUs <= timeoutUs ); // Exit the loop if the current loop time exceeds the default delay time // Exit overtime, return false return false; } // Get the absolute timestamp that needs to delay the waiting time inline timespec* GetAbsTime( size_t milliseconds, timespec& absTime ) { // CLOCK_REALTIME: The real-time time of the system changes with the change of the real-time time of the system, i.e. from UTC1970-1 to 10:0:0. // In the middle time, if the system time is changed by the user, the corresponding time will change accordingly. clock_gettime( CLOCK_REALTIME, &absTime ); absTime.tv_sec += milliseconds / 1000; absTime.tv_nsec += (milliseconds % 1000) * 1000000; // Nanosecond carry second if( absTime.tv_nsec >= 1000000000 ) { absTime.tv_sec += 1; absTime.tv_nsec -= 1000000000; } return &absTime; } // Sleep implemented by sem_timedwait -- defective // If the semaphore is greater than 0, reduce the semaphore and return to true immediately // If the semaphore is less than 0, the block waits and returns false when the block timeout occurs bool SemTimedWait( size_t timeout ) { timespec absTime; // Get the absolute timestamp that needs to delay the waiting time GetAbsTime( timeout, absTime ); if( sem_timedwait( &g_sem, &absTime ) != 0 ) { return false; } return true; } int main(void) { bool signaled = false; uint64_t startUs = 0; uint64_t elapsedUs = 0; // Initialize semaphores, number 0 sem_init( &g_sem, 0, 0 ); ////////////////////// Sleep implemented by sem_trywait+usleep//////////////////// // Get the start time in microseconds startUs = GetMonnotonicTime(); // Delayed waiting signaled = Wait(1000); // Gets the time to wait overtime in microseconds elapsedUs = GetMonnotonicTime() - startUs; // Output signaled: 0 Wait time: 1000ms std::cout << "signaled:" << signaled << "\t Wait time:" << elapsedUs/1000 << "ms" << std::endl; ////////////////////// Sleep implemented by sem_timedwait//////////////////// ///////////////////// There is a flaw, because when the system time is modified in sem_timedwait blocking, sem_timedwait will always be blocked.////////////////// // Get the start time in microseconds startUs = GetMonnotonicTime(); // Delayed waiting signaled = SemTimedWait(2000); // Gets the time to wait overtime in microseconds elapsedUs = GetMonnotonicTime() - startUs; // Output signaled: 0 SemTimedWait time: 2000ms std::cout << "signaled:" << signaled << "\t SemTimedWait time:" << elapsedUs/1000 << "ms" << std::endl; return 0; }
Test results:
[root@lincoding sem]# ./sem_test signaled:0 Wait time:1000ms signaled:0 SemTimedWait time:2000ms
summary
Try not to use sem_timedwait function to achieve the function of delay waiting. To use the function of delay waiting, it is recommended to use sem_trywait+usleep to achieve delay blocking!