#include #include #include #include #include #include /* *============================================================================== * * * Windows Implementation * * *============================================================================== */ #if defined(CONFIG_OS_WINDOWS) void Timer_SetSelector( _In_ Selector* selector ) { /* Selector is not needed for Windows, so this is a NO-OP */ PAL_UNUSED(selector); } VOID CALLBACK _WinTimerCallback( _Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_opt_ PVOID Context, _Inout_ PTP_TIMER pTimer ) { HMODULE newHModule; PAL_UNUSED(pTimer); /* CallbackMayRunLong provides a hint to thread pool that this handler * may take a long time to execute. It allows the thread pool to optimize * as needed. */ if (CallbackMayRunLong( Instance )) { trace_Timer_CallbackMayRunLong_True(); } else { trace_Timer_CallbackMayRunLong_False(); } GetModuleHandleExW( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR) _WinTimerCallback, &newHModule); FreeLibraryWhenCallbackReturns(Instance, newHModule); _Strand_ScheduleTimer( (Strand*)Context ); } /* SHIM to fault inject CreateThreadpoolTimer */ PTP_TIMER WINAPI CreateThreadpoolTimer_Injected(_In_ PTP_TIMER_CALLBACK pfnti, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe, NitsCallSite cs) { return CreateThreadpoolTimer(pfnti, pv, pcbe); } /* Starts a Timer using the specified values. */ _Use_decl_annotations_ TimerResult Timer_Start( Timer* timer, Strand* strand ) { DEBUG_ASSERT( timer ); DEBUG_ASSERT( strand ); if (NULL != timer->pTimer) { trace_Timer_CannotStartTimer_AlreadyRunning( timer ); return TimerResult_InvalidArgument; } timer->reason = TimerReason_Expired; timer->pTimer = CreateThreadpoolTimer_Injected( _WinTimerCallback, strand, NULL, NitsHere() ); if (NULL == timer->pTimer) { trace_Timer_Initialization_Failed(GetLastError()); return TimerResult_Failed; } /* Convert specified interval and start the timer */ { FILETIME ft; ULARGE_INTEGER tmp; tmp.QuadPart = 0; tmp.QuadPart -= timer->timeoutInUsec * 10; /* convert to 100 nanosecond units and set time */ ft.dwLowDateTime = tmp.u.LowPart; ft.dwHighDateTime = tmp.u.HighPart; SetThreadpoolTimer( timer->pTimer, &ft, 0, 0); } trace_Timer_Started_MSCVER(timer->timeoutInUsec * 10); return TimerResult_Success; } _Use_decl_annotations_ void Timer_Fire( Timer* timer, Strand* strand, TimerReason reason ) { DEBUG_ASSERT( timer ); DEBUG_ASSERT( strand ); /* Timer_Close NULLs pTimer, so a NULL value means that the Timer has been * closed, but not restarted. */ if (NULL != timer->pTimer) { SetThreadpoolTimer( timer->pTimer, NULL, 0, 0 ); /* Triggers immediate cancel and clears pending callbacks */ WaitForThreadpoolTimerCallbacks( timer->pTimer, MI_TRUE ); /* Blocks until all pending callbacks have completed */ timer->reason = reason; trace_Timer_ManualTrigger( timer, strand ); _Strand_ScheduleTimer( strand ); } } _Use_decl_annotations_ void Timer_Close( Timer* timer ) { DEBUG_ASSERT( timer ); /* * Note: It is not necessary to call SetThreadpoolTimer( , NULL, , ) and * WaitForThreadpoolTimerCallbacks( , MI_TRUE ) here because the timer will * have already been cancelled or triggered in order for execution to reach * this point. * * Per MSDN: pTimer is freed immediately if ther are no callbacks pending. * Otherwise it is freed after the last callback completes. */ if (NULL == timer || NULL == timer->pTimer) { trace_Timer_Double_Close( timer ); } else { CloseThreadpoolTimer( timer->pTimer ); timer->pTimer = NULL; timer->reason = TimerReason_Expired; trace_Timer_Close( timer ); } } #endif /* defined(CONFIG_OS_WINDOWS) */ /* *============================================================================== * * * POSIX Implementation * * *============================================================================== */ #if defined(CONFIG_POSIX) static Selector* timerSelector = NULL; void Timer_SetSelector( _In_ Selector* selector ) { timerSelector = selector; } MI_Boolean _HandlerTimerCallback( Selector* sel, Handler* handler, MI_Uint32 mask, MI_Uint64 currentTimeUsec) { if (mask & SELECTOR_TIMEOUT) { /* Returns MI_FALSE to signal it is OK to remove the Handler. * actual handling will occur once the Selector calls this func * again with SELECTOR_REMOVE. This is expected to happen * immediately. */ return MI_FALSE; } else if (mask & SELECTOR_REMOVE || mask & SELECTOR_DESTROY) { _Strand_ScheduleTimer( (Strand*)handler->data ); } else if (mask & SELECTOR_ADD) { trace_Timer_Selector_Added(); } else { /* No other mask values should be passed. If so, this will catch them. */ trace_Timer_Unexpected_Selector_Mask( mask ); DEBUG_ASSERT(MI_FALSE); } return MI_TRUE; } /* State checks have already been performed. This function just needs to * perform the requested action according to its OS type. */ _Use_decl_annotations_ TimerResult Timer_Start( Timer* timer, Strand* strand ) { PAL_Uint64 currentTimeUsec = 0; DEBUG_ASSERT( timer ); DEBUG_ASSERT( strand ); timer->selector = timerSelector; /* Keeping it as a member allows it to be checked once */ if (NULL == timer->selector || NULL == timer->selector->rep) { trace_Timer_Selector_Missing( timer->selector ); return TimerResult_InvalidArgument; } if (MI_RESULT_OK == Selector_ContainsHandler(timer->selector, &timer->handler)) { trace_Timer_CannotStartTimer_AlreadyRunning( timer ); return TimerResult_InvalidArgument; } if (PAL_TRUE != PAL_Time(¤tTimeUsec)) { trace_Timer_Cannot_AccessCurrentTime(); return TimerResult_Failed; } timer->reason = TimerReason_Expired; timer->handler.fireTimeoutAt = currentTimeUsec + timer->timeoutInUsec; timer->handler.sock = INVALID_SOCK; timer->handler.data = strand; timer->handler.callback = _HandlerTimerCallback; if (MI_RESULT_OK != Selector_AddHandler( timer->selector, &timer->handler )) { trace_Timer_Cannot_AddHandlerToSelector( timer->selector ); return TimerResult_Failed; } trace_Timer_Started_POSIX( timer->timeoutInUsec ); return TimerResult_Success; } _Use_decl_annotations_ void Timer_Fire( Timer* timer, Strand* strand, TimerReason reason ) { DEBUG_ASSERT( timer ); DEBUG_ASSERT( strand ); /* Handler is zero'd during Timer_Start and Timer_Close. A NULL callback * means that this timer is not active. */ if (NULL != timer->handler.callback) // TODO: Selector_ContainsHandler is a more thorough check, but less efficient { PAL_Uint64 currentTimeUsec = 0; if (PAL_TRUE != PAL_Time(¤tTimeUsec)) { /* Guarantee it will be a time in the past if current time is not accessible. */ currentTimeUsec = TIME_NEVER + 1; trace_Timer_Cannot_AccessCurrentTime(); } if ( TimerReason_Canceled == reason || timer->handler.fireTimeoutAt > currentTimeUsec ) { /* Due to how Selector works, Selector will not check for timeouts * during long running operations. In those instances, the time * of fireTimeoutAt will have already occurred, but the Selector * will have not gotten a chance to detect it yet. That is treated * as the default scenario. This signals that the timeout is treated * as a manuallly triggered timeout. */ timer->reason = reason; } timer->handler.fireTimeoutAt = currentTimeUsec; trace_Timer_ManualTrigger( timer, strand ); Selector_Wakeup( timer->selector, MI_TRUE ); } } _Use_decl_annotations_ void Timer_Close( Timer* timer ) { DEBUG_ASSERT( timer ); if (NULL == timer->handler.callback) { trace_Timer_Double_Close( timer ); } else { /* Handler is not present in the Selector's list since this will only * trigger, if SELECTOR_REMOVE has gone through _HandlerTimerCallback, * so it is OK to zero out. */ memset( &timer->handler, 0, sizeof(Handler) ); timer->reason = TimerReason_Expired; trace_Timer_Close( timer ); } } #endif /* defined(CONFIG_POSIX) */