void ObjectMemory::deallocate(OTE* ote) { #ifdef _DEBUG ASSERT(!ote->isFree()); if (Interpreter::executionTrace) { tracelock lock(TRACESTREAM); TRACESTREAM << ote << " (" << hex << (UINT)ote << "), refs " << dec << (int)ote->m_count << ", is being deallocated" << endl; } #endif ASSERT(!isPermanent(ote)); // We can have up to 256 different destructors (8 bits) switch (ote->heapSpace()) { case OTEFlags::NormalSpace: freeChunk(ote->m_location); releasePointer(ote); break; case OTEFlags::VirtualSpace: ::VirtualFree(static_cast<VirtualObject*>(ote->m_location)->getHeader(), 0, MEM_RELEASE); releasePointer(ote); break; case OTEFlags::BlockSpace: Interpreter::m_otePools[Interpreter::BLOCKPOOL].deallocate(ote); break; case OTEFlags::ContextSpace: // Return it to the interpreter's free list of contexts Interpreter::m_otePools[Interpreter::CONTEXTPOOL].deallocate(ote); break; case OTEFlags::DWORDSpace: Interpreter::m_otePools[Interpreter::DWORDPOOL].deallocate(ote); break; case OTEFlags::HeapSpace: //_asm int 3; HARDASSERT(FALSE); break; case OTEFlags::FloatSpace: Interpreter::m_otePools[Interpreter::FLOATPOOL].deallocate(ote); break; case OTEFlags::PoolSpace: { MWORD size = ote->sizeOf(); HARDASSERT(size <= MaxSmallObjectSize); freeSmallChunk(ote->m_location, size); releasePointer(ote); } break; default: ASSERT(false); } }
// The receiver has terminated. Remove from the Processor's Pending Terminations // list. Answer whether the process was actually on that list. void OverlappedCall::RemoveFromPendingTerminations() { ProcessOTE* oteProc = pendingTerms()->remove(m_oteProcess); if (oteProc == m_oteProcess) { Process* myProc = oteProc->m_location; HARDASSERT(myProc->Thread() == this); myProc->ClearThread(); // Return it to the scheduling queues - we resume it to cause a scheduling decision to be made. Interpreter::resume(oteProc); // Remove the reference that was from the pending terminations list oteProc->countDown(); } else { HARDASSERT(oteProc->isNil()); } m_oteProcess->countDown(); m_oteProcess = (ProcessOTE*)Pointers.Nil; // Remove the call from the active list as it is being destroyed - I don't think we need // the lock anymore { //CMonitorLock lock(s_listMonitor); Unlink(); } }
// Callback proc for MM timers // N.B. This routine is called from a separate thread (in Win32), rather than in // interrupt time, but careful coding is still important. The recommended // list of routines is limited to: // EnterCriticalSection ReleaseSemaphore // LeaveCriticalSection SetEvent // timeGetSystemTime timeGetTime // OutputDebugString timeKillEvent // PostMessage timeSetEvent // // "If a Win32 low-level audio callback [we are using mm timers here] shares data // with other code, a Critical Section or similar mutual exclusion mechanism should // be used to protect the integrity of the data". // Access to the asynchronous semaphore array is protected by a critical section // in the asynchronousSignal and CheckProcessSwitch routines. We don't really care // that much about the timerID void CALLBACK Interpreter::TimeProc(UINT uID, UINT /*uMsg*/, DWORD /*dwUser*/, DWORD /*dw1*/, DWORD /*dw2*/) { // Avoid firing a timer which has been cancelled (or is about to be cancelled!) // We use an InterlockedExchange() to set the value to 0 so that the main thread // can recognise that the timer has fired without race conditions if (_InterlockedExchange(reinterpret_cast<SHAREDLONG*>(&timerID), 0) != 0) { // If not previously killed (which is very unlikely except in certain exceptional // circumstances where the timer is killed at the exact moment it is about to fire) // then go ahead and signal the semaphore and the wakeup event // We mustn't access Pointers from an async thread when object memory is compacting // as the Pointer will be wrong GrabAsyncProtect(); SemaphoreOTE* timerSemaphore = Pointers.TimingSemaphore; HARDASSERT(!ObjectMemoryIsIntegerObject(timerSemaphore)); HARDASSERT(!timerSemaphore->isFree()); HARDASSERT(timerSemaphore->m_oteClass == Pointers.ClassSemaphore); // Asynchronously signal the required semaphore asynchronously, which will be detected // in sync. with the dispatching of byte codes, and properly signalled asynchronousSignalNoProtect(timerSemaphore); // Signal the timing Event, in case the idle process has put the VM to sleep SetWakeupEvent(); RelinquishAsyncProtect(); } else // An old timer (which should have been cancelled) has fired trace("Old timer %d fired, current %d\n", uID, timerID); }
void OverlappedCall::PerformCall() { // Must only be called from the overlapped thread HARDASSERT(::GetCurrentThreadId() == m_dwThreadId); // Run a call - this will finish by calling SignalCompletion, which will return to it // and it (after placing the return value on the stack) then returns here. Oop retVal = asyncDLL32Call(m_pMethod, m_nArgCount, this, &m_interpContext); HARDASSERT(m_oteProcess->m_location->Thread() == this); if (retVal != NULL) { #if TRACING == 1 { TRACELOCK(); TRACESTREAM << std::hex << GetCurrentThreadId() << L": Completed " << *m_pMethod << "; " << *this << std::endl; } #endif InterpreterRegisters& regs = Interpreter::GetRegisters(); ASSERT(m_interpContext.m_pActiveProcess == regs.m_pActiveProcess); regs.StoreSPInFrame(); } else { #if TRACING == 1 { TRACELOCK(); TRACESTREAM << std::hex << GetCurrentThreadId() << L": Call failed; " << GetLastErrorText() << std::endl; } #endif // The call failed as if it were a primitive failure - the args have // not been popped and we need to activate the backup smalltalk code. // However, since the calling Process is in a wait state, we need to // notify the main thread to wake it up m_bCallPrimitiveFailed = true; NotifyInterpreterOfCallReturn(); WaitForInterpreter(); } #ifdef _DEBUG //Interpreter::checkReferences(m_interpContext); #endif CallFinished(); // We don't use any of the old context after here, so even if this thread is immediately // reused before it gets a chance to suspend itself, it wont matter VERIFY(::SetEvent(m_hEvtCompleted)); }
// Start off the thread, answering whether it worked or not bool OverlappedCall::BeginThread() { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); HARDASSERT(m_hThread == 0); // N.B. Start it suspended m_hThread = (HANDLE)_beginthreadex( /*security=*/ NULL, /*stack_size=*/ 0, /*start_address*/ ThreadMain, /*arglist=*/ this, /*initflag=*/ 0, //CREATE_SUSPENDED, /*thrdaddr=*/ reinterpret_cast<UINT*>(&m_dwThreadId)); return m_hThread != 0; }
/////////////////////////////////////////////////////////////////////////////// // The call on the worker thread has returned to the assembler call primitive. // We can't complete the call until the main thread is ready to accept it, so // we must notify it that there is a call pending completion, and wait for it // to reactivate the blocked process associated with this overlapped call. // void OverlappedCall::OnCallReturned() { // Must always be called from the worker thread HARDASSERT(::GetCurrentThreadId() == m_dwThreadId); NotifyInterpreterOfCallReturn(); #if TRACING == 1 { TRACELOCK(); TRACESTREAM << std::hex << GetCurrentThreadId() << L": OnCallReturned, waiting to complete; " << *this << std::endl; } #endif //if (inPrim) DebugBreak(); WaitForInterpreter(); if (GetProcess()->Thread() != this || m_state != Running) { HARDASSERT(FALSE); ::DebugCrashDump(L"Terminated overlapped call got though to completion (%#x)\nPlease contact Object Arts.", GetProcess()->Thread()); RaiseException(SE_VMTERMINATETHREAD, EXCEPTION_NONCONTINUABLE, 0, NULL); } // We should now be running to the exclusion of the interpreter main thread, and no other // threads should be accessing any of the interpreter or object memory state #if TRACING == 1 { TRACELOCK(); TRACESTREAM << std::hex << GetCurrentThreadId() << L": OnCallReturned, completing; " << *this << std::endl; } #endif // OK, now ready to pop args and push result on stack (in sync with main thread) InterpreterRegisters& activeContext = Interpreter::GetRegisters(); // The process must be the active one in order to complete HARDASSERT(m_interpContext.m_pActiveProcess == activeContext.m_pActiveProcess); if (m_interpContext.m_stackPointer != activeContext.m_stackPointer) { DebugCrashDump(L"Overlapped call stack modified before could complete!"); DEBUGBREAK(); } // Our event will be signalled again when we return to the }
void OverlappedCall::OnActivateProcess() { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); // Probably need 3 states - nothing pending, request pending, completion pending if (m_bCompletionRequestPending && CanComplete()) { m_bCompletionRequestPending = false; if (m_bCallPrimitiveFailed) { m_bCallPrimitiveFailed = false; ASSERT(m_interpContext.m_stackPointer == Interpreter::m_registers.m_stackPointer); ASSERT(m_interpContext.m_basePointer == Interpreter::m_registers.m_basePointer); Interpreter::m_registers.m_oopNewMethod = m_interpContext.m_oopNewMethod; Interpreter::activateNewMethod(m_interpContext.m_oopNewMethod->m_location); } // Let the overlapped thread continue ::SetEvent(m_hEvtGo); // And wait for it to finish (non-alertable since we don't want other completions to interfere) DWORD dwRet = ::WaitForSingleObject(m_hEvtCompleted, INFINITE); if (dwRet != WAIT_OBJECT_0) trace(L"%#x: OverlappedCall(%#x) completion wait terminated abnormally with %#x\n", GetCurrentThreadId(), this, dwRet); } }
void ObjectMemory::PopulateZct() { HARDASSERT(m_nZctEntries <= 0); // To build the new Zct all we need do is countDown() everything in the current process // stack. Any counts that dropped to zero, will get put back in the new Zct. m_nZctEntries = 0; #ifdef _DEBUG bool bLast = alwaysReconcileOnAdd; alwaysReconcileOnAdd = false; #endif // This will populate the Zct with all objects ref'd only from the active process stack Interpreter::DecStackRefs(); #ifdef _DEBUG alwaysReconcileOnAdd = bLast; //CHECKREFSNOFIX #endif if (m_nZctEntries > (m_nZctHighWater - m_nZctHighWater/4)) { // More than 75% full, then grow it GrowZct(); } else if ((m_nZctHighWater > (int)ZctMinSize) && (m_nZctEntries < m_nZctHighWater/4)) { // Less than 25% full, so shrink it ShrinkZct(); } // Reconciliation complete m_bIsReconcilingZct = false; }
void OverlappedCall::Init() { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); m_hEvtGo = NewAutoResetEvent(); m_hEvtCompleted = NewAutoResetEvent(); BeginThread(); }
bool OverlappedCall::CanComplete() { // Only Process safe if called from main thread HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); InterpreterRegisters& activeContext = Interpreter::GetRegisters(); HARDASSERT(m_interpContext.m_pActiveProcess == activeContext.m_pActiveProcess); if (m_nSuspendCount > 0 || m_state != Running) return false; if (m_interpContext.m_pActiveFrame != activeContext.m_pActiveFrame) return false; HARDASSERT(m_interpContext.m_stackPointer == activeContext.m_stackPointer); return true; }
bool OverlappedCall::Initiate(CompiledMethod* pMethod, unsigned argCount) { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); #if TRACING == 1 { TRACELOCK(); TRACESTREAM << std::hex << GetCurrentThreadId() << L": Initiate; " << *this << std::endl; } #endif // Note that the callDepth member examined by IsInCall() method is only accessed // from the main thread, or when the main thread is blocked waiting for call // completion on the worker thread, therefore it needs no synchronisation if (IsInCall()) // Nested overlapped calls are not currently supported return false; m_nCallDepth++; // Now save down contextual information m_pMethod = pMethod; m_nArgCount = argCount; ASSERT(m_oteProcess == Interpreter::actualActiveProcessPointer()); // Copy context from Interpreter // ?? Not sure we'll need all this m_interpContext = Interpreter::GetRegisters(); // As we are about to suspend the process, we must also store down the active frame into // the suspended frame, and update the active frame with the current IP/SP. m_interpContext.PrepareToSuspendProcess(); Process* proc = m_oteProcess->m_location; // Block the process on its own private Semaphore - this simplifies the completion // handling since all that is needed is to async signal the Semaphore from the // background thread, and then let the process activation code (in process.cpp) // spot that an overlapped call completion is pending. However it does create // a ref. count issue since the Semaphore may be the only ref. to the process, and // the process is probably the only ref. to the Semaphore. Interpreter::QueueProcessOn(m_oteProcess, reinterpret_cast<LinkedListOTE*>(proc->OverlapSemaphore())); // OK to start the async. operation now // We don't use Suspend/Resume because if thread is not suspended yet // (i.e. it hasn't reached its SuspendThread() call), then calling Resume() here // will do nothing, and the thread will suspend itself for ever! ::SetEvent(m_hEvtGo); TODO("Try a deliberate SwitchToThread here to see effect of call completing before we reschedule") // Reschedule as we probably need another process to run if (Interpreter::schedule() == m_oteProcess) DebugBreak(); //CHECKREFERENCES return true; }
void ObjectMemory::GrowZct() { TRACE("ZCT overflow at %d entries (%d in use), growing to %d\n", m_nZctHighWater, m_nZctEntries, m_nZctHighWater*2); m_nZctHighWater <<= 1; // Reserve and high water must be powers of 2, so this should be unless about to overflow reserve HARDASSERT((DWORD)m_nZctHighWater <= ZctReserve); m_pZct = static_cast<OTE**>(::VirtualAlloc(m_pZct, m_nZctHighWater*sizeof(OTE*), MEM_COMMIT, PAGE_READWRITE)); if (!m_pZct) RaiseFatalError(IDP_ZCTRESERVEFAIL, 2, m_nZctHighWater, ZctReserve); }
// Allocate an OverlappedCall from the pool, or create a new one if none available OverlappedCall* OverlappedCall::New(ProcessOTE* oteProcess) { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); //CMonitorLock lock(s_listMonitor); OverlappedCall* answer = new OverlappedCall(oteProcess); s_activeList.AddFirst(answer); return answer; }
OTE* __fastcall ObjectMemory::recursiveFree(OTE* rootOTE) { HARDASSERT(!isIntegerObject(rootOTE)); HARDASSERT(!isPermanent(rootOTE)); HARDASSERT(!rootOTE->isFree()); HARDASSERT(rootOTE->m_flags.m_count == 0); #ifndef _AFX if (rootOTE->isFinalizable()) { finalize(rootOTE); rootOTE->beUnfinalizable(); } else #endif { // Deal with the class first, as this is now held in the OTE recursiveCountDown(reinterpret_cast<POTE>(rootOTE->m_oteClass)); if (rootOTE->isPointers()) { // Recurse through referenced objects as necessary const MWORD lastPointer = rootOTE->getWordSize(); Oop* pFields = reinterpret_cast<Oop*>(rootOTE->m_location); // Start after the header (only includes size now, which is not an Oop) for (MWORD i = ObjectHeaderSize; i < lastPointer; i++) { Oop fieldPointer = pFields[i]; if (!isIntegerObject(fieldPointer)) { OTE* fieldOTE = reinterpret_cast<OTE*>(fieldPointer); recursiveCountDown(fieldOTE); } } } deallocate(rootOTE); } return rootOTE; // Important for some assembler routines - will be non-zero, so can act as TRUE }
void OverlappedCall::ReincrementProcessReferences() { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); //CMonitorLock lock(s_listMonitor); OverlappedCall* next = s_activeList.First(); while (next) { next->m_oteProcess->countUp(); next = next->Next(); } }
// Attempt to resume the thread DWORD OverlappedCall::Resume() { HARDASSERT(::GetCurrentThreadId() != m_dwThreadId); #if TRACING == 1 { tracelock lock(::thinDump); ::thinDump << std::hex << GetCurrentThreadId()<< L": Resume; " << *this << std::endl; } #endif InterlockedDecrement(&m_nSuspendCount); return ResumeThread(); }
void OverlappedCall::MarkRoots() { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); //CMonitorLock lock(s_listMonitor); OverlappedCall* next = s_activeList.First(); while (next) { ObjectMemory::MarkObjectsAccessibleFromRoot(reinterpret_cast<POTE>(next->m_oteProcess)); next = next->Next(); } }
OverlappedCall::~OverlappedCall() { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); #if TRACING == 1 { TRACELOCK(); TRACESTREAM << std::hex << GetCurrentThreadId() << L": Destroy " << *this << std::endl; } #endif //HARDASSERT(m_oteProcess->isNil()); }
Oop* __fastcall Interpreter::primitiveAsyncDLL32Call(Oop* const, unsigned argCount) { CompiledMethod* method = m_registers.m_oopNewMethod->m_location; #if TRACING == 1 { TRACELOCK(); TRACESTREAM << L"primAsync32: Prepare to call " << *method << L" from " << Interpreter::actualActiveProcessPointer() << std::endl; } #endif OverlappedCall* pCall = OverlappedCall::Do(method, argCount); if (pCall == NULL) // Nested overlapped calls are not supported return primitiveFailure(0); HARDASSERT(newProcessWaiting()); // The overlapped call may already have returned, in which case a process switch // will not occur, so we must notify the overlapped call that it can complete as // it will not receive a notification from either finishing the handling of an interrupt // or by switching back to the process if (!CheckProcessSwitch()) { HARDASSERT(pCall->m_nCallDepth == 1); pCall->OnActivateProcess(); } #if TRACING == 1 { TRACELOCK(); TRACESTREAM << L"registers.sp = " << m_registers.m_stackPointer<< L" frame.sp = " << m_registers.m_pActiveFrame->stackPointer() << std::endl; } #endif return m_registers.m_stackPointer; }
// Note that this is only called on shutdown void OverlappedCall::TerminateThread() { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); QueueTerminate(); DWORD dwWaitRet = ::WaitForSingleObject(m_hThread, s_dwTerminateTimeout); dwWaitRet; #if TRACING == 1 { tracelock lock(::thinDump); ::thinDump << GetCurrentThreadId()<< L": " << *this<< L" terminated (" << dwWaitRet<< L")" << std::endl; } #endif }
void OverlappedCall::Term() { HARDASSERT(::GetCurrentThreadId() == m_dwThreadId); if (m_hThread) { ::CloseHandle(m_hThread); m_hThread = NULL; ::CloseHandle(m_hEvtGo); m_hEvtGo = NULL; ::CloseHandle(m_hEvtCompleted); m_hEvtCompleted = NULL; } }
// Static entry point required by _beginthreadex() unsigned __stdcall OverlappedCall::ThreadMain(void* pvClosure) { HARDASSERT(::GetCurrentThreadId() != Interpreter::MainThreadId()); // Take over the initial reference (while thread is running, // this keeps a reference on the OverlappedCall object which will then // be released when the smart pointer goes out of scope at the end of // this function, at which point the call will normally be deleted, // unless that is something else is holding a reference to it.) OverlappedCallPtr pThis(static_cast<OverlappedCall*>(pvClosure), false); // N.B. OC may not actually deleted as may still be ref'd from APC queue, etc return pThis->TryMain(); }
void Interpreter::CancelSampleTimer() { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); if (m_hSampleTimer != NULL) { if (!::DeleteTimerQueueTimer(NULL, m_hSampleTimer, INVALID_HANDLE_VALUE)) { DWORD dwErr = GetLastError(); dwErr; } m_hSampleTimer = NULL; } }
// Answer the index of the last occuppied OT entry unsigned __stdcall ObjectMemory::lastOTEntry() { HARDASSERT(m_pOT); // HARDASSERT(m_nInCritSection > 0); unsigned i = m_nOTSize-1; const OTE* pOT = m_pOT; while (pOT[i].isFree()) { ASSERT(i > 0); i--; } return i; }
// Compact an object by updating all the Oops it contains using the // forwarding pointers in the old OT. void ObjectMemory::compactObject(OTE* ote) { // We shouldn't come in here unless OTE already fixed for this object HARDASSERT(ote >= m_pOT && ote < m_pFreePointerList); // First fix up the class (remember that the new object pointer is stored in the // old one's object location slot compactOop(ote->m_oteClass); if (ote->isPointers()) { VariantObject* varObj = static_cast<VariantObject*>(ote->m_location); const MWORD lastPointer = ote->pointersSize(); for (MWORD i = 0; i < lastPointer; i++) { // This will get nicely optimised by the Compiler Oop fieldPointer = varObj->m_fields[i]; // We don't need to visit SmallIntegers and objects we've already visited if (!isIntegerObject(fieldPointer)) { OTE* fieldOTE = reinterpret_cast<OTE*>(fieldPointer); // If pointing at a free'd object ,then it has been moved if (fieldOTE->isFree()) { // Should be one of the old OT entries, pointing outside the o Oop movedTo = reinterpret_cast<Oop>(fieldOTE->m_location); HARDASSERT(movedTo >= (Oop)m_pOT && movedTo < (Oop)m_pFreePointerList); // Get the new OTE from the old ... varObj->m_fields[i] = movedTo; } } } } // else, we don't even need to look at the body of byte objects any more }
void OverlappedCall::compact() { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); ObjectMemory::compactOop(m_oteProcess); #if TRACING == 1 { TRACELOCK(); TRACESTREAM << std::hex << GetCurrentThreadId() << L"Compacting " << *this << std::endl; } #endif //ObjectMemory::compactOop(m_interpContext.m_oopMessageSelector); ObjectMemory::compactOop(m_interpContext.m_oopNewMethod); }
// Static initialization of async call support void OverlappedCall::Initialize() { HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); CRegKey rkOverlap; if (OpenDolphinKey(rkOverlap, L"Overlapped")==ERROR_SUCCESS) { rkOverlap.QueryDWORDValue(L"TerminateTimeout", s_dwTerminateTimeout); s_dwTerminateTimeout = max(s_dwTerminateTimeout, 100); } else { s_dwTerminateTimeout = 500; } }
void OverlappedCall::WaitForInterpreter() { // Wait for main thread to process the async signal and permit us to continue DWORD dwRet; while ((dwRet = ::WaitForSingleObjectEx(m_hEvtGo, 5000, TRUE)) != WAIT_OBJECT_0) { thinDump << std::hex << GetCurrentThreadId() << L": OnCallReturned, wait interrupted (" << dwRet << "); " << *this << std::endl; if (dwRet == WAIT_ABANDONED) { // Event deleted, etc, so terminate the thread RaiseException(SE_VMTERMINATETHREAD, EXCEPTION_NONCONTINUABLE, 0, NULL); } // Interrupted to process an APC (probably a suspend/terminate) or due to a timeout HARDASSERT(dwRet == WAIT_IO_COMPLETION || dwRet == WAIT_TIMEOUT); } }
// Static clean up async call support on shutdown void OverlappedCall::Uninitialize() { // Access to the pool lists must only be from the main thread HARDASSERT(::GetCurrentThreadId() == Interpreter::MainThreadId()); // NOTE: As the threads exit, they unlink the overlapped call object // from the active list. To do this they need to enter the list // monitor, so we have to be careful to avoid a deadlock here by not // holding that monitor while enumerating // Terminate all overlapped call threads associated with processes OverlappedCallPtr next = RemoveFirstFromList(s_activeList); while (next) { next->TerminateThread(); next = RemoveFirstFromList(s_activeList); } }
// Used by an overlapped thread to suspend itself void OverlappedCall::SuspendThread() { // Should only be called from the overlapped call thread itself for safety HARDASSERT(::GetCurrentThreadId() == m_dwThreadId); // Only really suspend if there is at least one suspend still pending (an intervening // resume may revoke the pending suspend) if (m_nSuspendCount > 0) { DWORD dwRet = ::SuspendThread(GetCurrentThread()); dwRet; #if TRACING == 1 TRACELOCK(); TRACESTREAM << std::hex << GetCurrentThreadId()<< L": Suspending (count=" << m_nSuspendCount << "); " << *this << dwRet << std::endl; } else { TRACELOCK(); TRACESTREAM << std::hex << GetCurrentThreadId()<< L": Suspend ignored (count=" << m_nSuspendCount << "); " << *this << std::endl; #endif } }