You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1784 lines
57 KiB
1784 lines
57 KiB
//+----------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1993.
|
|
//
|
|
// File: procssr.cxx
|
|
//
|
|
// Contents: CJobProcessor class implementation.
|
|
//
|
|
// Classes: CJobProcessor
|
|
//
|
|
// Functions: None.
|
|
//
|
|
// History: 25-Oct-95 MarkBl Created
|
|
// 11/16/00 Dgrube remove (dwRet >= WAIT_OBJECT_0) &&
|
|
// from "else if ((dwRet >= WAIT_OBJECT_0) && (dwRet < WAIT_ABANDONED_0))"
|
|
// since dwRet is a DWORD. It would never occur and
|
|
// is causing compile errors.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
|
|
#include "..\pch\headers.hxx"
|
|
#pragma hdrstop
|
|
#include "globals.hxx"
|
|
#include "svc_core.hxx"
|
|
#include "..\inc\resource.h"
|
|
|
|
// something to differentiate returns from RunDll32 from our returns
|
|
#define ERROR_LEVEL_OFFSET 100
|
|
|
|
// Parameters to CloseWindowEnumProc and ThreadWindowEnumProc
|
|
struct ENUMPROCPARMS
|
|
{
|
|
DWORD dwProcessId; // IN - pid of the process being closed
|
|
BOOL fWindowFound; // OUT - whether WM_CLOSE was sent to any window
|
|
};
|
|
|
|
BOOL CALLBACK CloseWindowEnumProc(HWND, LPARAM);
|
|
BOOL CALLBACK ThreadWindowEnumProc(HWND, LPARAM);
|
|
BOOL CloseWindowForProcess(CRun* pRun);
|
|
|
|
|
|
//
|
|
// Notes on the use of CRun's m_dwMaxRunTime and m_ftKill fields:
|
|
//
|
|
// m_ftKill is the time when the job processor thread monitoring the
|
|
// job should try to kill the job, if it hasn't already terminated.
|
|
// It is an absolute time. It is computed when the job is launched, based
|
|
// on a combination of (1) the duration-end of triggers that have
|
|
// TASK_TRIGGER_FLAG_KILL_AT_DURATION_END set and (2) the MaxRunTime set on
|
|
// the job itself.
|
|
// (1) can be predicted before the job runs, so it is calculated in
|
|
// GetTriggerRunTimes() and stored in m_ftKill.
|
|
// (2) is a relative time, so in many cases its end time cannot be
|
|
// predicted until the job runs. It is temporarily stored in m_dwMaxRunTime
|
|
// when the CRun object is created; but it is converted to an absolute time
|
|
// and combined with m_ftKill when the job is launched in RunJobs().
|
|
//
|
|
// Once the job is launched, m_ftKill remains the same for the lifetime of
|
|
// the CRun object, even if the job is killed because of
|
|
// TASK_FLAG_KILL_ON_IDLE_END and restarted because of
|
|
// TASK_FLAG_RESTART_ON_IDLE_RESUME.
|
|
//
|
|
// m_dwMaxRunTime is the remaining number of milliseconds that the
|
|
// CJobProcessor::PerformTask() thread will wait for the job to terminate.
|
|
// It is initialized to (m_ftKill - current time) in CJobProcessor::
|
|
// SubmitJobs() and repeatedly adjusted downwards each time the job processor
|
|
// thread wakes up. If the job is killed and restarted, m_dwMaxRunTime is
|
|
// recomputed from the original m_ftKill and the new current time.
|
|
//
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::~CJobProcessor
|
|
//
|
|
// Synopsis: Job processor destructor. This object is reference counted,
|
|
// ala class CTask inheritance. As a result, we are guaranteed
|
|
// all of this is safe to do, as no outstanding references
|
|
// remain.
|
|
//
|
|
// Arguments: N/A
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
CJobProcessor::~CJobProcessor()
|
|
{
|
|
TRACE3(CJobProcessor, ~CJobProcessor);
|
|
|
|
if (_rgHandles != NULL)
|
|
{
|
|
//
|
|
// Close the processor notification event handle & delete the handle
|
|
// array.
|
|
//
|
|
|
|
if (_rgHandles)
|
|
{
|
|
if (_rgHandles[0])
|
|
CloseHandle(_rgHandles[0]);
|
|
|
|
delete _rgHandles;
|
|
_rgHandles = NULL;
|
|
}
|
|
}
|
|
|
|
DeleteCriticalSection(&_csProcessorCritSection);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::Initialize
|
|
//
|
|
// Synopsis: Perform the initialization steps that would have otherwise
|
|
// been performed in the constructor. This method enables return
|
|
// of a status code if initialization should fail.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
CJobProcessor::Initialize(void)
|
|
{
|
|
TRACE3(CJobProcessor, Initialize);
|
|
|
|
HRESULT hr;
|
|
|
|
schAssert(_rgHandles == NULL);
|
|
|
|
//
|
|
// Create the handle array with the processor notification event handle
|
|
// as the sole element.
|
|
//
|
|
|
|
_rgHandles = new HANDLE[1];
|
|
|
|
if (_rgHandles == NULL)
|
|
{
|
|
CHECK_HRESULT(E_OUTOFMEMORY);
|
|
return(E_OUTOFMEMORY);
|
|
}
|
|
|
|
|
|
//
|
|
// Create the processor notification event and assign its handle to the
|
|
// handle array.
|
|
//
|
|
|
|
_rgHandles[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
if (_rgHandles[0] == NULL)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
CHECK_HRESULT(hr);
|
|
return(hr);
|
|
}
|
|
|
|
//
|
|
// Request a thread to service this object.
|
|
//
|
|
|
|
hr = RequestService(this);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
this->InService();
|
|
}
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::IsIdle
|
|
//
|
|
// Synopsis: This member is called during job processor pool garbage
|
|
// collection to determine if this processor can be removed
|
|
// from the pool. This method is problematic, but this is OK,
|
|
// since the worst that can happen is this processor may
|
|
// be removed from the pool prematurely, requiring use of a
|
|
// additional, redundant job processor object.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL
|
|
CJobProcessor::IsIdle(void)
|
|
{
|
|
TRACE3(CJobProcessor, IsIdle);
|
|
|
|
if ((_RequestQueue.GetCount() + _ProcessingQueue.GetCount()) == 0)
|
|
{
|
|
return(this->GetReferenceCount() == 1 ? TRUE : FALSE);
|
|
}
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::Next
|
|
//
|
|
// Synopsis: Return the next processor this object refers to. The returned
|
|
// object is AddRef()'d to reflect the new reference.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Notes: The processor pool is locked to ensure this thread is the
|
|
// sole thread accessing the pool throughout this operation.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
CJobProcessor *
|
|
CJobProcessor::Next(void)
|
|
{
|
|
TRACE3(CJobProcessor, Next);
|
|
|
|
gpJobProcessorMgr->LockProcessorPool();
|
|
|
|
CJobProcessor * pjpNext = (CJobProcessor *)CDLink::Next();
|
|
|
|
if (pjpNext != NULL)
|
|
{
|
|
pjpNext->AddRef();
|
|
}
|
|
|
|
gpJobProcessorMgr->UnlockProcessorPool();
|
|
|
|
return(pjpNext);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::Prev
|
|
//
|
|
// Synopsis: Return the previous processor this object refers to. The
|
|
// returned object is AddRef()'d to reflect the new reference.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Notes: The processor pool is locked to ensure this thread is the
|
|
// sole thread accessing the pool throughout this operation.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
CJobProcessor *
|
|
CJobProcessor::Prev(void)
|
|
{
|
|
TRACE3(CJobProcessor, Prev);
|
|
|
|
gpJobProcessorMgr->LockProcessorPool();
|
|
|
|
CJobProcessor * pjpPrev = (CJobProcessor *)CDLink::Prev();
|
|
|
|
if (pjpPrev != NULL)
|
|
{
|
|
pjpPrev->AddRef();
|
|
}
|
|
|
|
gpJobProcessorMgr->UnlockProcessorPool();
|
|
|
|
return(pjpPrev);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::PerformTask
|
|
//
|
|
// Synopsis: This is the function performed by the worker thread on the
|
|
// job processor. The processor thread enters a wait on the array
|
|
// of handles in the private data member, _rgHandles. The first
|
|
// array element is a handle to the processor notification event.
|
|
// This event is signals this thread that new jobs have been sub-
|
|
// mitted to this processor. The remaining n-1 handles in the
|
|
// array are job process handles signaled on job completion.
|
|
// When a job completes, the persisted job object is updated with
|
|
// the job's exit status code, completion time, etc.
|
|
//
|
|
// It's possible the wait for one or more jobs may time out. If
|
|
// the processor notification event wait times out, the wait is
|
|
// re-entered. If a job times out, its handle is removed from
|
|
// wait handle array and the job's job info object removed from
|
|
// the processing queue.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
CJobProcessor::PerformTask(void)
|
|
{
|
|
#define CLOSE_WAIT_TIME (3 * 60 * 1000) // 3 mins (milliseconds).
|
|
#define WAIT_TIME_DEFAULT (10 * 3 * 60 * 1000) // 30 mins (milliseconds).
|
|
|
|
TRACE3(CJobProcessor, PerformTask);
|
|
|
|
CRun * pRun;
|
|
DWORD dwObjectIndex;
|
|
|
|
//
|
|
// Initialize this thread's keep-awake count.
|
|
//
|
|
InitThreadWakeCount();
|
|
|
|
for (;;)
|
|
{
|
|
//
|
|
// Wait for job completion, timeout, or processor notification.
|
|
//
|
|
// NB : ProcessQueue count + 1 since there is no processing queue
|
|
// entry for the first handle, the new submission event
|
|
// handle.
|
|
//
|
|
// There will never be a discrepancy between the processing
|
|
// queue count and the actual number of handles in _rgHandles
|
|
// since this thread exclusively updates the processing queue.
|
|
//
|
|
|
|
DWORD dwTimeoutInterval = WAIT_TIME_DEFAULT;
|
|
DWORD cHandles = _ProcessingQueue.GetCount() + 1;
|
|
|
|
if (cHandles > 1)
|
|
{
|
|
//
|
|
// There are jobs to process.
|
|
//
|
|
// Scan job info objects in the processor queue for the minimum
|
|
// value of the job's maximum run time. This will be the wait
|
|
// time on WaitForMultipleObjects.
|
|
//
|
|
|
|
for (pRun = _ProcessingQueue.GetFirstElement();
|
|
pRun != NULL;
|
|
pRun = pRun->Next())
|
|
{
|
|
schDebugOut((DEB_USER3,
|
|
"PerformTask(0x%x) Job " FMT_TSTR " remaining time %u ms\n",
|
|
this,
|
|
pRun->GetName(),
|
|
pRun->GetMaxRunTime()));
|
|
|
|
dwTimeoutInterval = min(dwTimeoutInterval,
|
|
pRun->GetMaxRunTime());
|
|
}
|
|
}
|
|
|
|
schDebugOut((DEB_USER3,
|
|
"PerformTask(0x%x) Processor entering wait; p queue cnt(%d); " \
|
|
"wait time %u ms\n",
|
|
this,
|
|
cHandles - 1,
|
|
dwTimeoutInterval));
|
|
|
|
DWORD dwWaitTime = GetTickCount();
|
|
DWORD dwRet = WaitForMultipleObjects(cHandles,
|
|
_rgHandles,
|
|
FALSE,
|
|
dwTimeoutInterval);
|
|
|
|
//
|
|
// Serialize processor data structure access.
|
|
//
|
|
|
|
EnterCriticalSection(&_csProcessorCritSection);
|
|
|
|
//
|
|
// (Note that GetTickCount() wrap is automatically taken care of
|
|
// by 2's-complement subtraction.)
|
|
//
|
|
dwWaitTime = GetTickCount() - dwWaitTime;
|
|
|
|
schDebugOut((DEB_USER3,
|
|
"PerformTask(0x%x) Processor awake after %u ms\n",
|
|
this,
|
|
dwWaitTime));
|
|
|
|
//
|
|
// Decrement each job's max run time by the amount of time waited.
|
|
// Skip jobs with zeroed max run time values.
|
|
//
|
|
|
|
for (pRun = _ProcessingQueue.GetFirstElement();
|
|
pRun != NULL;
|
|
pRun = pRun->Next())
|
|
{
|
|
//
|
|
// NB : Jobs with infinite run times do not expire. Therefore, do
|
|
// not decrease the max run time value.
|
|
//
|
|
|
|
if (pRun->GetMaxRunTime() != 0 &&
|
|
pRun->GetMaxRunTime() != INFINITE)
|
|
{
|
|
if (pRun->GetMaxRunTime() > dwWaitTime)
|
|
{
|
|
pRun->SetMaxRunTime(pRun->GetMaxRunTime() - dwWaitTime);
|
|
}
|
|
else
|
|
{
|
|
pRun->SetMaxRunTime(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dwRet == WAIT_FAILED)
|
|
{
|
|
//
|
|
// Wait attempt failed. Shutdown the processor & bail out.
|
|
//
|
|
// BUGBUG : Should probably log this.
|
|
//
|
|
|
|
schDebugOut((DEB_ERROR,
|
|
"PerformTask(0x%x) Wait failure(0x%x) - processor " \
|
|
"shutdown initiated\n",
|
|
this,
|
|
HRESULT_FROM_WIN32(GetLastError())));
|
|
this->_Shutdown();
|
|
LeaveCriticalSection(&_csProcessorCritSection);
|
|
break;
|
|
}
|
|
|
|
if (dwRet == WAIT_TIMEOUT)
|
|
{
|
|
if (!_ProcessingQueue.GetCount() && !_RequestQueue.GetCount())
|
|
{
|
|
//
|
|
// Shutdown this processor. The wait has expired and no jobs
|
|
// are in service, nor are there new requests queued.
|
|
//
|
|
|
|
schDebugOut((DEB_TRACE,
|
|
"PerformTask(0x%x) Processor idle - shutdown " \
|
|
"initiated\n",
|
|
this));
|
|
this->_Shutdown();
|
|
LeaveCriticalSection(&_csProcessorCritSection);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// One or more jobs timed out (those with max run time values of
|
|
// zero). Close associated event handle, overwrite event handle
|
|
// array entry, then remove and destroy the associated job info
|
|
// object from the processor queue.
|
|
//
|
|
|
|
schDebugOut((DEB_USER3,
|
|
"PerformTask(0x%x) Wait timeout\n",
|
|
this));
|
|
|
|
CRun * pRunNext;
|
|
DWORD i;
|
|
for (pRun = _ProcessingQueue.GetFirstElement(), i = 1;
|
|
pRun != NULL;
|
|
pRun = pRunNext, i++)
|
|
{
|
|
pRunNext = pRun->Next();
|
|
|
|
if (pRun->GetMaxRunTime() != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Post a WM_CLOSE message to the job if this is the
|
|
// first attempt at closure. If WM_CLOSE was issued
|
|
// previously and the job is still running, resort to
|
|
// TerminateProcess.
|
|
//
|
|
|
|
if (!(pRun->IsFlagSet(RUN_STATUS_CLOSE_PENDING)))
|
|
{
|
|
pRun->SetFlag(RUN_STATUS_TIMED_OUT);
|
|
|
|
schDebugOut((DEB_ITRACE,
|
|
"PerformTask(0x%x) Forced closure; issuing " \
|
|
"WM_CLOSE to job " FMT_TSTR "\n",
|
|
this,
|
|
pRun->GetName()));
|
|
|
|
//
|
|
// Log job closure, post WM_CLOSE, then re-enter the
|
|
// wait for closure.
|
|
//
|
|
|
|
SYSTEMTIME stFinished;
|
|
GetLocalTime(&stFinished);
|
|
|
|
g_pSched->JobPostProcessing(pRun, stFinished);
|
|
|
|
|
|
/*******
|
|
|
|
|
|
// Attach to the correct desktop prior to enumerating
|
|
// the windows
|
|
//
|
|
HWINSTA hwinstaSave = NULL;
|
|
HDESK hdeskSave = NULL;
|
|
HWINSTA hwinsta = NULL;
|
|
|
|
DWORD dwTreadId = GetCurrentThreadId( );
|
|
|
|
if( NULL == dwTreadId )
|
|
{
|
|
schDebugOut((DEB_ERROR,
|
|
"CJobProcessor::PerformTask, GetCurrentThreadId " ));
|
|
}
|
|
else
|
|
{
|
|
hdeskSave = GetThreadDesktop( dwTreadId );
|
|
}
|
|
|
|
if( NULL == hdeskSave )
|
|
{
|
|
schDebugOut((DEB_ERROR,
|
|
"CJobProcessor::PerformTask, GetThreadDesktop " ));
|
|
}
|
|
else
|
|
{
|
|
hwinstaSave = GetProcessWindowStation( );
|
|
}
|
|
|
|
if( NULL == hwinstaSave )
|
|
{
|
|
schDebugOut((DEB_ERROR,
|
|
"CJobProcessor::PerformTask, GetProcessWindowStation " ));
|
|
}
|
|
|
|
|
|
hwinsta = OpenWindowStation(
|
|
pRun->GetStation( ),
|
|
TRUE,
|
|
MAXIMUM_ALLOWED );
|
|
|
|
if( NULL == hwinsta )
|
|
{
|
|
schDebugOut((DEB_ERROR,
|
|
"CJobProcessor::PerformTask, OpenWindowStation " ));
|
|
}
|
|
else if( !SetProcessWindowStation( hwinsta ) )
|
|
{
|
|
schDebugOut((DEB_ERROR,
|
|
"CJobProcessor::PerformTask, SetProcessWindowStation " ));
|
|
}
|
|
|
|
HDESK hDesk = OpenDesktop(
|
|
pRun->GetDesktop(),
|
|
0, //No hooks allowed
|
|
TRUE, //No inheritance
|
|
MAXIMUM_ALLOWED
|
|
);
|
|
|
|
if( !SetThreadDesktop( hDesk ) )
|
|
{
|
|
schDebugOut((DEB_ERROR,
|
|
"CJobProcessor::PerformTask, OpenDesktop failed, " \
|
|
"status = 0x%lx\n",
|
|
GetLastError()));
|
|
}
|
|
else
|
|
{
|
|
|
|
// Success enumerate windows else SetMaxRunTime to 0
|
|
// and ultimately kill the process (not very graceful)
|
|
//
|
|
EnumWindows(CloseWindowEnumProc, (LPARAM) &Parms);
|
|
}
|
|
********************/
|
|
|
|
|
|
pRun->SetFlag(RUN_STATUS_CLOSE_PENDING);
|
|
BOOL fFoundWindow = CloseWindowForProcess(pRun);
|
|
|
|
if (fFoundWindow)
|
|
{
|
|
pRun->SetMaxRunTime(CLOSE_WAIT_TIME);
|
|
}
|
|
else
|
|
{
|
|
schDebugOut((DEB_ITRACE, "PerformTask: no windows found\n"));
|
|
|
|
//
|
|
// If WM_CLOSE was not sent to any windows, there is no
|
|
// point waiting for the job to terminate.
|
|
// DCR: It would be polite, and perhaps help the app to
|
|
// avoid data loss (depending on the app), to send some other
|
|
// notification, such as a CTRL_C_EVENT. See bug 65251.
|
|
//
|
|
pRun->SetMaxRunTime(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
schDebugOut((DEB_ITRACE,
|
|
"PerformTask(0x%x) 2nd forced closure; issuing " \
|
|
"TerminateProcess on job " FMT_TSTR "\n",
|
|
this,
|
|
pRun->GetName()));
|
|
|
|
DWORD dwExitCode = 0;
|
|
GetExitCodeProcess(pRun->GetHandle(), &dwExitCode);
|
|
|
|
if (dwExitCode == STILL_ACTIVE)
|
|
{
|
|
TerminateProcess(pRun->GetHandle(), (UINT)-1);
|
|
}
|
|
|
|
if (i < _ProcessingQueue.GetCount()) // Ignore last
|
|
// entry.
|
|
{
|
|
CopyMemory(&_rgHandles[i],
|
|
&_rgHandles[i + 1],
|
|
sizeof(HANDLE) *
|
|
(_ProcessingQueue.GetCount() - i));
|
|
}
|
|
|
|
i--; // Reflect overwritten array entry.
|
|
|
|
//
|
|
// Remove CRun object from the processing queue
|
|
// and destroy it.
|
|
//
|
|
|
|
_ProcessingQueue.RemoveElement(pRun);
|
|
|
|
if (pRun->IsFlagSet(TASK_FLAG_SYSTEM_REQUIRED))
|
|
{
|
|
//
|
|
// This thread is monitoring one less system-
|
|
// required job
|
|
//
|
|
WrapSetThreadExecutionState(FALSE,
|
|
"processor - forced close of task");
|
|
}
|
|
|
|
if (pRun->IsFlagSet(RUN_STATUS_RESTART_ON_IDLE_RESUME)
|
|
&& pRun->GetWait() > 0)
|
|
{
|
|
//
|
|
// Ask the main thread to move it back into the
|
|
// idle wait queue
|
|
//
|
|
pRun->ClearFlag(JOB_INTERNAL_FLAG_MASK);
|
|
pRun->SetMaxRunTime(INFINITE);
|
|
g_pSched->SubmitIdleRun(pRun);
|
|
//
|
|
// Note that we changed (reduced) pRun's MaxRunTime
|
|
// when we killed it. However we didn't mess with
|
|
// the kill time. The MaxRunTime will be recomputed
|
|
// based on the same kill time as before when this
|
|
// run is next submitted to a processor.
|
|
//
|
|
}
|
|
else
|
|
{
|
|
delete pRun;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (dwRet < WAIT_ABANDONED_0)
|
|
{
|
|
//
|
|
// One or more jobs completed.
|
|
//
|
|
|
|
dwObjectIndex = dwRet - WAIT_OBJECT_0;
|
|
|
|
if (dwObjectIndex == 0)
|
|
{
|
|
//
|
|
// Processor notification event signaled. Either new jobs
|
|
// have been submitted or the service is stopping.
|
|
//
|
|
|
|
if (IsServiceStopping())
|
|
{
|
|
//
|
|
// Service stop. Shutdown the processor.
|
|
//
|
|
|
|
schDebugOut((DEB_TRACE,
|
|
"PerformTask(0x%x) Service stop - processor " \
|
|
"shutdown initiated\n",
|
|
this));
|
|
this->_Shutdown();
|
|
LeaveCriticalSection(&_csProcessorCritSection);
|
|
break;
|
|
}
|
|
|
|
ResetEvent(_rgHandles[0]);
|
|
|
|
//
|
|
// Move jobs from request to processing queue.
|
|
//
|
|
|
|
_ProcessRequests();
|
|
|
|
//
|
|
// Unblock the thread that called SubmitJobs().
|
|
// (We happen to know it's the thread in the main service
|
|
// loop so we can use the global event. A cleaner model
|
|
// would be to either pass the handle of the event to
|
|
// SubmitJobs, or use an event private to SubmitJobs and
|
|
// PerformTask.)
|
|
//
|
|
g_pSched->Unblock();
|
|
}
|
|
else if (dwObjectIndex < cHandles)
|
|
{
|
|
//
|
|
// A job has finished (or closed).
|
|
// Find the CRun object associated with the handle.
|
|
//
|
|
|
|
if ((pRun = _ProcessingQueue.FindJob(
|
|
_rgHandles[dwObjectIndex])) != NULL)
|
|
{
|
|
pRun->ClearFlag(RUN_STATUS_RUNNING);
|
|
|
|
if (!(pRun->GetFlags() & RUN_STATUS_CLOSE_PENDING))
|
|
{
|
|
schDebugOut((DEB_USER3,
|
|
"PerformTask(0x%x) Job " FMT_TSTR " completed\n",
|
|
this,
|
|
pRun->GetName()));
|
|
|
|
//
|
|
// The job finished on its own. Log completion
|
|
// status. Fetch job completion time for pending log
|
|
// entry.
|
|
//
|
|
|
|
pRun->SetFlag(RUN_STATUS_FINISHED);
|
|
|
|
SYSTEMTIME stFinished;
|
|
GetLocalTime(&stFinished);
|
|
|
|
//
|
|
// Standard job post processing.
|
|
//
|
|
|
|
g_pSched->JobPostProcessing(pRun, stFinished);
|
|
}
|
|
else
|
|
{
|
|
// (NOTE: This may not be necessary - this info
|
|
// is not used yet.)
|
|
//
|
|
pRun->SetFlag(RUN_STATUS_CLOSED);
|
|
}
|
|
|
|
//
|
|
// Fix up handle array to reflect processed entry.
|
|
//
|
|
|
|
if (dwObjectIndex < _ProcessingQueue.GetCount())
|
|
{
|
|
CopyMemory(&_rgHandles[dwObjectIndex],
|
|
&_rgHandles[dwObjectIndex + 1],
|
|
sizeof(HANDLE) *
|
|
(_ProcessingQueue.GetCount() - dwObjectIndex));
|
|
}
|
|
|
|
//
|
|
// Remove CRun object from the processing queue and
|
|
// destroy it.
|
|
//
|
|
|
|
_ProcessingQueue.RemoveElement(pRun);
|
|
|
|
if (pRun->IsFlagSet(TASK_FLAG_SYSTEM_REQUIRED))
|
|
{
|
|
//
|
|
// This thread is monitoring one less system-
|
|
// required job
|
|
//
|
|
WrapSetThreadExecutionState(FALSE,
|
|
"processor - last task exited");
|
|
}
|
|
|
|
if (pRun->IsFlagSet(RUN_STATUS_CLOSE_PENDING) &&
|
|
pRun->IsFlagSet(RUN_STATUS_RESTART_ON_IDLE_RESUME) &&
|
|
pRun->GetWait() > 0)
|
|
{
|
|
//
|
|
// Ask the main thread to move it back into the
|
|
// idle wait queue
|
|
//
|
|
pRun->ClearFlag(JOB_INTERNAL_FLAG_MASK);
|
|
pRun->SetMaxRunTime(INFINITE);
|
|
g_pSched->SubmitIdleRun(pRun);
|
|
//
|
|
// Note that we changed (reduced) pRun's MaxRunTime
|
|
// when we killed it. However we didn't mess with
|
|
// the kill time. The MaxRunTime will be reset to
|
|
// match the same kill time as before when this run
|
|
// is next submitted to a processor.
|
|
//
|
|
}
|
|
else
|
|
{
|
|
delete pRun;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Index out of range. This should never happen.
|
|
//
|
|
|
|
schDebugOut((DEB_ERROR,
|
|
"PerformTask(0x%x) Wait array index (%d) out of " \
|
|
"range! Handle count(%d)\n",
|
|
this,
|
|
dwObjectIndex,
|
|
cHandles));
|
|
|
|
schAssert(0);
|
|
|
|
LeaveCriticalSection(&_csProcessorCritSection);
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Clueless how we got here. Just continue the wait.
|
|
//
|
|
|
|
schAssert(!"How did this branch get evaluated?");
|
|
LeaveCriticalSection(&_csProcessorCritSection);
|
|
continue;
|
|
}
|
|
|
|
LeaveCriticalSection(&_csProcessorCritSection);
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: CloseWindowForProcess
|
|
//
|
|
// Synopsis: launches separate process to issue WM_CLOSE to main window
|
|
//
|
|
// Arguments: CRun for the job of interest
|
|
//
|
|
// Return value: true if a window was found.
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL CloseWindowForProcess(CRun* pRun)
|
|
{
|
|
// processID == 0 means we can't find / don't have a proc id
|
|
if (pRun->GetProcessId() == 0)
|
|
return FALSE;
|
|
|
|
BOOL fWindowClosed = false;
|
|
|
|
// okay - it might not be the default desktop, but is the best guess we have
|
|
// if we guess wrong, no harm done - the PID is unique & we won't close the wrong proc
|
|
BOOL fIsDefaultWinsta = (NULL == pRun->GetStation()) || (NULL == pRun->GetDesktop());
|
|
|
|
// create process in proper winsta
|
|
// process is rundll32 to invoke CloseProc
|
|
|
|
WCHAR dllPath[MAX_PATH +2];
|
|
WCHAR runDllPath[MAX_PATH +2];
|
|
BOOL bLaunched = false;
|
|
HANDLE hProc = INVALID_HANDLE_VALUE;
|
|
|
|
if (ExpandEnvironmentStringsW(L"%windir%\\system32\\rundll32.exe", runDllPath, MAX_PATH +1) &&
|
|
GetModuleFileNameW(g_hInstance, dllPath, MAX_PATH +1))
|
|
{
|
|
WCHAR templitt[] = L"%s %s,CloseProc %u";
|
|
|
|
size_t cchCommandLine = wcslen(runDllPath) + wcslen(dllPath) + wcslen(templitt) + 20;
|
|
WCHAR* pCommandLine = new WCHAR[cchCommandLine];
|
|
|
|
size_t cchDesktop;
|
|
WCHAR* pDesktop = NULL;
|
|
if (fIsDefaultWinsta)
|
|
pDesktop = L"WinSta0\\Default";
|
|
else
|
|
{
|
|
cchDesktop = wcslen(pRun->GetStation()) + wcslen(pRun->GetDesktop()) + 5;
|
|
pDesktop = new WCHAR[cchDesktop];
|
|
}
|
|
|
|
if (pCommandLine && pDesktop)
|
|
{
|
|
StringCchPrintf(pCommandLine, cchCommandLine, templitt, runDllPath, dllPath, pRun->GetProcessId());
|
|
|
|
if (!fIsDefaultWinsta)
|
|
{
|
|
StringCchCopy(pDesktop, cchDesktop, pRun->GetStation());
|
|
StringCchCat(pDesktop, cchDesktop, L"\\");
|
|
StringCchCat(pDesktop, cchDesktop, pRun->GetDesktop());
|
|
}
|
|
// else - pDesktop was init'd above
|
|
|
|
PROCESS_INFORMATION procInfo;
|
|
STARTUPINFO startInfo;
|
|
SecureZeroMemory(&startInfo, sizeof(startInfo));
|
|
startInfo.lpDesktop = pDesktop;
|
|
startInfo.cb = sizeof(STARTUPINFO);
|
|
|
|
bLaunched = CreateProcessW(NULL, pCommandLine, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS,
|
|
NULL, NULL, &startInfo, &procInfo);
|
|
hProc = procInfo.hProcess;
|
|
}
|
|
|
|
// if fIsDefaultWinsta, then pDesktop is pointing at static memory
|
|
if (pDesktop && !fIsDefaultWinsta) delete[] pDesktop;
|
|
if (pCommandLine) delete[] pCommandLine;
|
|
}
|
|
|
|
if (bLaunched)
|
|
{
|
|
if (WaitForSingleObject(hProc, 60000) == WAIT_OBJECT_0)
|
|
{
|
|
DWORD exitCode;
|
|
if (GetExitCodeProcess(hProc, &exitCode))
|
|
fWindowClosed = exitCode - ERROR_LEVEL_OFFSET;
|
|
}
|
|
|
|
CloseHandle(hProc);
|
|
}
|
|
|
|
return fWindowClosed;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: CloseProcEx
|
|
//
|
|
// Synopsis: Entry point used with RunDll32
|
|
// closes down window via WM_CLOSE
|
|
//
|
|
// Arguments: [hwnd] -- ignored
|
|
// [hinst] -- uninteresting
|
|
// [nCmdShow] -- boring
|
|
// [lpszCmdLine] -- command line from invocation
|
|
//
|
|
// Notes: command line should be proc id.
|
|
// CloseProc in main is a straight passthrough to this one.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
extern "C" void CALLBACK CloseProcEx(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
|
|
{
|
|
BOOL fFoundWindow = FALSE;
|
|
|
|
ENUMPROCPARMS params;
|
|
params.fWindowFound = 0;
|
|
|
|
if (lpszCmdLine && strlen(lpszCmdLine))
|
|
{
|
|
if (sscanf(lpszCmdLine, "%u", ¶ms.dwProcessId)) // SEC:REVIEWED 2002-04-30
|
|
// this function does not involve an unbounded string copy
|
|
{
|
|
EnumWindows(CloseWindowEnumProc, (LPARAM) ¶ms);
|
|
fFoundWindow = params.fWindowFound;
|
|
}
|
|
}
|
|
|
|
ExitProcess(fFoundWindow +ERROR_LEVEL_OFFSET);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: CloseWindowEnumProc
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Arguments: [hWnd] --
|
|
// [lParam] --
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL CALLBACK
|
|
CloseWindowEnumProc(HWND hWnd, LPARAM lParam)
|
|
{
|
|
DWORD dwProcessId, dwThreadId;
|
|
ENUMPROCPARMS * pParms = (ENUMPROCPARMS *) lParam;
|
|
|
|
dwThreadId = GetWindowThreadProcessId(hWnd, &dwProcessId);
|
|
|
|
if (dwProcessId == pParms->dwProcessId)
|
|
{
|
|
//
|
|
// Enumerate and close each owned, non-child window. This will close
|
|
// open dialogs along with the main window(s).
|
|
//
|
|
|
|
DWORD dwErr;
|
|
if (!EnumThreadWindows(dwThreadId, ThreadWindowEnumProc, lParam))
|
|
dwErr = GetLastError();
|
|
|
|
// some processes, e.g. cmd.exe don't get enumerated by EnumThreadWindows
|
|
// if so, we'll try one close message to the top window.
|
|
if ((pParms->fWindowFound == false) && IsWindow(hWnd))
|
|
{
|
|
PostMessage(hWnd, WM_CLOSE, 0, 0);
|
|
pParms->fWindowFound = true;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: ThreadWindowEnumProc
|
|
//
|
|
// Synopsis: Enumeration procedure.
|
|
//
|
|
// Arguments: [hWnd] -- The window handle.
|
|
// [lParam] -- The process ID.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL CALLBACK
|
|
ThreadWindowEnumProc(HWND hWnd, LPARAM lParam)
|
|
{
|
|
DWORD dwProcessId;
|
|
ENUMPROCPARMS * pParms = (ENUMPROCPARMS *) lParam;
|
|
|
|
GetWindowThreadProcessId(hWnd, &dwProcessId);
|
|
|
|
if (dwProcessId == pParms->dwProcessId)
|
|
{
|
|
//
|
|
// Close any dialogs.
|
|
//
|
|
|
|
//
|
|
// The most common dialog we are likely to see at this point is a
|
|
// "save changes" dialog. First try to send no to close that dialog
|
|
// and then try a cancel.
|
|
//
|
|
if( !PostMessage(hWnd, WM_COMMAND, 0, MAKEWPARAM(IDNO, 0)) )
|
|
{
|
|
schDebugOut((DEB_ERROR,
|
|
"CJobProcessor::PerformTask - ThreadWindowEnumProc, PMsg1, " \
|
|
"status = 0x%lx\n",
|
|
GetLastError()));
|
|
}
|
|
|
|
if( !PostMessage(hWnd, WM_COMMAND, 0, MAKEWPARAM(IDCANCEL, 0)) )
|
|
{
|
|
schDebugOut((DEB_ERROR,
|
|
"CJobProcessor::PerformTask - ThreadWindowEnumProc, PMsg2, " \
|
|
"status = 0x%lx\n",
|
|
GetLastError()));
|
|
}
|
|
//
|
|
// Close any non-child windows.
|
|
//
|
|
|
|
if( !PostMessage(hWnd, WM_CLOSE, 0, 0) )
|
|
{
|
|
schDebugOut((DEB_ERROR,
|
|
"CJobProcessor::PerformTask - ThreadWindowEnumProc, PMsg3, " \
|
|
"status = 0x%lx\n",
|
|
GetLastError()));
|
|
}
|
|
//
|
|
// Tell the calling function that we found a matching window.
|
|
//
|
|
pParms->fWindowFound = TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::SubmitJobs
|
|
//
|
|
// Synopsis: This method is used to submit new jobs to this processor.
|
|
//
|
|
// Each processor can handle a maximum of (MAXIMUM_WAIT_OBJECTS
|
|
// - 1) jobs (from the WaitForMultipleObjects constraint of
|
|
// at most MAXIMUM_WAIT_OBJECTS). Subject to processor capacity,
|
|
// all, or a subset of the jobs passed may be taken.
|
|
//
|
|
// Arguments: [pRunList] -- Submitted job linked list object. Jobs taken are
|
|
// transferred from this list to a private one.
|
|
//
|
|
// Returns: S_OK -- No submissions taken (as a result of a normal
|
|
// condition, such as the job processor already full,
|
|
// or the job processor shutting down).
|
|
// S_SCHED_JOBS_ACCEPTED -- Some submissions taken.
|
|
// On return, GetFirstJob() will return NULL if all
|
|
// submissions were taken.
|
|
// S_FALSE -- The service is shutting down. Call Shutdown()
|
|
// on this processor immediately after this return
|
|
// code. Submissions were likely taken, but they will
|
|
// not execute.
|
|
// HRESULT -- On error.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
CJobProcessor::SubmitJobs(CRunList * pRunList)
|
|
{
|
|
TRACE3(CJobProcessor, SubmitJobs);
|
|
schAssert(pRunList != NULL);
|
|
|
|
HRESULT hr = S_OK;
|
|
BOOL fJobsAccepted = FALSE;
|
|
|
|
schDebugOut((DEB_USER3,
|
|
"SubmitJobs(0x%x) pRunList(0x%x)\n",
|
|
this,
|
|
pRunList));
|
|
|
|
//
|
|
// Serialize processor data structure access.
|
|
//
|
|
|
|
EnterCriticalSection(&_csProcessorCritSection);
|
|
|
|
FILETIME ftNow = GetLocalTimeAsFileTime();
|
|
schDebugOut((DEB_USER3, "SubmitJobs: Time now = %lx %lx\n",
|
|
ftNow.dwLowDateTime, ftNow.dwHighDateTime));
|
|
|
|
//
|
|
// Add as many jobs as this processor will allow to the request queue.
|
|
// See synopsis for details.
|
|
//
|
|
// NB : Adding one to the request/processing queue sum to reflect the new
|
|
// processor notification event handle. For this handle array entry,
|
|
// there is no processing queue entry.
|
|
//
|
|
|
|
CRun * pRun = pRunList->GetFirstJob();
|
|
|
|
//
|
|
// First, check if this processor is in the process of shutting down.
|
|
// The data member, _rgHandles, is utilized as a flag to indicate this.
|
|
// If it is NULL, this processor has shutdown and will take no more jobs.
|
|
//
|
|
|
|
if (_rgHandles != NULL)
|
|
{
|
|
while ( !pRun->IsNull() &&
|
|
(MAXIMUM_WAIT_OBJECTS - (this->_RequestQueue.GetCount() +
|
|
this->_ProcessingQueue.GetCount() +
|
|
1) ))
|
|
{
|
|
CRun * pRunNext = pRun->Next();
|
|
|
|
pRun->UnLink();
|
|
|
|
schDebugOut((DEB_USER3,
|
|
"SubmitJobs: pRun(%#lx) (" FMT_TSTR ") KillTime = %lx %lx\n",
|
|
pRun, pRun->GetName(), pRun->GetKillTime().dwLowDateTime,
|
|
pRun->GetKillTime().dwHighDateTime));
|
|
|
|
//
|
|
// Compute the max run time (the time we will wait for
|
|
// this job to complete) based on the kill time
|
|
//
|
|
DWORDLONG MaxRunTime;
|
|
if (FTto64(pRun->GetKillTime()) < FTto64(ftNow))
|
|
{
|
|
MaxRunTime = 0;
|
|
}
|
|
else
|
|
{
|
|
MaxRunTime = (FTto64(pRun->GetKillTime()) - FTto64(ftNow)) /
|
|
FILETIMES_PER_MILLISECOND;
|
|
MaxRunTime = min(MaxRunTime, MAXULONG);
|
|
}
|
|
pRun->SetMaxRunTime((DWORD) MaxRunTime);
|
|
schDebugOut((DEB_USER3, "SubmitJobs: MaxRunTime = %lu\n", MaxRunTime));
|
|
|
|
_RequestQueue.AddElement(pRun);
|
|
|
|
fJobsAccepted = TRUE;
|
|
|
|
pRun = pRunNext;
|
|
}
|
|
|
|
//
|
|
// Is there a thread servicing this object? If not, request one.
|
|
//
|
|
|
|
if (!this->IsInService())
|
|
{
|
|
//
|
|
// NB : A RequestService() return code of S_FALSE indicates the
|
|
// service is shutting down. Simply propagate this return
|
|
// code. It will then be the caller's responsibility to
|
|
// shut down this processor.
|
|
//
|
|
|
|
hr = RequestService(this);
|
|
|
|
if (SUCCEEDED(hr) && hr != S_FALSE)
|
|
{
|
|
this->InService();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the processor notification event.
|
|
//
|
|
|
|
schDebugOut((DEB_USER3,
|
|
"CJobProcessor::SubmitJobs(0x%x) Signalling processor thread\n"));
|
|
|
|
//
|
|
// A NOP if RequestService() failed above.
|
|
//
|
|
|
|
SetEvent(_rgHandles[0]);
|
|
}
|
|
|
|
LeaveCriticalSection(&_csProcessorCritSection);
|
|
|
|
if (hr == S_OK && fJobsAccepted)
|
|
{
|
|
hr = S_SCHED_JOBS_ACCEPTED;
|
|
}
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::KillJob
|
|
//
|
|
// Synopsis: Kill all instances of the job indicated, if in service by
|
|
// this processor.
|
|
//
|
|
// Arguments: [ptszJobName] -- Job name.
|
|
//
|
|
// Returns: None.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
CJobProcessor::KillJob(LPTSTR ptszJobName)
|
|
{
|
|
TRACE(CJobProcessor, KillJob);
|
|
BOOL fContractInitiated = FALSE;
|
|
|
|
//
|
|
// Serialize processor data structure access.
|
|
//
|
|
|
|
EnterCriticalSection(&_csProcessorCritSection);
|
|
|
|
//
|
|
// Is the job serviced by this processor?
|
|
// Find associated job info object(s) in the processing queue.
|
|
//
|
|
// NB : Rarely, but it is possible there may be more than one instance.
|
|
//
|
|
|
|
CRun * pRun;
|
|
for (pRun = _ProcessingQueue.GetFirstElement(); pRun != NULL;
|
|
pRun = pRun->Next())
|
|
{
|
|
//
|
|
// The abort flag check addresses the case where more than one user
|
|
// simultaneously aborts the same job.
|
|
//
|
|
|
|
if (!lstrcmpi(ptszJobName, pRun->GetName()) &&
|
|
!(pRun->GetFlags() & RUN_STATUS_ABORTED))
|
|
{
|
|
//
|
|
// Set flags for immediate timeout and closure.
|
|
//
|
|
|
|
pRun->SetMaxRunTime(0);
|
|
pRun->SetFlag(RUN_STATUS_ABORTED);
|
|
fContractInitiated = TRUE;
|
|
}
|
|
}
|
|
|
|
if (fContractInitiated)
|
|
{
|
|
//
|
|
// This logic will induce the PerformTask thread to respond as
|
|
// follows:
|
|
// - The wait will unblock and the next wait time re-calculated;
|
|
// this value will be zero since the min value is taken.
|
|
// - The wait is re-entered and immediately times out.
|
|
// - Jobs with max run times of zero are closed in the
|
|
// WAIT_TIMEOUT condition. As a result, the jobs are killed.
|
|
//
|
|
|
|
SetEvent(_rgHandles[0]);
|
|
}
|
|
|
|
LeaveCriticalSection(&_csProcessorCritSection);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::KillIfFlagSet
|
|
//
|
|
// Synopsis: Kill all jobs that have the passed in flag set, if in service
|
|
// by this processor.
|
|
//
|
|
// Arguments: [dwFlag] - Job flag value, one of TASK_FLAG_KILL_ON_IDLE_END
|
|
// or TASK_FLAG_KILL_IF_GOING_ON_BATTERIES.
|
|
// Returns: None.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
CJobProcessor::KillIfFlagSet(DWORD dwFlag)
|
|
{
|
|
TRACE(CJobProcessor, KillIfFlagSet);
|
|
BOOL fContractInitiated = FALSE;
|
|
|
|
//
|
|
// Serialize processor data structure access.
|
|
//
|
|
|
|
EnterCriticalSection(&_csProcessorCritSection);
|
|
|
|
//
|
|
// Is the job serviced by this processor?
|
|
// Find associated job info object(s) in the processing queue.
|
|
//
|
|
|
|
CRun * pRun;
|
|
for (pRun = _ProcessingQueue.GetFirstElement(); pRun != NULL;
|
|
pRun = pRun->Next())
|
|
{
|
|
//
|
|
// The abort flag check addresses the case where more than one user
|
|
// simultaneously aborts the same job.
|
|
//
|
|
|
|
if ((pRun->GetFlags() & dwFlag) &&
|
|
!(pRun->GetFlags() & RUN_STATUS_ABORTED))
|
|
{
|
|
//
|
|
// Set flags for immediate timeout and closure.
|
|
//
|
|
|
|
pRun->SetMaxRunTime(0);
|
|
pRun->SetFlag(RUN_STATUS_ABORTED);
|
|
if (dwFlag == TASK_FLAG_KILL_ON_IDLE_END &&
|
|
pRun->IsFlagSet(TASK_FLAG_RESTART_ON_IDLE_RESUME) &&
|
|
! pRun->IsIdleTriggered())
|
|
{
|
|
//
|
|
// Note that this is the only case in which we set
|
|
// RUN_STATUS_RESTART_ON_IDLE_RESUME. If a job is terminated
|
|
// because a user explicitly terminated it, for example, we
|
|
// don't want to restart it on idle resume.
|
|
//
|
|
pRun->SetFlag(RUN_STATUS_RESTART_ON_IDLE_RESUME);
|
|
}
|
|
fContractInitiated = TRUE;
|
|
}
|
|
}
|
|
|
|
if (fContractInitiated)
|
|
{
|
|
//
|
|
// This logic will induce the PerformTask thread to respond as
|
|
// follows:
|
|
// - The wait will unblock and the next wait time re-calculated;
|
|
// this value will be zero since the min value is taken.
|
|
// - The wait is re-entered and immediately times out.
|
|
// - Jobs with max run times of zero are closed in the
|
|
// WAIT_TIMEOUT condition. As a result, the jobs are killed.
|
|
//
|
|
|
|
SetEvent(_rgHandles[0]);
|
|
}
|
|
|
|
LeaveCriticalSection(&_csProcessorCritSection);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::Shutdown
|
|
//
|
|
// Synopsis: Effect processor shutdown. Do so by signalling the
|
|
// PerformTask thread. The thread will check the global service
|
|
// status flag. If the service is stopped (actually, in the
|
|
// process of stopping), the thread will execute the processor
|
|
// shutdown code & relinquish itself.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Returns: None.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
CJobProcessor::Shutdown(void)
|
|
{
|
|
TRACE3(CJobProcessor, Shutdown);
|
|
|
|
EnterCriticalSection(&_csProcessorCritSection);
|
|
|
|
if (_rgHandles != NULL)
|
|
{
|
|
SetEvent(_rgHandles[0]);
|
|
}
|
|
|
|
LeaveCriticalSection(&_csProcessorCritSection);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::_EmptyJobQueue
|
|
//
|
|
// Synopsis: Empty respective job queue and log, per job, the reason why.
|
|
//
|
|
// Arguments: [JobQueue] -- Reference to CJobQueue instance.
|
|
// [dwMsgId] -- Why each job was abandoned. A value of zero
|
|
// indicates no reason; nothing is logged.
|
|
//
|
|
// Notes: Must be in the processor critical section for the duration
|
|
// of this method!
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
CJobProcessor::_EmptyJobQueue(CJobQueue & JobQueue, DWORD dwMsgId)
|
|
{
|
|
TRACE3(CJobProcessor, _EmptyJobQueue);
|
|
|
|
CRun * pRun;
|
|
|
|
for (pRun = JobQueue.RemoveElement(); pRun != NULL;
|
|
pRun = JobQueue.RemoveElement())
|
|
{
|
|
if (!dwMsgId)
|
|
{
|
|
//
|
|
// BUGBUG : Log job info + reason why the job was abandoned.
|
|
// Should logging be per job? Per incident w/ job list?
|
|
//
|
|
}
|
|
|
|
delete pRun;
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::_ProcessRequests
|
|
//
|
|
// Synopsis: Transfer submitted jobs from the request queue to the
|
|
// processing queue and rebuild the wait handle array.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Notes: Must be in the processor critical section for the duration
|
|
// of this method!
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
CJobProcessor::_ProcessRequests(void)
|
|
{
|
|
TRACE3(CJobProcessor, _ProcessRequests);
|
|
|
|
if (_RequestQueue.GetCount())
|
|
{
|
|
//
|
|
// Sum request, processing queue counts.
|
|
//
|
|
|
|
DWORD cJobs = _RequestQueue.GetCount() +
|
|
_ProcessingQueue.GetCount() + 1;
|
|
|
|
schDebugOut((DEB_USER3,
|
|
"CJobProcessor::_ProcessRequests(0x%x) Total job count(%d) = " \
|
|
"request(%d) + processing(%d) + 1\n",
|
|
this,
|
|
cJobs,
|
|
_RequestQueue.GetCount(),
|
|
_ProcessingQueue.GetCount()));
|
|
|
|
//
|
|
// Logic in SubmitJobs should prevent this from becoming false.
|
|
//
|
|
|
|
schAssert(cJobs <= MAXIMUM_WAIT_OBJECTS);
|
|
|
|
HANDLE * rgHandles = new HANDLE[cJobs];
|
|
|
|
if (rgHandles == NULL)
|
|
{
|
|
//
|
|
// Leave request, processing queues as-is.
|
|
//
|
|
LogServiceError(IDS_FATAL_ERROR,
|
|
ERROR_NOT_ENOUGH_MEMORY,
|
|
IDS_HELP_HINT_CLOSE_APPS);
|
|
ERR_OUT("JobProcessor: ProcessRequests", E_OUTOFMEMORY);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Copy existing handles.
|
|
//
|
|
|
|
CopyMemory(rgHandles,
|
|
_rgHandles,
|
|
sizeof(HANDLE) * (_ProcessingQueue.GetCount() + 1));
|
|
|
|
//
|
|
// Copy new job handles from request queue and transfer request
|
|
// queue contents to the tail of the processing queue.
|
|
//
|
|
|
|
for (DWORD i = _ProcessingQueue.GetCount() + 1; i < cJobs; i++)
|
|
{
|
|
CRun * pRun = _RequestQueue.RemoveElement();
|
|
|
|
Win4Assert( pRun != NULL );
|
|
|
|
rgHandles[i] = pRun->GetHandle();
|
|
_ProcessingQueue.AddElement(pRun);
|
|
|
|
if (pRun->IsFlagSet(TASK_FLAG_SYSTEM_REQUIRED))
|
|
{
|
|
//
|
|
// Increment the count of running system_required jobs
|
|
// handled by this thread. If this is the first such
|
|
// job, tell the system not to sleep until further notice.
|
|
//
|
|
WrapSetThreadExecutionState(TRUE, "processor - new job");
|
|
}
|
|
}
|
|
|
|
delete _rgHandles;
|
|
_rgHandles = rgHandles;
|
|
}
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CJobProcessor::_Shutdown
|
|
//
|
|
// Synopsis: Take no more requests and dump whatever jobs remain in the
|
|
// request & processing queues.
|
|
//
|
|
// Arguments: None.
|
|
//
|
|
// Notes: Must be in the processor critical section for the duration
|
|
// of this method!
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
CJobProcessor::_Shutdown(void)
|
|
{
|
|
TRACE3(CJobProcessor, _Shutdown);
|
|
|
|
//
|
|
// Utilizing the handle array member as a flag to indicate that this
|
|
// processor will take no more new jobs. Set this member to NULL on
|
|
// shutdown.
|
|
//
|
|
// First close the processor notification event handle & delete the
|
|
// array.
|
|
//
|
|
|
|
// No need to keep the machine awake for this thread any more
|
|
if (pfnSetThreadExecutionState != NULL)
|
|
{
|
|
schDebugOut((DEB_USER5, "RESETTING sys-required state: processor shutdown\n"));
|
|
(pfnSetThreadExecutionState)(ES_CONTINUOUS);
|
|
}
|
|
|
|
CloseHandle(_rgHandles[0]);
|
|
_rgHandles[0] = NULL;
|
|
delete _rgHandles;
|
|
_rgHandles = NULL;
|
|
|
|
//
|
|
// Now, empty request & processing queues.
|
|
//
|
|
// BUGBUG : Log job abandoned message.
|
|
//
|
|
|
|
this->_EmptyJobQueue(_RequestQueue);
|
|
this->_EmptyJobQueue(_ProcessingQueue);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CSchedWorker::JobPostProcessing
|
|
//
|
|
// Synopsis: Set the exit code, current status, and NextRunTime on the
|
|
// job object and log the run exit.
|
|
//
|
|
// Arguments: [pRun] -- Job run information object.
|
|
// [stFinished] -- Job finish time (local time). For logging.
|
|
//
|
|
// Notes: None.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
CSchedWorker::JobPostProcessing(
|
|
CRun * pRun,
|
|
SYSTEMTIME & stFinished)
|
|
{
|
|
TRACE3(CSchedWorker, JobPostProcessing);
|
|
schDebugOut((DEB_ITRACE,
|
|
"JobPostProcessing pRun(0x%x) flags(0x%x)\n",
|
|
pRun,
|
|
pRun->GetFlags()));
|
|
|
|
DWORD dwExitCode;
|
|
|
|
CJob * pJob = NULL;
|
|
|
|
//
|
|
// Instantiate the job so that the exit status can be saved.
|
|
//
|
|
// Note: if any of the variable length properties or the triggers are
|
|
// needed, then a full activation will be necessary.
|
|
//
|
|
// Important: the running instance count must be protected by the
|
|
// critical section here, where it is decremented, and in RunJobs, where
|
|
// it is incremented. These are the only sections of code that change
|
|
// the running instance count on the file object.
|
|
//
|
|
|
|
EnterCriticalSection(&m_SvcCriticalSection);
|
|
|
|
HRESULT hr = ActivateWithRetry(pRun->GetName(), &pJob, FALSE);
|
|
if (FAILED(hr))
|
|
{
|
|
//
|
|
// The job object may have been deleted. We can't supply LogTaskError
|
|
// with the name of the run target, since that's on the job object,
|
|
// which we just failed to load.
|
|
//
|
|
|
|
LogTaskError(pRun->GetName(),
|
|
NULL,
|
|
IDS_LOG_SEVERITY_WARNING,
|
|
IDS_LOG_JOB_WARNING_CANNOT_LOAD,
|
|
NULL,
|
|
(DWORD)hr);
|
|
ERR_OUT("JobPostProcessing Activate", hr);
|
|
LeaveCriticalSection(&m_SvcCriticalSection);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Isolate the executable name.
|
|
//
|
|
|
|
TCHAR tszExeName[MAX_PATH + 1];
|
|
GetExeNameFromCmdLine(pJob->GetCommand(), MAX_PATH + 1, tszExeName);
|
|
|
|
if (pRun->GetFlags() & RUN_STATUS_FINISHED)
|
|
{
|
|
//
|
|
// Only check the exit code if the job completed normally, that is,
|
|
// it wasn't timed-out or aborted.
|
|
//
|
|
if (!GetExitCodeProcess(pRun->GetHandle(), &dwExitCode))
|
|
{
|
|
LogTaskError(pRun->GetName(),
|
|
tszExeName,
|
|
IDS_LOG_SEVERITY_WARNING,
|
|
IDS_CANT_GET_EXITCODE,
|
|
NULL,
|
|
GetLastError());
|
|
ERR_OUT("GetExitCodeProcess", GetLastError());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// BUGBUG : What is written on the job when not complete?
|
|
//
|
|
}
|
|
|
|
//
|
|
// PostRunUpdate updates the flags and instance count, so always call it.
|
|
//
|
|
pJob->PostRunUpdate(dwExitCode, pRun->GetFlags() & RUN_STATUS_FINISHED);
|
|
|
|
//
|
|
// If the last run and the delete flag is set, delete the job object.
|
|
//
|
|
|
|
if (pJob->IsFlagSet(JOB_I_FLAG_NO_MORE_RUNS) &&
|
|
pJob->IsFlagSet(TASK_FLAG_DELETE_WHEN_DONE))
|
|
{
|
|
hr = pJob->Delete();
|
|
if (FAILED(hr))
|
|
{
|
|
LogTaskError(pRun->GetName(),
|
|
tszExeName,
|
|
IDS_LOG_SEVERITY_WARNING,
|
|
IDS_CANT_DELETE_JOB,
|
|
NULL,
|
|
GetLastError());
|
|
ERR_OUT("JobPostProcessing, delete-when-done", hr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Write the updated status to the job object. If there are sharing
|
|
// violations, retry two times.
|
|
//
|
|
hr = pJob->SaveWithRetry(NULL,
|
|
FALSE,
|
|
SAVEP_RUNNING_INSTANCE_COUNT |
|
|
SAVEP_PRESERVE_NET_SCHEDULE);
|
|
if (FAILED(hr))
|
|
{
|
|
LogTaskError(pRun->GetName(),
|
|
tszExeName,
|
|
IDS_LOG_SEVERITY_WARNING,
|
|
IDS_CANT_UPDATE_JOB,
|
|
NULL,
|
|
GetLastError());
|
|
ERR_OUT("JobPostProcessing, Saving run-completion-status", hr);
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&m_SvcCriticalSection);
|
|
|
|
if (pRun->GetFlags() & RUN_STATUS_FINISHED)
|
|
{
|
|
// Log job finish time & result.
|
|
//
|
|
LogTaskStatus(pRun->GetName(),
|
|
tszExeName,
|
|
IDS_LOG_JOB_STATUS_FINISHED,
|
|
dwExitCode);
|
|
}
|
|
else if (pRun->GetFlags() & RUN_STATUS_ABORTED)
|
|
{
|
|
// Log job closure on abort warning.
|
|
//
|
|
LogTaskError(pRun->GetName(),
|
|
tszExeName,
|
|
IDS_LOG_SEVERITY_WARNING,
|
|
IDS_LOG_JOB_WARNING_ABORTED,
|
|
&stFinished);
|
|
}
|
|
else if (pRun->GetFlags() & RUN_STATUS_TIMED_OUT)
|
|
{
|
|
// Log job closure on timeout warning.
|
|
//
|
|
LogTaskError(pRun->GetName(),
|
|
tszExeName,
|
|
IDS_LOG_SEVERITY_WARNING,
|
|
IDS_LOG_JOB_WARNING_TIMEOUT,
|
|
&stFinished,
|
|
0,
|
|
IDS_HELP_HINT_TIMEOUT);
|
|
}
|
|
|
|
pJob->Release();
|
|
}
|
|
|