2019-05-02 11:38:59 -05:00
// 23 april 2019
# define UNICODE
# define _UNICODE
# define STRICT
# define STRICT_TYPED_ITEMIDS
# define WINVER 0x0600
# define _WIN32_WINNT 0x0600
# define _WIN32_WINDOWS 0x0600
# define _WIN32_IE 0x0700
# define NTDDI_VERSION 0x06000000
# include <windows.h>
2019-05-03 09:32:31 -05:00
# include <errno.h>
# include <process.h>
# include <setjmp.h>
# include <stdio.h>
# include <stdlib.h>
2019-05-02 11:38:59 -05:00
# include "timer.h"
2019-05-05 10:03:17 -05:00
# include "timerpriv.h"
2019-05-02 11:38:59 -05:00
2019-05-05 14:20:54 -05:00
// blah MinGW-w64
# ifndef UI_E_ILLEGAL_REENTRANCY
# define UI_E_ILLEGAL_REENTRANCY 0x802A0003
# endif
2019-05-02 11:38:59 -05:00
static HRESULT lastErrorCodeToHRESULT ( DWORD lastError )
{
if ( lastError = = 0 )
return E_FAIL ;
return HRESULT_FROM_WIN32 ( lastError ) ;
}
static HRESULT lastErrorToHRESULT ( void )
{
return lastErrorCodeToHRESULT ( GetLastError ( ) ) ;
}
static HRESULT WINAPI hrWaitForMultipleObjectsEx ( DWORD n , const HANDLE * objects , BOOL waitAll , DWORD timeout , BOOL alertable , DWORD * result )
{
SetLastError ( 0 ) ;
* result = WaitForMultipleObjectsEx ( n , objects , waitAll , timeout , alertable ) ;
if ( * result = = WAIT_FAILED )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
2019-05-02 22:15:49 -05:00
static HRESULT WINAPI hrSuspendThread ( HANDLE thread )
{
DWORD ret ;
SetLastError ( 0 ) ;
ret = SuspendThread ( thread ) ;
if ( ret = = ( DWORD ) ( - 1 ) )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
static HRESULT WINAPI hrGetThreadContext ( HANDLE thread , LPCONTEXT ctx )
{
BOOL ret ;
SetLastError ( 0 ) ;
ret = GetThreadContext ( thread , ctx ) ;
if ( ret = = 0 )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
static HRESULT WINAPI hrSetThreadContext ( HANDLE thread , CONST CONTEXT * ctx )
{
BOOL ret ;
SetLastError ( 0 ) ;
ret = SetThreadContext ( thread , ctx ) ;
if ( ret = = 0 )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
static HRESULT WINAPI hrPostThreadMessageW ( DWORD threadID , UINT uMsg , WPARAM wParam , LPARAM lParam )
{
BOOL ret ;
SetLastError ( 0 ) ;
ret = PostThreadMessageW ( threadID , uMsg , wParam , lParam ) ;
if ( ret = = 0 )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
static HRESULT WINAPI hrResumeThread ( HANDLE thread )
{
DWORD ret ;
SetLastError ( 0 ) ;
ret = ResumeThread ( thread ) ;
if ( ret = = ( DWORD ) ( - 1 ) )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
static HRESULT WINAPI hrDuplicateHandle ( HANDLE sourceProcess , HANDLE sourceHandle , HANDLE targetProcess , LPHANDLE targetHandle , DWORD access , BOOL inherit , DWORD options )
{
BOOL ret ;
SetLastError ( 0 ) ;
ret = DuplicateHandle ( sourceProcess , sourceHandle ,
targetProcess , targetHandle ,
access , inherit , options ) ;
if ( ret = = 0 )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
2019-05-02 11:38:59 -05:00
static HRESULT WINAPI hrCreateWaitableTimerW ( LPSECURITY_ATTRIBUTES attributes , BOOL manualReset , LPCWSTR name , HANDLE * handle )
{
SetLastError ( 0 ) ;
* handle = CreateWaitableTimerW ( attributes , manualReset , name ) ;
if ( * handle = = NULL )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
2019-05-02 22:15:49 -05:00
static HRESULT WINAPI hrCreateEventW ( LPSECURITY_ATTRIBUTES attributes , BOOL manualReset , BOOL initialState , LPCWSTR name , HANDLE * handle )
{
SetLastError ( 0 ) ;
* handle = CreateEventW ( attributes , manualReset , initialState , name ) ;
if ( * handle = = NULL )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
2019-05-02 11:38:59 -05:00
static HRESULT WINAPI hrSetWaitableTimer ( HANDLE timer , const LARGE_INTEGER * duration , LONG period , PTIMERAPCROUTINE completionRoutine , LPVOID completionData , BOOL resume )
{
BOOL ret ;
SetLastError ( 0 ) ;
ret = SetWaitableTimer ( timer , duration , period , completionRoutine , completionData , resume ) ;
if ( ret = = 0 )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
2019-05-02 22:15:49 -05:00
static HRESULT __cdecl hr_beginthreadex ( void * security , unsigned stackSize , unsigned ( __stdcall * threadProc ) ( void * arg ) , void * threadProcArg , unsigned flags , unsigned * thirdArg , uintptr_t * handle )
{
DWORD lastError ;
// _doserrno is the equivalent of GetLastError(), or at least that's how _beginthreadex() uses it.
_doserrno = 0 ;
* handle = _beginthreadex ( security , stackSize , threadProc , threadProcArg , flags , thirdArg ) ;
if ( * handle = = 0 ) {
lastError = ( DWORD ) _doserrno ;
return lastErrorCodeToHRESULT ( lastError ) ;
}
return S_OK ;
}
static HRESULT WINAPI hrSetEvent ( HANDLE event )
{
BOOL ret ;
SetLastError ( 0 ) ;
ret = SetEvent ( event ) ;
if ( ret = = 0 )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
2019-05-02 11:38:59 -05:00
static HRESULT WINAPI hrWaitForSingleObject ( HANDLE handle , DWORD timeout )
{
DWORD ret ;
SetLastError ( 0 ) ;
ret = WaitForSingleObject ( handle , timeout ) ;
if ( ret = = WAIT_FAILED )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
timerTime timerMonotonicNow ( void )
{
LARGE_INTEGER qpc ;
QueryPerformanceCounter ( & qpc ) ;
return qpc . QuadPart ;
}
timerDuration timerTimeSub ( timerTime end , timerTime start )
{
LARGE_INTEGER qpf ;
2019-05-05 10:03:17 -05:00
timerprivInt128 quot ;
2019-05-02 11:38:59 -05:00
QueryPerformanceFrequency ( & qpf ) ;
2019-05-05 10:03:17 -05:00
timerprivMulDivInt64 ( end - start , timerSecond , qpf . QuadPart , & quot ) ;
2019-05-05 10:16:17 -05:00
// on underflow/overflow, return the minimum/maximum possible timerDuration (respectively); this is based on what Go does
2019-05-05 10:03:17 -05:00
return timerprivInt128ToInt64 ( & quot ,
2019-05-05 10:16:17 -05:00
INT64_MIN , timerDurationMin ,
INT64_MAX , timerDurationMax ) ;
2019-05-02 11:38:59 -05:00
}
2019-05-03 00:53:37 -05:00
// note: the idea for the SetThreadContext() nuttery is from https://www.codeproject.com/Articles/71529/Exception-Injection-Throwing-an-Exception-in-Other
struct timeoutParams {
jmp_buf retpos ;
HANDLE timer ;
HANDLE finished ;
HANDLE targetThread ;
DWORD targetThreadID ;
HRESULT hr ;
} ;
2019-05-04 15:47:56 -05:00
static DWORD timeoutParamsSlot ;
static HRESULT timeoutParamsHRESULT = S_OK ;
static INIT_ONCE timeoutParamsOnce = INIT_ONCE_STATIC_INIT ;
2019-05-03 09:41:27 -05:00
static void onTimeout ( void )
2019-05-03 00:53:37 -05:00
{
struct timeoutParams * p ;
p = ( struct timeoutParams * ) TlsGetValue ( timeoutParamsSlot ) ;
longjmp ( p - > retpos , 1 ) ;
}
static BOOL CALLBACK timeoutParamsSlotInit ( PINIT_ONCE once , PVOID param , PVOID * ctx )
{
SetLastError ( 0 ) ;
timeoutParamsSlot = TlsAlloc ( ) ;
if ( timeoutParamsSlot = = TLS_OUT_OF_INDEXES )
timeoutParamsHRESULT = lastErrorToHRESULT ( ) ;
return TRUE ;
}
static HRESULT setupNonReentrance ( struct timeoutParams * p )
{
SetLastError ( 0 ) ;
if ( InitOnceExecuteOnce ( & timeoutParamsOnce , timeoutParamsSlotInit , NULL , NULL ) = = 0 )
return lastErrorToHRESULT ( ) ;
if ( timeoutParamsHRESULT ! = S_OK )
return timeoutParamsHRESULT ;
if ( TlsGetValue ( timeoutParamsSlot ) ! = NULL )
return UI_E_ILLEGAL_REENTRANCY ;
SetLastError ( 0 ) ;
if ( TlsSetValue ( timeoutParamsSlot , p ) = = 0 )
return lastErrorToHRESULT ( ) ;
return S_OK ;
}
static void teardownNonReentrance ( void )
{
TlsSetValue ( timeoutParamsSlot , NULL ) ;
}
static void redirectToOnTimeout ( CONTEXT * ctx , struct timeoutParams * p )
{
# if defined(_AMD64_)
ctx - > Rip = ( DWORD64 ) onTimeout ;
# elif defined(_ARM_)
ctx - > Pc = ( DWORD ) onTimeout ;
# elif defined(_ARM64_)
ctx - > Pc = ( DWORD64 ) onTimeout ;
# elif defined(_X86_)
ctx - > Eip = ( DWORD ) onTimeout ;
# elif defined(_IA64_)
// TODO verify that this is correct
ctx - > StIIP = ( ULONGLONG ) onTimeout ;
2019-05-03 09:41:27 -05:00
# else
# error unknown CPU architecture; cannot create CONTEXT objects for CPU-specific Windows test code
2019-05-03 00:53:37 -05:00
# endif
}
static void criticalCallFailed ( const char * func , HRESULT hr )
{
2019-05-05 14:20:54 -05:00
fprintf ( stderr , " *** internal error in timerRunWithTimeout(): %s failed: 0x%08I32X \n " , func , hr ) ;
2019-05-03 00:53:37 -05:00
abort ( ) ;
}
static unsigned __stdcall timerThreadProc ( void * data )
{
struct timeoutParams * p = ( struct timeoutParams * ) data ;
HANDLE objects [ 2 ] ;
CONTEXT ctx ;
DWORD which ;
HRESULT hr ;
objects [ 0 ] = p - > timer ;
objects [ 1 ] = p - > finished ;
p - > hr = hrWaitForMultipleObjectsEx ( 2 , objects ,
FALSE , INFINITE , FALSE , & which ) ;
if ( p - > hr ! = S_OK )
// act as if we timed out; the other thread will see the error
which = WAIT_OBJECT_0 ;
if ( which = = WAIT_OBJECT_0 + 1 )
// we succeeded; do nothing
return 0 ;
// we timed out (or there was an error); signal it
hr = hrSuspendThread ( p - > targetThread ) ;
if ( hr ! = S_OK )
criticalCallFailed ( " SuspendThread() " , hr ) ;
ZeroMemory ( & ctx , sizeof ( CONTEXT ) ) ;
2019-05-03 09:41:27 -05:00
ctx . ContextFlags = CONTEXT_CONTROL ;
2019-05-03 00:53:37 -05:00
hr = hrGetThreadContext ( p - > targetThread , & ctx ) ;
if ( hr ! = S_OK )
criticalCallFailed ( " GetThreadContext() " , hr ) ;
redirectToOnTimeout ( & ctx , p ) ;
hr = hrSetThreadContext ( p - > targetThread , & ctx ) ;
if ( hr ! = S_OK )
criticalCallFailed ( " SetThreadContext() " , hr ) ;
// and force the thread to return from GetMessage(), if we are indeed in that
// and yes, this is the way to do it (https://devblogs.microsoft.com/oldnewthing/?p=16553, https://devblogs.microsoft.com/oldnewthing/20050405-46/?p=35973, https://devblogs.microsoft.com/oldnewthing/20080528-00/?p=22163)
hr = hrPostThreadMessageW ( p - > targetThreadID , WM_NULL , 0 , 0 ) ;
if ( hr ! = S_OK )
criticalCallFailed ( " PostThreadMessageW() " , hr ) ;
hr = hrResumeThread ( p - > targetThread ) ;
if ( hr ! = S_OK )
criticalCallFailed ( " ResumeThread() " , hr ) ;
return 0 ;
}
2019-05-30 21:09:45 -05:00
timerSysError timerRunWithTimeout ( timerDuration d , void ( * f ) ( void * data ) , void * data , bool * timedOut )
2019-05-03 00:53:37 -05:00
{
2019-05-03 09:32:31 -05:00
struct timeoutParams * p ;
2019-05-30 21:09:45 -05:00
bool doTeardownNonReentrance = false ;
2019-05-03 00:53:37 -05:00
MSG msg ;
volatile HANDLE timerThread = NULL ;
LARGE_INTEGER duration ;
HRESULT hr ;
2019-05-30 21:09:45 -05:00
* timedOut = false ;
2019-05-03 09:32:31 -05:00
// we use a pointer to heap memory here to avoid volatile kludges
p = ( struct timeoutParams * ) malloc ( sizeof ( struct timeoutParams ) ) ;
if ( p = = NULL )
return ( timerSysError ) E_OUTOFMEMORY ;
ZeroMemory ( p , sizeof ( struct timeoutParams ) ) ;
2019-05-03 00:53:37 -05:00
2019-05-03 09:32:31 -05:00
hr = setupNonReentrance ( p ) ;
2019-05-03 00:53:37 -05:00
if ( hr ! = S_OK )
goto out ;
2019-05-30 21:09:45 -05:00
doTeardownNonReentrance = true ;
2019-05-03 00:53:37 -05:00
// to ensure that the PostThreadMessage() above will not fail because the thread doesn't have a message queue
PeekMessage ( & msg , NULL , WM_USER , WM_USER , PM_NOREMOVE ) ;
hr = hrDuplicateHandle ( GetCurrentProcess ( ) , GetCurrentThread ( ) ,
2019-05-03 09:32:31 -05:00
GetCurrentProcess ( ) , & ( p - > targetThread ) ,
2019-05-03 00:53:37 -05:00
0 , FALSE , DUPLICATE_SAME_ACCESS ) ;
if ( hr ! = S_OK ) {
2019-05-03 09:32:31 -05:00
p - > targetThread = NULL ;
2019-05-03 00:53:37 -05:00
goto out ;
}
2019-05-03 09:32:31 -05:00
p - > targetThreadID = GetCurrentThreadId ( ) ;
2019-05-03 00:53:37 -05:00
2019-05-03 09:32:31 -05:00
hr = hrCreateWaitableTimerW ( NULL , TRUE , NULL , & ( p - > timer ) ) ;
2019-05-03 00:53:37 -05:00
if ( hr ! = S_OK ) {
2019-05-03 09:32:31 -05:00
p - > timer = NULL ;
2019-05-03 00:53:37 -05:00
goto out ;
}
2019-05-03 09:32:31 -05:00
hr = hrCreateEventW ( NULL , TRUE , FALSE , NULL , & ( p - > finished ) ) ;
2019-05-03 00:53:37 -05:00
if ( hr ! = S_OK ) {
2019-05-03 09:32:31 -05:00
p - > finished = NULL ;
2019-05-03 00:53:37 -05:00
goto out ;
}
2019-05-03 09:32:31 -05:00
if ( setjmp ( p - > retpos ) = = 0 ) {
uintptr_t timerThreadValue = 0 ;
2019-05-03 00:53:37 -05:00
hr = hr_beginthreadex ( NULL , 0 , timerThreadProc , p , 0 , NULL , & timerThreadValue ) ;
if ( hr ! = S_OK ) {
timerThread = NULL ;
goto out ;
}
timerThread = ( HANDLE ) timerThreadValue ;
duration . QuadPart = d / 100 ;
duration . QuadPart = - duration . QuadPart ;
2019-05-03 09:32:31 -05:00
hr = hrSetWaitableTimer ( p - > timer , & duration , 0 , NULL , NULL , FALSE ) ;
2019-05-03 00:53:37 -05:00
if ( hr ! = S_OK )
goto out ;
2019-05-03 09:32:31 -05:00
( * f ) ( data ) ;
} else if ( p - > hr ! = S_OK ) {
hr = p - > hr ;
2019-05-03 00:53:37 -05:00
goto out ;
} else
2019-05-30 21:09:45 -05:00
* timedOut = true ;
2019-05-03 00:53:37 -05:00
hr = S_OK ;
out :
if ( timerThread ! = NULL ) {
2019-05-03 09:32:31 -05:00
HRESULT xhr ; // don't overwrite hr below
2019-05-03 00:53:37 -05:00
// if either of these two fail, we cannot continue because the timer thread might interrupt us later, screwing everything up
2019-05-03 09:32:31 -05:00
xhr = hrSetEvent ( p - > finished ) ;
if ( xhr ! = S_OK )
criticalCallFailed ( " SetEvent() " , xhr ) ;
xhr = hrWaitForSingleObject ( timerThread , INFINITE ) ;
if ( xhr ! = S_OK )
criticalCallFailed ( " WaitForSingleObject() " , xhr ) ;
2019-05-03 00:53:37 -05:00
CloseHandle ( timerThread ) ;
}
2019-05-03 09:32:31 -05:00
if ( p - > finished ! = NULL )
CloseHandle ( p - > finished ) ;
if ( p - > timer ! = NULL )
CloseHandle ( p - > timer ) ;
if ( p - > targetThread ! = NULL )
CloseHandle ( p - > targetThread ) ;
2019-05-04 11:11:31 -05:00
if ( doTeardownNonReentrance )
teardownNonReentrance ( ) ;
2019-05-03 09:32:31 -05:00
free ( p ) ;
2019-05-03 00:53:37 -05:00
return ( timerSysError ) hr ;
}
2019-05-02 22:15:49 -05:00
timerSysError timerSleep ( timerDuration d )
2019-05-02 11:38:59 -05:00
{
HANDLE timer ;
LARGE_INTEGER duration ;
HRESULT hr ;
2019-05-02 22:15:49 -05:00
duration . QuadPart = d / 100 ;
2019-05-02 11:38:59 -05:00
duration . QuadPart = - duration . QuadPart ;
hr = hrCreateWaitableTimerW ( NULL , TRUE , NULL , & timer ) ;
if ( hr ! = S_OK )
return ( timerSysError ) hr ;
hr = hrSetWaitableTimer ( timer , & duration , 0 , NULL , NULL , FALSE ) ;
if ( hr ! = S_OK ) {
CloseHandle ( timer ) ;
return ( timerSysError ) hr ;
}
hr = hrWaitForSingleObject ( timer , INFINITE ) ;
CloseHandle ( timer ) ;
return ( timerSysError ) hr ;
}