/*++ Copyright (c) 1989-1998 Microsoft Corporation Module Name: threads.c Abstract: This module defines support functions for the worker, waiter, and timer thread pools. Author: Gurdeep Singh Pall (gurdeep) Nov 13, 1997 Revision History: lokeshs - extended/modified threadpool. Rob Earhart (earhart) September 28, 2000 Moved globals from threads.h to threads.c Split into independant modules Event cache cleanup Environment: These routines are statically linked in the caller's executable and are callable only from user mode. They make use of Nt system services. --*/ #include #include #include #include #include "ntrtlp.h" #include "threads.h" // Thread pool globals // Events used for synchronization // NTRAID#201102-2000/10/10-earhart -- C requires that statically // declared structures be initialized to zero, which is correct for an // slist -- although it would be nice to have some sort of an // SLIST_HEADER_STATIC_INITIALIZER available to use here. SLIST_HEADER EventCache; RTLP_START_THREAD RtlpStartThread ; PRTLP_START_THREAD RtlpStartThreadFunc = RtlpStartThread ; RTLP_EXIT_THREAD RtlpExitThread ; PRTLP_EXIT_THREAD RtlpExitThreadFunc = RtlpExitThread ; ULONG MaxThreads = 500; #if DBG1 PVOID CallbackFn1, CallbackFn2, Context1, Context2 ; #endif #if DBG CHAR InvalidSignatureMsg[] = "Invalid threadpool object signature"; CHAR InvalidDelSignatureMsg[] = "Invalid or deleted threadpool object signature"; #endif NTSTATUS NTAPI RtlSetThreadPoolStartFunc( PRTLP_START_THREAD StartFunc, PRTLP_EXIT_THREAD ExitFunc ) /*++ Routine Description: This routine sets the thread pool's thread creation function. This is not thread safe, because it is intended solely for kernel32 to call for processes that aren't csrss/smss. Arguments: StartFunc - Function to create a new thread Return Value: --*/ { RtlpStartThreadFunc = StartFunc ; RtlpExitThreadFunc = ExitFunc ; return STATUS_SUCCESS ; } NTSTATUS RtlThreadPoolCleanup ( ULONG Flags ) /*++ Routine Description: This routine cleans up the thread pool. Arguments: None Return Value: STATUS_SUCCESS : if none of the components are in use. STATUS_UNSUCCESSFUL : if some components are still in use. --*/ { NTSTATUS Status, NextStatus; return STATUS_UNSUCCESSFUL; // // Attempt to cleanup all modules. Keep, as our final return // value, the status of the first cleanup routine to error (it's // pretty arbitrary), but continue on and attempt cleanup of all // modules. // Status = RtlpTimerCleanup(); NextStatus = RtlpWaitCleanup(); if (NT_SUCCESS(Status)) { Status = NextStatus; } NextStatus = RtlpWorkerCleanup(); if (NT_SUCCESS(Status)) { Status = NextStatus; } return Status; } NTSTATUS NTAPI RtlpStartThread ( PUSER_THREAD_START_ROUTINE Function, PVOID Parameter, HANDLE *ThreadHandleReturn ) { return RtlCreateUserThread( NtCurrentProcess(), // process handle NULL, // security descriptor TRUE, // Create suspended? 0L, // ZeroBits: default 0L, // Max stack size: default 0L, // Committed stack size: default Function, // Function to start in Parameter, // Parameter to start with ThreadHandleReturn, // Thread handle return NULL // Thread id ); } NTSTATUS NTAPI RtlpStartThreadpoolThread ( PUSER_THREAD_START_ROUTINE Function, PVOID Parameter, HANDLE *ThreadHandleReturn ) /*++ Routine Description: This routine is used start a new wait thread in the pool. Arguments: None Return Value: STATUS_SUCCESS - Timer Queue created successfully. STATUS_NO_MEMORY - There was not sufficient heap to perform the requested operation. --*/ { NTSTATUS Status; HANDLE ThreadHandle; if (ThreadHandleReturn) { *ThreadHandleReturn = NULL; } if (LdrpShutdownInProgress) { return STATUS_UNSUCCESSFUL; } // We don't want to create the thread while impersonating; // this was nt raid #278770 ASSERT(! RtlIsImpersonating()); // Create the thread. #if DBG DbgPrintEx(DPFLTR_RTLTHREADPOOL_ID, RTLP_THREADPOOL_TRACE_MASK, "StartThread: Starting worker thread %p(%p)\n", Function, Parameter); #endif Status = RtlpStartThreadFunc(Function, Parameter, &ThreadHandle); #if DBG DbgPrintEx(DPFLTR_RTLTHREADPOOL_ID, RTLP_THREADPOOL_TRACE_MASK, "StartThread: Started worker thread: status %p, handle %x\n", Status, ThreadHandle); #endif if (! NT_SUCCESS(Status)) { goto cleanup; } if (ThreadHandleReturn) { // Set the thread handle return before we resume the // thread -- in case the thread proceeds to use the // returned handle. *ThreadHandleReturn = ThreadHandle; } Status = NtResumeThread(ThreadHandle, NULL); if (! NT_SUCCESS(Status)) { NtTerminateThread(ThreadHandle, Status); if (ThreadHandleReturn) { *ThreadHandleReturn = NULL; } NtClose(ThreadHandle); } cleanup: return Status ; } NTSTATUS NTAPI RtlpExitThread( NTSTATUS Status ) { return NtTerminateThread( NtCurrentThread(), Status ); } VOID RtlpDoNothing ( PVOID NotUsed1, PVOID NotUsed2, PVOID NotUsed3 ) /*++ Routine Description: This routine is used to see if the thread is alive Arguments: NotUsed1, NotUsed2 and NotUsed 3 - not used Return Value: None --*/ { } VOID RtlpThreadCleanup ( ) /*++ Routine Description: This routine is used for exiting timer, wait and IOworker threads. Arguments: Return Value: --*/ { NtTerminateThread( NtCurrentThread(), 0) ; } NTSTATUS RtlpWaitForEvent ( HANDLE Event, HANDLE ThreadHandle ) /*++ Routine Description: Waits for the event to be signalled. If the event is not signalled within one second, then checks to see that the thread is alive Arguments: Event : Event handle used for signalling completion of request ThreadHandle: Thread to check whether still alive Return Value: STATUS_SUCCESS if event was signalled else return NTSTATUS --*/ { NTSTATUS Status; HANDLE Handles[2]; Handles[0] = Event; Handles[1] = ThreadHandle; Status = NtWaitForMultipleObjects(2, Handles, WaitAny, FALSE, NULL); if (Status == STATUS_WAIT_0) { // // The event has been signalled // Status = STATUS_SUCCESS; } else if (Status == STATUS_WAIT_1) { // // The target thread has died. // #if DBG DbgPrintEx(DPFLTR_RTLTHREADPOOL_ID, RTLP_THREADPOOL_ERROR_MASK, "Threadpool thread died before event could be signalled\n"); #endif Status = STATUS_UNSUCCESSFUL; } else if (NT_SUCCESS(Status)) { // // Something else has happened; make sure we fail. // Status = STATUS_UNSUCCESSFUL; } return Status; } PRTLP_EVENT RtlpGetWaitEvent ( VOID ) /*++ Routine Description: Returns an event from the event cache. Arguments: None Return Value: Pointer to event structure --*/ { PSLIST_ENTRY Entry; PRTLP_EVENT Event; NTSTATUS Status; ASSERT(! RtlIsImpersonating()); Entry = RtlInterlockedPopEntrySList(&EventCache); if (Entry) { Event = CONTAINING_RECORD(Entry, RTLP_EVENT, Link); } else { Event = RtlpAllocateTPHeap(sizeof(RTLP_EVENT), 0); if (Event) { Status = NtCreateEvent(&Event->Handle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE); if (! NT_SUCCESS(Status)) { RtlpFreeTPHeap(Event); Event = NULL; } else { OBJECT_HANDLE_FLAG_INFORMATION HandleInfo; HandleInfo.Inherit = FALSE; HandleInfo.ProtectFromClose = TRUE; NtSetInformationObject(Event->Handle, ObjectHandleFlagInformation, &HandleInfo, sizeof(HandleInfo)); } } } return Event; } VOID RtlpFreeWaitEvent ( PRTLP_EVENT Event ) /*++ Routine Description: Frees the event to the event cache Arguments: Event - the event struct to put back into the cache Return Value: Nothing --*/ { ASSERT(Event != NULL); // // Note: There's a race between checking the depth and pushing the // event. This doesn't hurt anything. // if (RtlQueryDepthSList(&EventCache) >= MAX_UNUSED_EVENTS) { OBJECT_HANDLE_FLAG_INFORMATION HandleInfo; HandleInfo.Inherit = FALSE; HandleInfo.ProtectFromClose = FALSE; NtSetInformationObject(Event->Handle, ObjectHandleFlagInformation, &HandleInfo, sizeof(HandleInfo)); NtClose(Event->Handle); RtlpFreeTPHeap(Event); } else { RtlInterlockedPushEntrySList(&EventCache, (PSLIST_ENTRY)&Event->Link); } } VOID RtlpWaitOrTimerCallout(WAITORTIMERCALLBACKFUNC Function, PVOID Context, BOOLEAN TimedOut, PACTIVATION_CONTEXT ActivationContext, HANDLE ImpersonationToken, PRTL_CRITICAL_SECTION const *LocksHeld) /*++ Routine Description: Perform a safe callout to the supplied wait or timer callback Arguments: Function -- the function to call Context -- the context parameter for the function TimedOut -- whether this callback occurred because of a timer expiration ActivationContext -- the request's originating activation context ImpersonationToken -- the request's impersonation token Return Value: None --*/ { NTSTATUS Status; RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME ActivationFrame = { sizeof(ActivationFrame), RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME_FORMAT_WHISTLER }; #if (DBG1) DBG_SET_FUNCTION( Function, Context ); #endif if (ImpersonationToken) { RTL_VERIFY( NT_SUCCESS( NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, (PVOID)&ImpersonationToken, (ULONG)sizeof(HANDLE)))); } RtlActivateActivationContextUnsafeFast(&ActivationFrame, ActivationContext); __try { // ISSUE-2000/10/10-earhart: Once the top level exception // handling code's moved to RTL, we need to catch any // exceptions here, and recover the thread. Function(Context, TimedOut); } __finally { RtlCheckHeldCriticalSections(NtCurrentThread(), LocksHeld); RtlDeactivateActivationContextUnsafeFast(&ActivationFrame); if (RtlIsImpersonating()) { HANDLE NewToken = NULL; RTL_VERIFY( NT_SUCCESS( NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, (PVOID)&NewToken, (ULONG)sizeof(HANDLE)))); } } } VOID RtlpApcCallout(APC_CALLBACK_FUNCTION Function, NTSTATUS Status, PVOID Context1, PVOID Context2) /*++ Routine Description: Perform a safe callout to the supplied APC callback Arguments: Function -- the function to call Status -- the status parameter for the function Context1 -- the first context parameter for the function Context2 -- the second context parameter for the function Return Value: None --*/ { // ISSUE-2000/10/10-earhart: Once the top level exception handling // code's moved to RTL, we need to catch any exceptions here, and // recover the thread. Function(Status, Context1, Context2) ; RtlCheckHeldCriticalSections(NtCurrentThread(), NULL); } VOID RtlpWorkerCallout(WORKERCALLBACKFUNC Function, PVOID Context, PACTIVATION_CONTEXT ActivationContext, HANDLE ImpersonationToken) /*++ Routine Description: Perform a safe callout to the supplied worker callback Arguments: Function -- the function to call Context -- the context parameter for the function ActivationContext -- the request's originating activation context ImpersonationToken -- the request's impersonation token Return Value: None --*/ { RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME ActivationFrame = { sizeof(ActivationFrame), RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME_FORMAT_WHISTLER }; #if (DBG1) DBG_SET_FUNCTION( Function, Context ) ; #endif if (ImpersonationToken) { RTL_VERIFY( NT_SUCCESS( NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, (PVOID)&ImpersonationToken, (ULONG)sizeof(HANDLE)))); } RtlActivateActivationContextUnsafeFast(&ActivationFrame, ActivationContext); __try { // ISSUE-2000/10/10-earhart: Once the top level exception // handling code's moved to RTL, we need to catch any // exceptions here, and recover the thread. Function(Context) ; } __finally { RtlCheckHeldCriticalSections(NtCurrentThread(), NULL); RtlDeactivateActivationContextUnsafeFast(&ActivationFrame); if (RtlIsImpersonating()) { HANDLE NewToken = NULL; RTL_VERIFY( NT_SUCCESS( NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, (PVOID)&NewToken, (ULONG)sizeof(HANDLE)))); } } } NTSTATUS RtlpCaptureImpersonation( IN LOGICAL RequestDuplicateAccess, OUT PHANDLE Token ) { NTSTATUS Status; HANDLE NewToken; *Token = NULL; if (RtlIsImpersonating()) { Status = NtOpenThreadToken(NtCurrentThread(), TOKEN_IMPERSONATE | (RequestDuplicateAccess ? TOKEN_DUPLICATE : 0), TRUE, Token); if (! NT_SUCCESS(Status)) { return Status; } NewToken = NULL; Status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, (PVOID) &NewToken, (ULONG) sizeof(NewToken)); if (! NT_SUCCESS(Status)) { NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, (PVOID) Token, (ULONG) sizeof(*Token)); NtClose(*Token); *Token = NULL; return Status; } } return STATUS_SUCCESS; } VOID RtlpRestartImpersonation( IN HANDLE Token ) { NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &Token, sizeof(Token)); }