[SCHEDSVC] Use a timer to start jobs

This enables the service to start multiple jobs at the same time.
This commit is contained in:
Eric Kohl 2021-12-25 14:18:53 +01:00
parent 99dcd6f71c
commit fa607733cb
4 changed files with 117 additions and 159 deletions

View file

@ -31,60 +31,59 @@ RTL_RESOURCE JobListLock;
LIST_ENTRY StartListHead; LIST_ENTRY StartListHead;
RTL_RESOURCE StartListLock; RTL_RESOURCE StartListLock;
FILETIME NextJobStartTime;
BOOL bValidNextJobStartTime = FALSE;
static WORD wDaysArray[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; static WORD wDaysArray[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
/* FUNCTIONS *****************************************************************/ /* FUNCTIONS *****************************************************************/
DWORD VOID
GetNextJobTimeout(VOID) GetNextJobTimeout(HANDLE hTimer)
{ {
FILETIME FileTime; PLIST_ENTRY CurrentEntry;
SYSTEMTIME SystemTime; FILETIME DueTime;
ULARGE_INTEGER CurrentTime, Timeout; PJOB CurrentJob;
PJOB pNextJob;
if (IsListEmpty(&StartListHead)) bValidNextJobStartTime = FALSE;
CurrentEntry = JobListHead.Flink;
while (CurrentEntry != &JobListHead)
{ {
TRACE("No job in list! Wait until next update.\n"); CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, JobEntry);
return INFINITE;
if (bValidNextJobStartTime == FALSE)
{
CopyMemory(&NextJobStartTime, &CurrentJob->StartTime, sizeof(FILETIME));
bValidNextJobStartTime = TRUE;
}
else
{
if (CompareFileTime(&NextJobStartTime, &CurrentJob->StartTime) > 0)
CopyMemory(&NextJobStartTime, &CurrentJob->StartTime, sizeof(FILETIME));
}
CurrentEntry = CurrentEntry->Flink;
} }
pNextJob = CONTAINING_RECORD((&StartListHead)->Flink, JOB, StartEntry); if (bValidNextJobStartTime == FALSE)
FileTime.dwLowDateTime = pNextJob->StartTime.u.LowPart;
FileTime.dwHighDateTime = pNextJob->StartTime.u.HighPart;
FileTimeToSystemTime(&FileTime, &SystemTime);
TRACE("Start next job (%lu) at %02hu:%02hu %02hu.%02hu.%hu\n",
pNextJob->JobId, SystemTime.wHour, SystemTime.wMinute,
SystemTime.wDay, SystemTime.wMonth, SystemTime.wYear);
GetLocalTime(&SystemTime);
SystemTimeToFileTime(&SystemTime, &FileTime);
CurrentTime.u.LowPart = FileTime.dwLowDateTime;
CurrentTime.u.HighPart = FileTime.dwHighDateTime;
if (CurrentTime.QuadPart >= pNextJob->StartTime.QuadPart)
{ {
TRACE("Next event has already gone by!\n"); TRACE("No valid job!\n");
return 0; return;
} }
Timeout.QuadPart = (pNextJob->StartTime.QuadPart - CurrentTime.QuadPart) / 10000; LocalFileTimeToFileTime(&DueTime, &NextJobStartTime);
if (Timeout.u.HighPart != 0)
{
TRACE("Event happens too far in the future!\n");
return INFINITE;
}
TRACE("Timeout: %lu\n", Timeout.u.LowPart); SetWaitableTimer(hTimer,
return Timeout.u.LowPart; (PLARGE_INTEGER)&DueTime,
0,
NULL,
NULL,
TRUE);
} }
#if 0
static static
VOID VOID
ReScheduleJob( ReScheduleJob(
@ -117,59 +116,61 @@ ReScheduleJob(
DumpStartList(&StartListHead); DumpStartList(&StartListHead);
#endif #endif
} }
#endif
VOID VOID
RunNextJob(VOID) RunCurrentJobs(VOID)
{ {
PROCESS_INFORMATION ProcessInformation; PROCESS_INFORMATION ProcessInformation;
STARTUPINFOW StartupInfo; STARTUPINFOW StartupInfo;
PLIST_ENTRY CurrentEntry;
PJOB CurrentJob;
BOOL bRet; BOOL bRet;
PJOB pNextJob;
if (IsListEmpty(&StartListHead)) CurrentEntry = JobListHead.Flink;
while (CurrentEntry != &JobListHead)
{ {
ERR("No job in list!\n"); CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, JobEntry);
return;
if (CompareFileTime(&NextJobStartTime, &CurrentJob->StartTime) == 0)
{
TRACE("Run job %ld: %S\n", CurrentJob->JobId, CurrentJob->Command);
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
StartupInfo.lpTitle = CurrentJob->Command;
StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow = SW_SHOWDEFAULT;
if ((CurrentJob->Flags & JOB_NONINTERACTIVE) == 0)
{
StartupInfo.dwFlags |= STARTF_INHERITDESKTOP;
StartupInfo.lpDesktop = L"WinSta0\\Default";
}
bRet = CreateProcessW(NULL,
CurrentJob->Command,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&StartupInfo,
&ProcessInformation);
if (bRet == FALSE)
{
ERR("CreateProcessW() failed (Error %lu)\n", GetLastError());
}
else
{
CloseHandle(ProcessInformation.hThread);
CloseHandle(ProcessInformation.hProcess);
}
}
CurrentEntry = CurrentEntry->Flink;
} }
pNextJob = CONTAINING_RECORD((&StartListHead)->Flink, JOB, StartEntry);
TRACE("Run job %ld: %S\n", pNextJob->JobId, pNextJob->Command);
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
StartupInfo.lpTitle = pNextJob->Command;
StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow = SW_SHOWDEFAULT;
if ((pNextJob->Flags & JOB_NONINTERACTIVE) == 0)
{
StartupInfo.dwFlags |= STARTF_INHERITDESKTOP;
StartupInfo.lpDesktop = L"WinSta0\\Default";
}
bRet = CreateProcessW(NULL,
pNextJob->Command,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&StartupInfo,
&ProcessInformation);
if (bRet == FALSE)
{
ERR("CreateProcessW() failed (Error %lu)\n", GetLastError());
}
else
{
CloseHandle(ProcessInformation.hThread);
CloseHandle(ProcessInformation.hProcess);
}
ReScheduleJob(pNextJob);
} }
@ -420,8 +421,6 @@ LoadJobs(VOID)
/* Calculate the next start time */ /* Calculate the next start time */
CalculateNextStartTime(pJob); CalculateNextStartTime(pJob);
/* Insert the job into the start list */
InsertJobIntoStartList(&StartListHead, pJob);
#if 0 #if 0
DumpStartList(&StartListHead); DumpStartList(&StartListHead);
#endif #endif
@ -473,6 +472,7 @@ CalculateNextStartTime(
WORD wDaysOffset, wTempOffset, i, wJobDayOfWeek, wJobDayOfMonth; WORD wDaysOffset, wTempOffset, i, wJobDayOfWeek, wJobDayOfMonth;
DWORD_PTR CurrentTimeMs; DWORD_PTR CurrentTimeMs;
BOOL bDaysOffsetValid; BOOL bDaysOffsetValid;
ULARGE_INTEGER LocalStartTime;
TRACE("CalculateNextStartTime(%p)\n", pJob); TRACE("CalculateNextStartTime(%p)\n", pJob);
TRACE("JobTime: %lu\n", pJob->JobTime); TRACE("JobTime: %lu\n", pJob->JobTime);
@ -590,66 +590,18 @@ CalculateNextStartTime(
SystemTimeToFileTime(&StartSystemTime, &StartFileTime); SystemTimeToFileTime(&StartSystemTime, &StartFileTime);
pJob->StartTime.u.LowPart = StartFileTime.dwLowDateTime; LocalStartTime.u.LowPart = StartFileTime.dwLowDateTime;
pJob->StartTime.u.HighPart = StartFileTime.dwHighDateTime; LocalStartTime.u.HighPart = StartFileTime.dwHighDateTime;
if (bDaysOffsetValid && wDaysOffset != 0) if (bDaysOffsetValid && wDaysOffset != 0)
{ {
pJob->StartTime.QuadPart += ((ULONGLONG)wDaysOffset * 24 * 60 * 60 * 10000); LocalStartTime.QuadPart += ((ULONGLONG)wDaysOffset * 24 * 60 * 60 * 10000);
} }
pJob->StartTime.dwLowDateTime = LocalStartTime.u.LowPart;
pJob->StartTime.dwHighDateTime = LocalStartTime.u.HighPart;
} }
#if 0
VOID
InsertJobIntoStartList(
_In_ PLIST_ENTRY StartListHead,
_In_ PJOB pJob)
{
PLIST_ENTRY CurrentEntry, PreviousEntry;
PJOB CurrentJob;
if (IsListEmpty(StartListHead))
{
InsertHeadList(StartListHead, &pJob->StartEntry);
return;
}
CurrentEntry = StartListHead->Flink;
while (CurrentEntry != StartListHead)
{
CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, StartEntry);
if ((CurrentEntry == StartListHead->Flink) &&
(pJob->StartTime.QuadPart < CurrentJob->StartTime.QuadPart))
{
/* Insert before the first entry */
InsertHeadList(StartListHead, &pJob->StartEntry);
return;
}
if (pJob->StartTime.QuadPart < CurrentJob->StartTime.QuadPart)
{
/* Insert between the previous and the current entry */
PreviousEntry = CurrentEntry->Blink;
pJob->StartEntry.Blink = PreviousEntry;
pJob->StartEntry.Flink = CurrentEntry;
PreviousEntry->Flink = &pJob->StartEntry;
CurrentEntry->Blink = &pJob->StartEntry;
return;
}
if ((CurrentEntry->Flink == StartListHead) &&
(pJob->StartTime.QuadPart >= CurrentJob->StartTime.QuadPart))
{
/* Insert after the last entry */
InsertTailList(StartListHead, &pJob->StartEntry);
return;
}
CurrentEntry = CurrentEntry->Flink;
}
}
VOID VOID
DumpStartList( DumpStartList(
_In_ PLIST_ENTRY StartListHead) _In_ PLIST_ENTRY StartListHead)
@ -657,8 +609,8 @@ DumpStartList(
PLIST_ENTRY CurrentEntry; PLIST_ENTRY CurrentEntry;
PJOB CurrentJob; PJOB CurrentJob;
CurrentEntry = StartListHead->Flink; CurrentEntry = JobListHead->Flink;
while (CurrentEntry != StartListHead) while (CurrentEntry != &JobListHead)
{ {
CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, StartEntry); CurrentJob = CONTAINING_RECORD(CurrentEntry, JOB, StartEntry);
@ -667,5 +619,5 @@ DumpStartList(
CurrentEntry = CurrentEntry->Flink; CurrentEntry = CurrentEntry->Flink;
} }
} }
#endif
/* EOF */ /* EOF */

View file

@ -32,8 +32,7 @@ typedef struct _JOB
{ {
LIST_ENTRY JobEntry; LIST_ENTRY JobEntry;
LIST_ENTRY StartEntry; FILETIME StartTime;
ULARGE_INTEGER StartTime;
WCHAR Name[JOB_NAME_LENGTH]; WCHAR Name[JOB_NAME_LENGTH];
DWORD JobId; DWORD JobId;
@ -54,16 +53,17 @@ extern RTL_RESOURCE JobListLock;
extern LIST_ENTRY StartListHead; extern LIST_ENTRY StartListHead;
extern RTL_RESOURCE StartListLock; extern RTL_RESOURCE StartListLock;
extern HANDLE Events[2]; extern HANDLE Events[3];
/* job.c */ /* job.c */
DWORD VOID
GetNextJobTimeout(VOID); GetNextJobTimeout(
HANDLE hTimer);
VOID VOID
RunNextJob(VOID); RunCurrentJobs(VOID);
LONG LONG
SaveJob( SaveJob(

View file

@ -121,8 +121,6 @@ NetrJobAdd(
/* Calculate the next start time */ /* Calculate the next start time */
CalculateNextStartTime(pJob); CalculateNextStartTime(pJob);
/* Insert the job into the start list */
InsertJobIntoStartList(&StartListHead, pJob);
#if 0 #if 0
DumpStartList(&StartListHead); DumpStartList(&StartListHead);
#endif #endif
@ -169,8 +167,6 @@ NetrJobDel(
if ((CurrentJob->JobId >= MinJobId) && (CurrentJob->JobId <= MaxJobId)) if ((CurrentJob->JobId >= MinJobId) && (CurrentJob->JobId <= MaxJobId))
{ {
/* Remove the job from the start list */
RemoveEntryList(&CurrentJob->StartEntry);
#if 0 #if 0
DumpStartList(&StartListHead); DumpStartList(&StartListHead);
#endif #endif

View file

@ -37,7 +37,7 @@ static WCHAR ServiceName[] = L"Schedule";
static SERVICE_STATUS_HANDLE ServiceStatusHandle; static SERVICE_STATUS_HANDLE ServiceStatusHandle;
static SERVICE_STATUS ServiceStatus; static SERVICE_STATUS ServiceStatus;
HANDLE Events[2] = {NULL, NULL}; // StopEvent, UpdateEvent HANDLE Events[3] = {NULL, NULL, NULL}; // StopEvent, UpdateEvent, Timer
/* FUNCTIONS *****************************************************************/ /* FUNCTIONS *****************************************************************/
@ -181,6 +181,15 @@ ServiceInit(VOID)
return GetLastError(); return GetLastError();
} }
Events[2] = CreateWaitableTimerW(NULL, FALSE, NULL);
if (Events[2] == NULL)
{
ERR("Could not create the timer\n");
CloseHandle(Events[1]);
CloseHandle(Events[0]);
return GetLastError();
}
return ERROR_SUCCESS; return ERROR_SUCCESS;
} }
@ -188,7 +197,7 @@ ServiceInit(VOID)
VOID WINAPI VOID WINAPI
SchedServiceMain(DWORD argc, LPTSTR *argv) SchedServiceMain(DWORD argc, LPTSTR *argv)
{ {
DWORD dwWait, dwTimeout, dwError; DWORD dwWait, dwError;
UNREFERENCED_PARAMETER(argc); UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv); UNREFERENCED_PARAMETER(argv);
@ -216,13 +225,13 @@ SchedServiceMain(DWORD argc, LPTSTR *argv)
UpdateServiceStatus(SERVICE_RUNNING); UpdateServiceStatus(SERVICE_RUNNING);
dwTimeout = GetNextJobTimeout(); GetNextJobTimeout(Events[2]);
for (;;) for (;;)
{ {
/* Wait for the next event */ /* Wait for the next event */
TRACE("Wait for next event!\n"); TRACE("Wait for next event!\n");
dwWait = WaitForMultipleObjects(2, Events, FALSE, dwTimeout); dwWait = WaitForMultipleObjects(3, Events, FALSE, INFINITE);
if (dwWait == WAIT_OBJECT_0) if (dwWait == WAIT_OBJECT_0)
{ {
TRACE("Stop event signaled!\n"); TRACE("Stop event signaled!\n");
@ -233,16 +242,16 @@ SchedServiceMain(DWORD argc, LPTSTR *argv)
TRACE("Update event signaled!\n"); TRACE("Update event signaled!\n");
RtlAcquireResourceShared(&JobListLock, TRUE); RtlAcquireResourceShared(&JobListLock, TRUE);
dwTimeout = GetNextJobTimeout(); GetNextJobTimeout(Events[2]);
RtlReleaseResource(&JobListLock); RtlReleaseResource(&JobListLock);
} }
else if (dwWait == WAIT_TIMEOUT) else if (dwWait == WAIT_OBJECT_0 + 2)
{ {
TRACE("Timeout: Start the next job!\n"); TRACE("Timeout: Start the next job!\n");
RtlAcquireResourceExclusive(&JobListLock, TRUE); RtlAcquireResourceExclusive(&JobListLock, TRUE);
RunNextJob(); RunCurrentJobs();
dwTimeout = GetNextJobTimeout(); GetNextJobTimeout(Events[2]);
RtlReleaseResource(&JobListLock); RtlReleaseResource(&JobListLock);
} }
} }
@ -250,6 +259,7 @@ SchedServiceMain(DWORD argc, LPTSTR *argv)
/* Close the start and update event handles */ /* Close the start and update event handles */
CloseHandle(Events[0]); CloseHandle(Events[0]);
CloseHandle(Events[1]); CloseHandle(Events[1]);
CloseHandle(Events[2]);
/* Stop the service */ /* Stop the service */
UpdateServiceStatus(SERVICE_STOPPED); UpdateServiceStatus(SERVICE_STOPPED);