//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1990 - 1999 // // File: threads.cxx // //-------------------------------------------------------------------------- /* -------------------------------------------------------------------- Microsoft OS/2 LAN Manager Copyright(c) Microsoft Corp., 1990 -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- File: threads.cxx Description: This file provides a system independent threads package for use on the NT operating system. History: 5/24/90 [mikemon] File created. Kamen Moutafov (KamenM) Dec 99 - Feb 2000 - Support for cell debugging stuff -------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include unsigned long DefaultThreadStackSize = 0; void PauseExecution ( unsigned long milliseconds ) { Sleep(milliseconds); } DLL::DLL ( IN RPC_CHAR * DllName, OUT RPC_STATUS * Status ) /*++ Routine Description: We will load a dll and create an object to represent it. Arguments: DllName - Supplies the name of the dll to be loaded. Status - Returns the status of the operation. This will only be set if an error occurs. The possible error values are as follows. RPC_S_OUT_OF_MEMORY - Insufficient memory is available to load the dll. RPC_S_INVALID_ARG - The requested dll can not be found. --*/ { DWORD dwLastError; if (RpcpStringCompare(DllName, RPC_T("rpcrt4.dll")) == 0) { DllHandle = 0; return; } // Per the above condition, we can only be loading rpcrt4.dll on this path. // Since this is a known dll, we do not have to bother loading it explicitly // from the system folder. DllHandle = (void *)LoadLibrary((const RPC_SCHAR *)DllName); if ( DllHandle == 0 ) { dwLastError = GetLastError(); if ( (dwLastError == ERROR_NOT_ENOUGH_MEMORY) || (dwLastError == ERROR_COMMITMENT_LIMIT) ) { *Status = RPC_S_OUT_OF_MEMORY; } else { *Status = RPC_S_INVALID_ARG; } } } DLL::~DLL ( ) /*++ Routine Description: We just need to free the library, but only if was actually loaded. --*/ { if ( DllHandle != 0 ) { BOOL Status = FreeLibrary((HMODULE)DllHandle); ASSERT( Status ); } } extern HANDLE GetCompletionPortHandleForThread(void); extern void ReleaseCompletionPortHandleForThread(HANDLE h); void * DLL::GetEntryPoint ( IN char * Procedure ) /*++ Routine Description: We obtain the entry point for a specified procedure from this dll. Arguments: Procedure - Supplies the name of the entry point. Return Value: A pointer to the requested procedure will be returned if it can be found; otherwise, zero will be returned. --*/ { FARPROC ProcedurePointer; if (DllHandle == 0) { if (strcmp(Procedure, "TransportLoad") == 0) return (PVOID)TransportLoad; else if (strcmp(Procedure, "GetCompletionPortHandleForThread") == 0) return (PVOID)GetCompletionPortHandleForThread; else if (strcmp(Procedure, "ReleaseCompletionPortHandleForThread") == 0) return (PVOID) ReleaseCompletionPortHandleForThread; ASSERT(0); return NULL; } ProcedurePointer = GetProcAddress((HINSTANCE)DllHandle, (LPSTR) Procedure); if ( ProcedurePointer == 0 ) { ASSERT( GetLastError() == ERROR_PROC_NOT_FOUND ); } return(ProcedurePointer); } unsigned long CurrentTimeSeconds ( void ) // Return the current time in seconds. When this time is counted // from is not particularly important since it is used as a delta. { return(GetTickCount()*1000L); } RPC_STATUS SetThreadStackSize ( IN unsigned long ThreadStackSize ) /*++ Routine Description: We want to set the default thread stack size. Arguments: ThreadStackSize - Supplies the required thread stack size in bytes. Return Value: RPC_S_OK - We will always return this, because this routine always succeeds. --*/ { if (DefaultThreadStackSize < ThreadStackSize) DefaultThreadStackSize = ThreadStackSize; return(RPC_S_OK); } long ThreadGetRpcCancelTimeout ( ) { THREAD * ThreadInfo = RpcpGetThreadPointer(); ASSERT(ThreadInfo); return (ThreadInfo->CancelTimeout); } RPC_STATUS ThreadStartRoutine ( IN THREAD * Thread ) { RpcpSetThreadPointer(Thread); Thread->StartRoutine(); return 0; } THREAD * ThreadSelfHelper ( ) { THREAD * Thread; RPC_STATUS Status = RPC_S_OK; Thread = new THREAD(&Status); if (Thread == 0) { return 0; } if (Status != RPC_S_OK) { delete Thread; return 0; } return Thread; } RPC_STATUS RPC_ENTRY RpcMgmtSetCancelTimeout( long Timeout ) /*++ Routine Description: An application will use this routine to set the cancel timeout for a thread. Arguments: Timeout - Supplies the cancel timeout value to set in the thread. 0 = No cancel timeout n = seconds RPC_C_CANCEL_INFINITE_TIMEOUT = Infinite Return Value: RPC_S_OK - The operation completed successfully. RPC_S_INVALID_TIMEOUT - The specified timeout value is invalid. --*/ { InitializeIfNecessary(); THREAD *Thread = ThreadSelf() ; if (!Thread) { return RPC_S_OUT_OF_MEMORY; } return Thread->SetCancelTimeout(Timeout); } RPC_STATUS RegisterForCancels( CALL * Call ) { THREAD * ThreadInfo = ThreadSelf(); if (ThreadInfo == 0) { return RPC_S_OUT_OF_MEMORY; } return ThreadInfo->RegisterForCancels(Call); } RPC_STATUS UnregisterForCancels( ) { THREAD *ThreadInfo = RpcpGetThreadPointer(); ASSERT(ThreadInfo); ThreadInfo->UnregisterForCancels (); return RPC_S_OK; } RPC_STATUS RPC_ENTRY RpcCancelThread( IN void * ThreadHandle ) { if (!QueueUserAPC(CancelAPCRoutine, ThreadHandle, 0)) { return RPC_S_ACCESS_DENIED; } return RPC_S_OK; } RPC_STATUS RPC_ENTRY RpcCancelThreadEx ( IN void *ThreadHandle, IN long Timeout ) { if (!QueueUserAPC(CancelExAPCRoutine, ThreadHandle, (UINT_PTR) Timeout)) { return RPC_S_ACCESS_DENIED; } return RPC_S_OK; } RPC_STATUS RPC_ENTRY RpcServerTestCancel ( IN RPC_BINDING_HANDLE BindingHandle OPTIONAL ) /*++ Function Name:RpcServerTestCancel Parameters: BindingHandle - This is the SCALL on which the server is trying to test for cancels. Description: New API used by a server to check if a call has been cancelled. If BindingHandle is NULL, the test is perfomed on the dispatched call on the thread on which it is called. Async servers need to call RpcServerTestCancel(RpcAsyncGetCallHandle(pAsync)) Returns: RPC_S_OK: The call was cancelled RPC_S_CALL_IN_PROGRESS: The call was not cancelled RPC_S_INVALID_BINDING: The handle is invalid RPC_S_NO_CALL_ACTIVE: No call is active on this thread --*/ { CALL *Call; if (BindingHandle == 0) { Call = (CALL *) RpcpGetThreadContext(); if (Call == 0) { #if DBG PrintToDebugger("RPC: RpcServerTestCancel: no call active\n"); #endif return RPC_S_NO_CALL_ACTIVE; } } else { Call = (CALL *) BindingHandle; if (Call->InvalidHandle(SCALL_TYPE)) { #if DBG PrintToDebugger("RPC: RpcServerTestCancel: bad handle\n"); #endif return RPC_S_INVALID_BINDING; } } if (Call->TestCancel()) { return RPC_S_OK; } return RPC_S_CALL_IN_PROGRESS; } RPC_STATUS RPC_ENTRY RpcTestCancel( ) /*++ Function Name:RpcTestCancel Parameters: Description: This function is here only for backward compatibility. The new API to test for cancels is RpcServerTestCancel. Returns: --*/ { return RpcServerTestCancel(0); } void RPC_ENTRY RpcServerYield () { THREAD * ThreadInfo = RpcpGetThreadPointer(); ASSERT(ThreadInfo); if (ThreadInfo) { ThreadInfo->YieldThread(); } } THREAD::THREAD ( IN THREAD_PROC Procedure, IN void * Parameter, OUT RPC_STATUS * Status #ifdef RPC_OLD_IO_PROTECTION ) : ProtectCount(1), ReleaseCount(0), #else ) : #endif ThreadEvent(Status) /*++ Routine Description: We construct a thread in this method. It is a little bit weird, because we need to be able to clean things up if we cant create the thread. Arguments: Procedure - Supplies the procedure which the new thread should execute. Parameter - Supplies a parameter to be passed to the procedure. RpcStatus - Returns the status of the operation. This will be set to RPC_S_OUT_OF_THREADS if we can not create a new thread. --*/ { unsigned long ThreadIdentifier; HANDLE ImpersonationToken, NewToken = 0; NTSTATUS NtStatus; CommonConstructor(); SavedProcedure = Procedure; SavedParameter = Parameter; HandleToThread = 0; if (*Status != RPC_S_OK) { return; } if (IsServerSideDebugInfoEnabled()) { DebugCell = (DebugThreadInfo *) AllocateCell(&DebugCellTag); if (DebugCell == NULL) { *Status = RPC_S_OUT_OF_MEMORY; return; } else { DebugCell->TypeHeader = 0; DebugCell->Type = dctThreadInfo; DebugCell->Status = dtsAllocated; } } // // If there is a token on the calling thread, null it out // if (OpenThreadToken (GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_QUERY, TRUE, &ImpersonationToken) == FALSE) { if ( GetLastError() == ERROR_NO_TOKEN ) { ImpersonationToken = 0 ; } else { // // More interesting. There may be a token, and // we can't open it. Note that the anonymous token can't be opened // this way. Complain: // #if DBG PrintToDebugger( "RPC : OpenThreadToken returned %d\n", GetLastError() ); #endif ASSERT(0); *Status = RPC_S_ACCESS_DENIED; return; } } else { NtStatus = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &NewToken, sizeof(HANDLE)); if (!NT_SUCCESS(NtStatus)) { *Status = RPC_S_ACCESS_DENIED; #if DBG PrintToDebugger("RPC : NtSetInformationThread : %lx\n", NtStatus); #endif ASSERT(0); CloseHandle(ImpersonationToken); return; } } HandleToThread = CreateThread(0, DefaultThreadStackSize, (LPTHREAD_START_ROUTINE) ThreadStartRoutine, this, 0, &ThreadIdentifier); if ( HandleToThread == 0 ) { *Status = RPC_S_OUT_OF_THREADS; } if (ImpersonationToken) { // // If there was a token, restore it. // NtStatus = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &ImpersonationToken, sizeof(HANDLE)); #if DBG if (!NT_SUCCESS(NtStatus)) { PrintToDebugger("RPC : NtSetInformationThread : %lx\n", NtStatus); } #endif // DBG CloseHandle(ImpersonationToken); } } THREAD::THREAD ( OUT RPC_STATUS * Status #ifdef RPC_OLD_IO_PROTECTION ) : ProtectCount(1), ReleaseCount(0), #else ) : #endif ThreadEvent(Status) /*++ Routine Description: This overloaded constructor is called only by the main app thread. this is needed because in WMSG we will be dispatching in the context of main app thread. Arguments: RpcStatus - Returns the status of the operation --*/ { HANDLE hProcess ; CommonConstructor(); SavedProcedure = 0; SavedParameter = 0; HandleToThread = 0; if (*Status != RPC_S_OK) { return; } hProcess = GetCurrentProcess() ; if (!DuplicateHandle(hProcess, GetCurrentThread(), hProcess, &HandleToThread, 0, FALSE, DUPLICATE_SAME_ACCESS)) { *Status = RPC_S_OUT_OF_MEMORY ; return; } RpcpSetThreadPointer(this); *Status = RPC_S_OK ; } void THREAD::CommonConstructor ( ) /*++ Function Name:CommonConstructor Parameters: Description: Returns: --*/ { CancelTimeout = RPC_C_CANCEL_INFINITE_TIMEOUT; Context = 0; SecurityContext = 0; ClearCallCancelledFlag(); fAsync = FALSE; ExtendedStatus = RPC_S_OK; DebugCell = NULL; ThreadEEInfo = NULL; NDRSlot = NULL; CachedLrpcCall = NULL; LastSuccessfullyDestroyedContext = NULL; CachedWaiterPtr = NULL; CachedWaiterAvailable = TRUE; CachedEEInfoBlock = NULL; ParametersOfCachedEEInfo = 0; ActiveCall = 0; for (int i = 0; i < 4; i++) { BufferCache[i].pList = 0; BufferCache[i].cBlocks = 0; } #ifdef CHECK_MUTEX_INVERSION ConnectionMutexHeld = 0; #endif } THREAD::~THREAD ( ) { ASSERT (0 == SecurityContext); #ifdef RPC_OLD_IO_PROTECTION ASSERT(ProtectCount == ReleaseCount.GetInteger()); #endif if (CachedWaiterPtr && (CachedWaiterPtr != &CachedWaiter)) SWMRLock::FreeWaiterCache(&CachedWaiterPtr); if (DebugCell != NULL) { FreeCell(DebugCell, &DebugCellTag); } if ( HandleToThread != 0 ) { CloseHandle(HandleToThread); HandleToThread = UlongToPtr(0xbaadbaad); } if (CachedLrpcCall) { delete CachedLrpcCall; } if (ThreadEEInfo) PurgeEEInfo(); if (CachedEEInfoBlock) FreeEEInfoRecordShallow(CachedEEInfoBlock); gBufferCache->ThreadDetach(this); } void * THREAD::ThreadHandle ( ) /*++ Function Name:ThreadHandle Parameters: Description: Returns: --*/ { while ( HandleToThread == 0 ) { PauseExecution(100L); } return(HandleToThread); } RPC_STATUS THREAD::SetCancelTimeout ( IN long Timeout ) /*++ Function Name:SetCancelTimeout Parameters: Description: Returns: --*/ { CancelTimeout = Timeout; return RPC_S_OK; } void SetExtendedError ( IN RPC_STATUS Status ) { THREAD *pThread = ThreadSelf(); if (pThread == 0) { return; } pThread->SetExtendedError(Status); } RPC_STATUS I_RpcGetExtendedError ( ) { THREAD *pThread = ThreadSelf(); if (pThread == 0) { return RPC_S_OUT_OF_MEMORY; } return pThread->GetExtendedError(); } HANDLE RPC_ENTRY I_RpcTransGetThreadEvent( ) /*++ Routine Description: Returns the receive event specific to this thread. Arguments: None Return Value: Returns the send event. This function will not fail --*/ { THREAD *pThis = RpcpGetThreadPointer(); ASSERT(pThis); ASSERT(pThis->ThreadEvent.EventHandle); return(pThis->ThreadEvent.EventHandle); } RPC_STATUS THREAD::CancelCall ( IN BOOL fTimeoutValid, IN long Timeout ) { RPC_STATUS Status; if (fTimeoutValid) { CancelTimeout = Timeout; } SetCallCancelledFlag(); if (ActiveCall) { Status = ActiveCall->Cancel(HandleToThread); } else { Status = RPC_S_NO_CALL_ACTIVE; } return Status; } RPC_STATUS THREAD::RegisterForCancels ( IN CALL *Call ) { Call->NestingCall = ActiveCall; ActiveCall = Call; return RPC_S_OK; } void THREAD::UnregisterForCancels ( ) { if (ActiveCall) { ActiveCall = ActiveCall->NestingCall; } // // Need to think about when we should cancel the next call if ActiveCall != 0 // } void THREAD::PurgeEEInfo ( void ) { ASSERT(ThreadEEInfo != NULL); FreeEEInfoChain(ThreadEEInfo); ThreadEEInfo = NULL; } VOID RPC_ENTRY CancelAPCRoutine ( IN ULONG_PTR Timeout ) { RPC_STATUS Status; THREAD *Thread = ThreadSelf() ; if (Thread == 0) { return; } Status = Thread->CancelCall(); #if DBG if (Status != RPC_S_OK) { PrintToDebugger("RPC: CancelCall failed %d\n", Status); } #endif } void THREAD::GetWaiterCache ( OUT SWMRWaiter **WaiterCache, IN SCALL *SCall, IN SWMRWaiterType WaiterType ) { ASSERT(WaiterCache != NULL); if (CachedWaiterPtr) { LogEvent(SU_THREAD, EV_POP, CachedWaiterPtr->hEvent, CachedWaiterPtr, 0, 1, 1); // if we have something in the cache, we may as well use it *WaiterCache = CachedWaiterPtr; CachedWaiterPtr = NULL; } else if ((WaiterType == swmrwtWriter) && SCall->IsSyncCall() && CachedWaiterAvailable) { // the cache is empty, but this is exclusive, sync usage, so we can // borrow the thread event and cook up a waiter. We also make sure that // we aren't using the CachedWaiter already (such as in the multi-context handle case). CachedWaiterAvailable = FALSE; SWMRWaiter::CookupWaiterFromEventAndBuffer(&CachedWaiter, WaiterType, ThreadEvent.EventHandle); *WaiterCache = &CachedWaiter; } else { // the cache is empty and this is either shared or async usage so we can't // use the thread buffer/event. *WaiterCache = NULL; } } void THREAD::FreeWaiterCache ( IN OUT SWMRWaiter **WaiterCache ) { ASSERT(WaiterCache != NULL); if (*WaiterCache == &CachedWaiter) { ASSERT(CachedWaiterAvailable == FALSE); // We are done using the CachedWaiter, indicate that it is avaliable CachedWaiterAvailable = TRUE; } else if (*WaiterCache) // if we got something in the cache and this is not a cooked up item { if (CachedWaiterPtr) { // if our cache is already full, just free the new waiter SWMRLock::FreeWaiterCache(WaiterCache); } else { // store it CachedWaiterPtr = *WaiterCache; LogEvent(SU_THREAD, EV_PUSH, CachedWaiterPtr->hEvent, CachedWaiterPtr, 0, 1, 1); *WaiterCache = NULL; } } } VOID RPC_ENTRY CancelExAPCRoutine ( IN ULONG_PTR Timeout ) { RPC_STATUS Status; THREAD *Thread = ThreadSelf() ; if (Thread == 0) { return; } Status = Thread->CancelCall(TRUE, (long) Timeout); #if DBG if (Status != RPC_S_OK) { PrintToDebugger("RPC: CancelCall failed %d\n", Status); } #endif } void RpcpRaiseException ( IN RPC_STATUS exception ) { if ( exception == STATUS_ACCESS_VIOLATION ) { exception = ERROR_NOACCESS; } RaiseException(exception, EXCEPTION_NONCONTINUABLE, 0, 0); ASSERT(0); } void RPC_ENTRY RpcRaiseException ( IN RPC_STATUS exception ) { NukeStaleEEInfoIfNecessary(exception); RpcpRaiseException(exception); }