FreeRDP/winpr/libwinpr/synch/timer.c
Bernhard Miklautz 68e8569110 winpr/synch: remove the dependency on winsock.h
winsock.h pulls in a lot of defines and dependencies that are not
required and partially unwanted in winpr's core (for parts that are not
related to network). In order to get rid of this dependency and have an
independent defines for extended winpr functions the WINPR_FD_* defines
are used internally (and for exposed functions). Where required, like in
WSAEventSelect, the FD_* is mapped to WINPR_FD_*.
2015-07-14 11:39:41 +02:00

789 lines
17 KiB
C

/**
* WinPR: Windows Portable Runtime
* Synchronization Functions
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <winpr/crt.h>
#include <winpr/file.h>
#include <winpr/sysinfo.h>
#include <winpr/synch.h>
#ifndef _WIN32
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <signal.h>
#endif
#include "synch.h"
#ifndef _WIN32
#include "../handle/handle.h"
#include "../log.h"
#define TAG WINPR_TAG("synch.timer")
static BOOL TimerCloseHandle(HANDLE handle);
static BOOL TimerIsHandled(HANDLE handle)
{
WINPR_TIMER* pTimer = (WINPR_TIMER*) handle;
if (!pTimer || (pTimer->Type != HANDLE_TYPE_TIMER))
{
SetLastError(ERROR_INVALID_HANDLE);
return FALSE;
}
return TRUE;
}
static int TimerGetFd(HANDLE handle)
{
WINPR_TIMER *timer = (WINPR_TIMER *)handle;
if (!TimerIsHandled(handle))
return -1;
return timer->fd;
}
static DWORD TimerCleanupHandle(HANDLE handle)
{
int length;
UINT64 expirations;
WINPR_TIMER *timer = (WINPR_TIMER *)handle;
if (!TimerIsHandled(handle))
return WAIT_FAILED;
length = read(timer->fd, (void *) &expirations, sizeof(UINT64));
if (length != 8)
{
if (length == -1)
{
if (errno == ETIMEDOUT)
return WAIT_TIMEOUT;
WLog_ERR(TAG, "timer read() failure [%d] %s", errno, strerror(errno));
}
else
{
WLog_ERR(TAG, "timer read() failure - incorrect number of bytes read");
}
return WAIT_FAILED;
}
return WAIT_OBJECT_0;
}
BOOL TimerCloseHandle(HANDLE handle) {
WINPR_TIMER* timer;
timer = (WINPR_TIMER*) handle;
if (!TimerIsHandled(handle))
return FALSE;
#ifdef __linux__
if (timer->fd != -1)
close(timer->fd);
#endif
free(timer);
return TRUE;
}
#ifdef WITH_POSIX_TIMER
static BOOL g_WaitableTimerSignalHandlerInstalled = FALSE;
void WaitableTimerSignalHandler(int signum, siginfo_t* siginfo, void* arg)
{
WINPR_TIMER* timer = siginfo->si_value.sival_ptr;
if (!timer || (signum != SIGALRM))
return;
if (timer->pfnCompletionRoutine)
{
timer->pfnCompletionRoutine(timer->lpArgToCompletionRoutine, 0, 0);
if (timer->lPeriod)
{
timer->timeout.it_interval.tv_sec = (timer->lPeriod / 1000); /* seconds */
timer->timeout.it_interval.tv_nsec = ((timer->lPeriod % 1000) * 1000000); /* nanoseconds */
if ((timer_settime(timer->tid, 0, &(timer->timeout), NULL)) != 0)
{
WLog_ERR(TAG,"timer_settime");
}
}
}
}
int InstallWaitableTimerSignalHandler()
{
if (!g_WaitableTimerSignalHandlerInstalled)
{
struct sigaction action;
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask, SIGALRM);
action.sa_flags = SA_RESTART | SA_SIGINFO;
action.sa_sigaction = (void*) &WaitableTimerSignalHandler;
sigaction(SIGALRM, &action, NULL);
g_WaitableTimerSignalHandlerInstalled = TRUE;
}
return 0;
}
#endif
int InitializeWaitableTimer(WINPR_TIMER* timer)
{
if (!timer->lpArgToCompletionRoutine)
{
#ifdef HAVE_TIMERFD_H
int status;
timer->fd = timerfd_create(CLOCK_MONOTONIC, 0);
if (timer->fd <= 0)
{
free(timer);
return -1;
}
status = fcntl(timer->fd, F_SETFL, O_NONBLOCK);
if (status)
{
close(timer->fd);
return -1;
}
#endif
}
else
{
#ifdef WITH_POSIX_TIMER
struct sigevent sigev;
InstallWaitableTimerSignalHandler();
ZeroMemory(&sigev, sizeof(struct sigevent));
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = SIGALRM;
sigev.sigev_value.sival_ptr = (void*) timer;
if ((timer_create(CLOCK_MONOTONIC, &sigev, &(timer->tid))) != 0)
{
WLog_ERR(TAG,"timer_create");
return -1;
}
#endif
}
timer->bInit = TRUE;
return 0;
}
static HANDLE_OPS ops = {
TimerIsHandled,
TimerCloseHandle,
TimerGetFd,
TimerCleanupHandle
};
/**
* Waitable Timer
*/
HANDLE CreateWaitableTimerA(LPSECURITY_ATTRIBUTES lpTimerAttributes, BOOL bManualReset, LPCSTR lpTimerName)
{
HANDLE handle = NULL;
WINPR_TIMER* timer;
timer = (WINPR_TIMER*) calloc(1, sizeof(WINPR_TIMER));
if (timer)
{
WINPR_HANDLE_SET_TYPE_AND_MODE(timer, HANDLE_TYPE_TIMER, WINPR_FD_READ);
handle = (HANDLE) timer;
timer->fd = -1;
timer->lPeriod = 0;
timer->bManualReset = bManualReset;
timer->pfnCompletionRoutine = NULL;
timer->lpArgToCompletionRoutine = NULL;
timer->bInit = FALSE;
timer->ops = &ops;
}
return handle;
}
HANDLE CreateWaitableTimerW(LPSECURITY_ATTRIBUTES lpTimerAttributes, BOOL bManualReset, LPCWSTR lpTimerName)
{
return NULL;
}
HANDLE CreateWaitableTimerExA(LPSECURITY_ATTRIBUTES lpTimerAttributes, LPCSTR lpTimerName, DWORD dwFlags, DWORD dwDesiredAccess)
{
BOOL bManualReset;
bManualReset = (dwFlags & CREATE_WAITABLE_TIMER_MANUAL_RESET) ? TRUE : FALSE;
return CreateWaitableTimerA(lpTimerAttributes, bManualReset, lpTimerName);
}
HANDLE CreateWaitableTimerExW(LPSECURITY_ATTRIBUTES lpTimerAttributes, LPCWSTR lpTimerName, DWORD dwFlags, DWORD dwDesiredAccess)
{
return NULL;
}
BOOL SetWaitableTimer(HANDLE hTimer, const LARGE_INTEGER* lpDueTime, LONG lPeriod,
PTIMERAPCROUTINE pfnCompletionRoutine, LPVOID lpArgToCompletionRoutine, BOOL fResume)
{
ULONG Type;
WINPR_HANDLE* Object;
WINPR_TIMER* timer;
#ifdef WITH_POSIX_TIMER
LONGLONG seconds = 0;
LONGLONG nanoseconds = 0;
#ifdef HAVE_TIMERFD_H
int status = 0;
#endif /* HAVE_TIMERFD_H */
#endif /* WITH_POSIX_TIMER */
if (!winpr_Handle_GetInfo(hTimer, &Type, &Object))
return FALSE;
if (Type != HANDLE_TYPE_TIMER)
return FALSE;
if (!lpDueTime)
return FALSE;
if (lPeriod < 0)
return FALSE;
timer = (WINPR_TIMER*) Object;
timer->lPeriod = lPeriod; /* milliseconds */
timer->pfnCompletionRoutine = pfnCompletionRoutine;
timer->lpArgToCompletionRoutine = lpArgToCompletionRoutine;
if (!timer->bInit)
{
if (InitializeWaitableTimer(timer) < 0)
return FALSE;
}
#ifdef WITH_POSIX_TIMER
ZeroMemory(&(timer->timeout), sizeof(struct itimerspec));
if (lpDueTime->QuadPart < 0)
{
LONGLONG due = lpDueTime->QuadPart * (-1);
/* due time is in 100 nanosecond intervals */
seconds = (due / 10000000);
nanoseconds = ((due % 10000000) * 100);
}
else if (lpDueTime->QuadPart == 0)
{
seconds = nanoseconds = 0;
}
else
{
WLog_ERR(TAG, "absolute time not implemented");
return FALSE;
}
if (lPeriod > 0)
{
timer->timeout.it_interval.tv_sec = (lPeriod / 1000); /* seconds */
timer->timeout.it_interval.tv_nsec = ((lPeriod % 1000) * 1000000); /* nanoseconds */
}
if (lpDueTime->QuadPart != 0)
{
timer->timeout.it_value.tv_sec = seconds; /* seconds */
timer->timeout.it_value.tv_nsec = nanoseconds; /* nanoseconds */
}
else
{
timer->timeout.it_value.tv_sec = timer->timeout.it_interval.tv_sec; /* seconds */
timer->timeout.it_value.tv_nsec = timer->timeout.it_interval.tv_nsec; /* nanoseconds */
}
if (!timer->pfnCompletionRoutine)
{
#ifdef HAVE_TIMERFD_H
status = timerfd_settime(timer->fd, 0, &(timer->timeout), NULL);
if (status)
{
WLog_ERR(TAG, "timerfd_settime failure: %d", status);
return FALSE;
}
#endif
}
else
{
if ((timer_settime(timer->tid, 0, &(timer->timeout), NULL)) != 0)
{
WLog_ERR(TAG,"timer_settime");
return FALSE;
}
}
#endif
return TRUE;
}
BOOL SetWaitableTimerEx(HANDLE hTimer, const LARGE_INTEGER* lpDueTime, LONG lPeriod,
PTIMERAPCROUTINE pfnCompletionRoutine, LPVOID lpArgToCompletionRoutine, PREASON_CONTEXT WakeContext, ULONG TolerableDelay)
{
ULONG Type;
WINPR_HANDLE* Object;
WINPR_TIMER* timer;
if (!winpr_Handle_GetInfo(hTimer, &Type, &Object))
return FALSE;
if (Type == HANDLE_TYPE_TIMER)
{
timer = (WINPR_TIMER*) Object;
return TRUE;
}
return TRUE;
}
HANDLE OpenWaitableTimerA(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpTimerName)
{
return NULL;
}
HANDLE OpenWaitableTimerW(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCWSTR lpTimerName)
{
return NULL;
}
BOOL CancelWaitableTimer(HANDLE hTimer)
{
return TRUE;
}
/**
* Timer-Queue Timer
*/
/**
* Design, Performance, and Optimization of Timer Strategies for Real-time ORBs:
* http://www.cs.wustl.edu/~schmidt/Timer_Queue.html
*/
void timespec_add_ms(struct timespec* tspec, UINT32 ms)
{
UINT64 ns = tspec->tv_nsec + (ms * 1000000);
tspec->tv_sec += (ns / 1000000000);
tspec->tv_nsec = (ns % 1000000000);
}
UINT64 timespec_to_ms(struct timespec* tspec)
{
UINT64 ms;
ms = tspec->tv_sec * 1000;
ms += tspec->tv_nsec / 1000000;
return ms;
}
static void timespec_gettimeofday(struct timespec* tspec)
{
struct timeval tval;
gettimeofday(&tval, NULL);
tspec->tv_sec = tval.tv_sec;
tspec->tv_nsec = tval.tv_usec * 1000;
}
static int timespec_compare(const struct timespec* tspec1, const struct timespec* tspec2)
{
if (tspec1->tv_sec == tspec2->tv_sec)
return (tspec1->tv_nsec - tspec2->tv_nsec);
else
return (tspec1->tv_sec - tspec2->tv_sec);
}
static void timespec_copy(struct timespec* dst, struct timespec* src)
{
dst->tv_sec = src->tv_sec;
dst->tv_nsec = src->tv_nsec;
}
void InsertTimerQueueTimer(WINPR_TIMER_QUEUE_TIMER** pHead, WINPR_TIMER_QUEUE_TIMER* timer)
{
WINPR_TIMER_QUEUE_TIMER* node;
if (!(*pHead))
{
*pHead = timer;
timer->next = NULL;
return;
}
node = *pHead;
while (node->next)
{
if (timespec_compare(&(timer->ExpirationTime), &(node->ExpirationTime)) > 0)
{
if (timespec_compare(&(timer->ExpirationTime), &(node->next->ExpirationTime)) < 0)
break;
}
node = node->next;
}
if (node->next)
{
timer->next = node->next->next;
node->next = timer;
}
else
{
node->next = timer;
timer->next = NULL;
}
}
void RemoveTimerQueueTimer(WINPR_TIMER_QUEUE_TIMER** pHead, WINPR_TIMER_QUEUE_TIMER* timer)
{
BOOL found = FALSE;
WINPR_TIMER_QUEUE_TIMER* node;
WINPR_TIMER_QUEUE_TIMER* prevNode;
if (timer == *pHead)
{
*pHead = timer->next;
timer->next = NULL;
return;
}
node = *pHead;
prevNode = NULL;
while (node)
{
if (node == timer)
{
found = TRUE;
break;
}
prevNode = node;
node = node->next;
}
if (found)
{
if (prevNode)
{
prevNode->next = timer->next;
}
timer->next = NULL;
}
}
int FireExpiredTimerQueueTimers(WINPR_TIMER_QUEUE* timerQueue)
{
struct timespec CurrentTime;
WINPR_TIMER_QUEUE_TIMER* node;
if (!timerQueue->activeHead)
return 0;
timespec_gettimeofday(&CurrentTime);
node = timerQueue->activeHead;
while (node)
{
if (timespec_compare(&CurrentTime, &(node->ExpirationTime)) >= 0)
{
node->Callback(node->Parameter, TRUE);
node->FireCount++;
timerQueue->activeHead = node->next;
node->next = NULL;
if (node->Period)
{
timespec_add_ms(&(node->ExpirationTime), node->Period);
InsertTimerQueueTimer(&(timerQueue->activeHead), node);
}
else
{
InsertTimerQueueTimer(&(timerQueue->inactiveHead), node);
}
node = timerQueue->activeHead;
}
else
{
break;
}
}
return 0;
}
static void* TimerQueueThread(void* arg)
{
int status;
struct timespec timeout;
WINPR_TIMER_QUEUE* timerQueue = (WINPR_TIMER_QUEUE*) arg;
while (1)
{
pthread_mutex_lock(&(timerQueue->cond_mutex));
timespec_gettimeofday(&timeout);
if (!timerQueue->activeHead)
{
timespec_add_ms(&timeout, 50);
}
else
{
if (timespec_compare(&timeout, &(timerQueue->activeHead->ExpirationTime)) < 0)
{
timespec_copy(&timeout, &(timerQueue->activeHead->ExpirationTime));
}
}
status = pthread_cond_timedwait(&(timerQueue->cond), &(timerQueue->cond_mutex), &timeout);
FireExpiredTimerQueueTimers(timerQueue);
pthread_mutex_unlock(&(timerQueue->cond_mutex));
if (timerQueue->bCancelled)
break;
}
return NULL;
}
int StartTimerQueueThread(WINPR_TIMER_QUEUE* timerQueue)
{
pthread_cond_init(&(timerQueue->cond), NULL);
pthread_mutex_init(&(timerQueue->cond_mutex), NULL);
pthread_mutex_init(&(timerQueue->mutex), NULL);
pthread_attr_init(&(timerQueue->attr));
timerQueue->param.sched_priority = sched_get_priority_max(SCHED_FIFO);
pthread_attr_setschedparam(&(timerQueue->attr), &(timerQueue->param));
pthread_attr_setschedpolicy(&(timerQueue->attr), SCHED_FIFO);
pthread_create(&(timerQueue->thread), &(timerQueue->attr), TimerQueueThread, timerQueue);
return 0;
}
HANDLE CreateTimerQueue(void)
{
HANDLE handle = NULL;
WINPR_TIMER_QUEUE* timerQueue;
timerQueue = (WINPR_TIMER_QUEUE*) calloc(1, sizeof(WINPR_TIMER_QUEUE));
if (timerQueue)
{
WINPR_HANDLE_SET_TYPE_AND_MODE(timerQueue, HANDLE_TYPE_TIMER_QUEUE, WINPR_FD_READ);
handle = (HANDLE) timerQueue;
timerQueue->activeHead = NULL;
timerQueue->inactiveHead = NULL;
timerQueue->bCancelled = FALSE;
StartTimerQueueThread(timerQueue);
}
return handle;
}
BOOL DeleteTimerQueueEx(HANDLE TimerQueue, HANDLE CompletionEvent)
{
void* rvalue;
WINPR_TIMER_QUEUE* timerQueue;
WINPR_TIMER_QUEUE_TIMER* node;
WINPR_TIMER_QUEUE_TIMER* nextNode;
if (!TimerQueue)
return FALSE;
timerQueue = (WINPR_TIMER_QUEUE*) TimerQueue;
/* Cancel and delete timer queue timers */
pthread_mutex_lock(&(timerQueue->cond_mutex));
timerQueue->bCancelled = TRUE;
pthread_cond_signal(&(timerQueue->cond));
pthread_mutex_unlock(&(timerQueue->cond_mutex));
pthread_join(timerQueue->thread, &rvalue);
if (CompletionEvent == INVALID_HANDLE_VALUE)
{
/* Wait for all callback functions to complete before returning */
}
else
{
/* Cancel all timers and return immediately */
/* Move all active timers to the inactive timer list */
node = timerQueue->activeHead;
while (node)
{
InsertTimerQueueTimer(&(timerQueue->inactiveHead), node);
node = node->next;
}
timerQueue->activeHead = NULL;
/* Once all timers are inactive, free them */
node = timerQueue->inactiveHead;
while (node)
{
nextNode = node->next;
free(node);
node = nextNode;
}
timerQueue->inactiveHead = NULL;
}
/* Delete timer queue */
pthread_cond_destroy(&(timerQueue->cond));
pthread_mutex_destroy(&(timerQueue->cond_mutex));
pthread_mutex_destroy(&(timerQueue->mutex));
pthread_attr_destroy(&(timerQueue->attr));
free(timerQueue);
if (CompletionEvent && (CompletionEvent != INVALID_HANDLE_VALUE))
SetEvent(CompletionEvent);
return TRUE;
}
BOOL DeleteTimerQueue(HANDLE TimerQueue)
{
return DeleteTimerQueueEx(TimerQueue, NULL);
}
BOOL CreateTimerQueueTimer(PHANDLE phNewTimer, HANDLE TimerQueue,
WAITORTIMERCALLBACK Callback, PVOID Parameter, DWORD DueTime, DWORD Period, ULONG Flags)
{
struct timespec CurrentTime;
WINPR_TIMER_QUEUE* timerQueue;
WINPR_TIMER_QUEUE_TIMER* timer;
if (!TimerQueue)
return FALSE;
timespec_gettimeofday(&CurrentTime);
timerQueue = (WINPR_TIMER_QUEUE*) TimerQueue;
timer = (WINPR_TIMER_QUEUE_TIMER*) malloc(sizeof(WINPR_TIMER_QUEUE_TIMER));
if (!timer)
return FALSE;
WINPR_HANDLE_SET_TYPE_AND_MODE(timer, HANDLE_TYPE_TIMER_QUEUE_TIMER, WINPR_FD_READ);
*((UINT_PTR*) phNewTimer) = (UINT_PTR)(HANDLE) timer;
timespec_copy(&(timer->StartTime), &CurrentTime);
timespec_add_ms(&(timer->StartTime), DueTime);
timespec_copy(&(timer->ExpirationTime), &(timer->StartTime));
timer->Flags = Flags;
timer->DueTime = DueTime;
timer->Period = Period;
timer->Callback = Callback;
timer->Parameter = Parameter;
timer->timerQueue = (WINPR_TIMER_QUEUE*) TimerQueue;
timer->FireCount = 0;
timer->next = NULL;
pthread_mutex_lock(&(timerQueue->cond_mutex));
InsertTimerQueueTimer(&(timerQueue->activeHead), timer);
pthread_cond_signal(&(timerQueue->cond));
pthread_mutex_unlock(&(timerQueue->cond_mutex));
return TRUE;
}
BOOL ChangeTimerQueueTimer(HANDLE TimerQueue, HANDLE Timer, ULONG DueTime, ULONG Period)
{
struct timespec CurrentTime;
WINPR_TIMER_QUEUE* timerQueue;
WINPR_TIMER_QUEUE_TIMER* timer;
if (!TimerQueue || !Timer)
return FALSE;
timespec_gettimeofday(&CurrentTime);
timerQueue = (WINPR_TIMER_QUEUE*) TimerQueue;
timer = (WINPR_TIMER_QUEUE_TIMER*) Timer;
pthread_mutex_lock(&(timerQueue->cond_mutex));
RemoveTimerQueueTimer(&(timerQueue->activeHead), timer);
RemoveTimerQueueTimer(&(timerQueue->inactiveHead), timer);
timer->DueTime = DueTime;
timer->Period = Period;
timer->next = NULL;
timespec_copy(&(timer->StartTime), &CurrentTime);
timespec_add_ms(&(timer->StartTime), DueTime);
timespec_copy(&(timer->ExpirationTime), &(timer->StartTime));
InsertTimerQueueTimer(&(timerQueue->activeHead), timer);
pthread_cond_signal(&(timerQueue->cond));
pthread_mutex_unlock(&(timerQueue->cond_mutex));
return TRUE;
}
BOOL DeleteTimerQueueTimer(HANDLE TimerQueue, HANDLE Timer, HANDLE CompletionEvent)
{
WINPR_TIMER_QUEUE* timerQueue;
WINPR_TIMER_QUEUE_TIMER* timer;
if (!TimerQueue || !Timer)
return FALSE;
timerQueue = (WINPR_TIMER_QUEUE*) TimerQueue;
timer = (WINPR_TIMER_QUEUE_TIMER*) Timer;
pthread_mutex_lock(&(timerQueue->cond_mutex));
if (CompletionEvent == INVALID_HANDLE_VALUE)
{
/* Wait for all callback functions to complete before returning */
}
else
{
/* Cancel timer and return immediately */
RemoveTimerQueueTimer(&(timerQueue->activeHead), timer);
}
pthread_cond_signal(&(timerQueue->cond));
pthread_mutex_unlock(&(timerQueue->cond_mutex));
free(timer);
if (CompletionEvent && (CompletionEvent != INVALID_HANDLE_VALUE))
SetEvent(CompletionEvent);
return TRUE;
}
#endif