windows-nt/Source/XPSP1/NT/printscan/print/spooler/localspl/schedule.c
2020-09-26 16:20:57 +08:00

1596 lines
47 KiB
C

/*++
Copyright (c) 1990 - 1996 Microsoft Corporation
Module Name:
schedule.c
Abstract:
This module provides all the scheduling services for the Local Spooler
Author:
Dave Snipp (DaveSn) 15-Mar-1991
Revision History:
Krishna Ganugapati (KrishnaG) 07-Dec-1993 - rewrote the scheduler thread to
gracefully kill off port threads if there are no jobs assigned to ports and
to recreate the port thread if the port receives a job and is without a thread.
Matthew A Felton (MattFe) June 1994 RapidPrint implemented
MattFe April 96 Chained Jobs
--*/
#include <precomp.h>
#include "filepool.hxx"
#define MIDNIGHT (60 * 60 * 24)
//
// Ten minutes, seconds are multiplied in by the Scheduler code.
//
#define FPTIMEOUT (60 * 10)
#if DBG
/* For the debug message:
*/
#define HOUR_FROM_SECONDS(Time) (((Time) / 60) / 60)
#define MINUTE_FROM_SECONDS(Time) (((Time) / 60) % 60)
#define SECOND_FROM_SECONDS(Time) (((Time) % 60) % 60)
/* Format for %02d:%02d:%02d replaceable string:
*/
#define FORMAT_HOUR_MIN_SEC(Time) HOUR_FROM_SECONDS(Time), \
MINUTE_FROM_SECONDS(Time), \
SECOND_FROM_SECONDS(Time)
/* Format for %02d:%02d replaceable string:
*/
#define FORMAT_HOUR_MIN(Time) HOUR_FROM_SECONDS(Time), \
MINUTE_FROM_SECONDS(Time)
#endif
//extern HANDLE hFilePool;
HANDLE SchedulerSignal = NULL;
HANDLE PowerManagementSignal = NULL;
VOID
DbgPrintTime(
);
DWORD
GetTimeToWait(
DWORD CurrentTime,
PINIPRINTER pIniPrinter,
PINIJOB pIniJob
);
DWORD
GetCurrentTimeInSeconds(
VOID
);
VOID
InitializeSchedulingGlobals(
);
VOID
CheckMemoryAvailable(
PINIJOB *ppIniJob,
BOOL bFixedJob
);
VOID
UpdateJobList(
);
BOOL
AddToJobList(
PINIJOB pIniJob,
SIZE_T Required,
DWORD dwJobList
);
BOOL
SchedulerCheckPort(
PINISPOOLER pIniSpooler,
PINIPORT pIniPort,
PINIJOB pFixedIniJob,
PDWORD pdwSchedulerTimeout
);
BOOL
SchedulerCheckSpooler(
PINISPOOLER pIniSpooler,
PDWORD pdwSchedulerTimeout
);
BOOL
GetJobFromWaitingList(
PINIPORT *ppIniPort,
PINIJOB *ppIniJob,
DWORD dwPriority
);
#if _MSC_FULL_VER >= 13008827
#pragma warning(push)
#pragma warning(disable:4715) // Not all control paths return (due to infinite loop)
#endif
DWORD
SchedulerThread(
PINISPOOLER pIniSpooler
)
{
DWORD SchedulerTimeout = INFINITE; // In seconds
PINISPOOLER pIniSpoolerNext;
BOOL bJobScheduled = FALSE;
HANDLE hTempFP = INVALID_HANDLE_VALUE;
// Initialize the EMF scheduling parameters
InitializeSchedulingGlobals();
for( ; ; ) {
if (SchedulerTimeout == INFINITE) {
DBGMSG(DBG_TRACE, ("Scheduler thread waiting indefinitely\n"));
} else {
DBGMSG(DBG_TRACE, ("Scheduler thread waiting for %02d:%02d:%02d\n",
FORMAT_HOUR_MIN_SEC(SchedulerTimeout)));
//
// The SchedulerTimeout is in seconds, so we need to multiply
// by 1000.
//
SchedulerTimeout *= 1000;
}
if (WaitForSingleObject(SchedulerSignal,
SchedulerTimeout) == WAIT_FAILED) {
DBGMSG(DBG_WARNING, ("SchedulerThread:WaitforSingleObject failed: Error %d\n",
GetLastError()));
}
if (WaitForSingleObject(PowerManagementSignal, INFINITE) == WAIT_FAILED)
{
DBGMSG(DBG_WARNING, ("SchedulerThread:WaitforSingleObject failed on ACPI event: Error %d\n",
GetLastError()));
}
/* The timeout will be reset if there are jobs to be printed
* at a later time. This will result in WaitForSingleObject
* timing out when the first one is due to be printed.
*/
SchedulerTimeout = INFINITE;
bJobScheduled = FALSE;
EnterSplSem();
INCSPOOLERREF( pLocalIniSpooler );
for( pIniSpooler = pLocalIniSpooler;
pIniSpooler;
pIniSpooler = pIniSpoolerNext ){
//
// Only schedule check spoolers that are local.
//
if( pIniSpooler->SpoolerFlags & SPL_TYPE_LOCAL ){
bJobScheduled = (SchedulerCheckSpooler( pIniSpooler, &SchedulerTimeout )
|| bJobScheduled);
//
// FP Change
// Trim the filepool.
//
if (pIniSpooler &&
(hTempFP = pIniSpooler->hFilePool) != INVALID_HANDLE_VALUE &&
!bJobScheduled )
{
//
// We've incremented the spooler Refcount, so we can
// safely leave the splsem.
//
LeaveSplSem();
if (TrimPool(hTempFP))
{
if (SchedulerTimeout == INFINITE)
{
SchedulerTimeout = FPTIMEOUT;
}
}
EnterSplSem();
}
}
pIniSpoolerNext = pIniSpooler->pIniNextSpooler;
if( pIniSpoolerNext ){
INCSPOOLERREF( pIniSpoolerNext );
}
DECSPOOLERREF( pIniSpooler );
}
LeaveSplSem();
}
return 0;
}
#if _MSC_FULL_VER >= 13008827
#pragma warning(pop)
#endif
BOOL
SchedulerCheckPort(
PINISPOOLER pIniSpooler,
PINIPORT pIniPort,
PINIJOB pFixedIniJob,
PDWORD pdwSchedulerTimeout)
/*++
Function Description: Checks if pIniJob can be assigned to pIniPort. If pInijob is NULL we
search for another job that can print on pIniPort. The job (if any) is
scheduled and the dwSchedulerTimeout is adjusted for the next waiting
job
Parameters: pIniSpooler -- pointer to INISPOOLER struct
pIniPort -- Port to a assign a job to
pFixedIniJob -- Assign this job, if possible. If NULL search for other jobs
pdwSchedulerTimeOut -- How much time will the scheduler thread sleep
Return Values: TRUE if a job gets assigned to pIniPort
FALSE otherwise
--*/
{
BOOL bFixedJob, bReturn = FALSE;
PINIJOB pIniJob = NULL;
DWORD ThisPortTimeToWait; // In seconds
DWORD CurrentTickCount;
// Check of there is a pre assigned job
bFixedJob = pFixedIniJob ? TRUE : FALSE;
DBGMSG(DBG_TRACE, ("Now Processing Port %ws\n", pIniPort->pName));
SPLASSERT( pIniPort->signature == IPO_SIGNATURE );
// Check conditions based on which we can assign this
// port a job.
// Rule 1 - if there is a job being processed by this
// port, then leave this port alone.
if ( (pIniPort->pIniJob) &&
!(pIniPort->Status & PP_WAITING )){
SPLASSERT( pIniPort->pIniJob->signature == IJ_SIGNATURE );
// If this port has a job which has timed out AND
// there is another job waiting on this port then
// push the timed out job out by setting JOB_ABANDON
// see spooler.c LocalReadPrinter
pIniJob = pIniPort->pIniJob;
if (( pIniJob->Status & JOB_TIMEOUT ) &&
( pIniJob->WaitForWrite != NULL ) &&
( NULL != AssignFreeJobToFreePort( pIniPort, &ThisPortTimeToWait ) )) {
INCPORTREF( pIniPort );
INCJOBREF( pIniJob );
pIniJob->Status |= JOB_ABANDON;
ReallocSplStr(&pIniJob->pStatus, szFastPrintTimeout);
LogJobInfo( pIniSpooler,
MSG_DOCUMENT_TIMEOUT,
pIniJob->JobId,
pIniJob->pDocument,
pIniJob->pUser,
pIniJob->pIniPrinter->pName,
dwFastPrintWaitTimeout );
SetEvent( pIniJob->WaitForWrite );
SetPrinterChange(pIniJob->pIniPrinter,
pIniJob,
NVJobStatusAndString,
PRINTER_CHANGE_SET_JOB,
pIniJob->pIniPrinter->pIniSpooler);
DECJOBREF( pIniJob );
DECPORTREF( pIniPort );
}
return bReturn;
}
if (bFixedJob) {
// Use a pre-assigned job
pIniJob = pFixedIniJob;
} else {
// Is there any job that can be scheduled to this port ?
pIniJob = AssignFreeJobToFreePort(pIniPort, &ThisPortTimeToWait);
*pdwSchedulerTimeout = min(ThisPortTimeToWait, *pdwSchedulerTimeout);
}
if (pIniPort->Status & PP_THREADRUNNING ) {
if (pIniPort->Status & PP_WAITING) {
// If we are working on a Chained Job then the job
// has already been assigned by the port thread from
// the last job on this port so ignore any other job
// found for us.
if (pIniPort->pIniJob) {
if (bFixedJob && (pIniJob != pIniPort->pIniJob)) {
// The fixed job could not assigned because chained jobs
// must be printed sequentially
pIniJob = NULL;
} else {
pIniJob = pIniPort->pIniJob;
DBGMSG( DBG_TRACE, ("ScheduleThread NextJob pIniPort %x JoId %d pIniJob %x\n",
pIniPort, pIniJob->JobId, pIniJob ));
}
}
// If the delay in scheduling has been requested by FlushPrinter wait until
// IdleTime elapses
//
// We're using a local here to avoid multiple calls to GetTickCount().
//
CurrentTickCount = GetTickCount();
if (pIniPort->bIdleTimeValid && (int)(pIniPort->IdleTime - CurrentTickCount) > 0) {
//
// Our port is not ready to accept a job just yet, we need to
// remind the Scheduler to wake up in a little while to reassign
// the job to the port.
//
// The difference is in milliseconds, so we divide by 1000 to get to
// seconds, and add 1 to make sure we return after the timeout has
// expired.
//
*pdwSchedulerTimeout =
min( ((pIniPort->IdleTime - CurrentTickCount)/1000) + 1,
*pdwSchedulerTimeout);
//
// Null out the job so we don't assign it to the port.
//
pIniJob = NULL;
}
else {
pIniPort->bIdleTimeValid = FALSE;
}
if ( pIniJob ) {
CheckMemoryAvailable( &pIniJob, bFixedJob );
}
if ( pIniJob ) {
DBGMSG(DBG_TRACE, ("ScheduleThread pIniJob %x Size %d pDocument %ws\n",
pIniJob, pIniJob->Size, DBGSTR( pIniJob->pDocument)));
if (pIniPort != pIniJob->pIniPort) {
++pIniPort->cJobs;
pIniJob->pIniPort = pIniPort;
}
pIniPort->pIniJob = pIniJob;
//
// We have a new job on this port, make sure the Critical Section mask is
// cleared.
//
pIniPort->InCriticalSection = 0;
if( !pIniJob->pCurrentIniJob ){
//
// If pCurrentIniJob is NULL, then this is
// beginning of a new job (single or linked).
//
// Clustered spoolers are interested in the
// number of jobs that are actually printing.
// We need to know when all printing jobs are
// done so we can shutdown.
//
++pIniJob->pIniPrinter->pIniSpooler->cFullPrintingJobs;
if( pIniJob->NextJobId ){
//
// Chained Jobs
// Point the Master Jobs Current Pointer to
// the first in the chain.
//
pIniJob->pCurrentIniJob = pIniJob;
}
}
pIniPort->Status &= ~PP_WAITING;
// If the job is still spooling then we will need
// to create an event to synchronize the port thread
if ( !( pIniJob->Status & JOB_DIRECT ) ) {
pIniJob->WaitForWrite = NULL;
if ( pIniJob->Status & JOB_SPOOLING ) {
pIniJob->WaitForWrite = CreateEvent( NULL,
EVENT_RESET_MANUAL,
EVENT_INITIAL_STATE_NOT_SIGNALED,
NULL );
}
}
// Update cRef so that nobody can delete this job
// before the Port Thread Starts up
SplInSem();
INCJOBREF(pIniJob);
SetEvent(pIniPort->Semaphore);
pIniJob->Status |= JOB_DESPOOLING;
bReturn = TRUE;
} else {
//
// If the port thread is running and it is waiting
// for a job and there is no job to assign, then
// kill the port thread
//
DBGMSG(DBG_TRACE, ("Now destroying the new port thread %.8x\n", pIniPort));
DestroyPortThread(pIniPort, FALSE);
pIniPort->Status &= ~PP_WAITING;
if (pIniPort->Status & PP_FILE) {
//
// We should destroy the Pseudo-File Port at this
// point. There are no jobs assigned to this Port
// and we are in Critical Section
//
//
// Now deleting the pIniPort entry for the Pseudo-Port
//
DBGMSG(DBG_TRACE, ("Now deleting the Pseudo-Port %ws\n", pIniPort->pName));
if ( !pIniPort->cJobs )
DeletePortEntry(pIniPort);
return bReturn;
}
}
}
} else if (!(pIniPort->Status & PP_THREADRUNNING) && pIniJob) {
//
// If the port thread is not running, and there is a job to
// assign, then create a port thread. REMEMBER do not assign
// the job to the port because we are in a Spooler Section and
// if we release the Spooler Section, the first thing the port
// thread does is reinitialize its pIniPort->pIniJob to NULL
// Wait the next time around we execute the for loop to assign
// the job to this port. Should we set *pdwSchedulerTimeOut to zero??
//
DBGMSG( DBG_TRACE, ("ScheduleThread Now creating the new port thread pIniPort %x\n", pIniPort));
CreatePortThread( pIniPort );
bReturn = TRUE;
}
return bReturn;
}
BOOL
SchedulerCheckSpooler(
PINISPOOLER pIniSpooler,
PDWORD pdwSchedulerTimeout)
/*++
Function Description: This function assigns a waiting job to a port after every minute.
If memory is available it schedules as many jobs from the waiting
list as possible.
It then loops thru the ports in a round-robin fashion scheduling jobs
or adding them to the waiting list.
Parameters: pIniSpooler -- pointer to the INISPOOLER struct
pdwSchedulerTimeout -- duration of time for which the scheduler
thread will sleep
Return Values: NONE
--*/
{
DWORD ThisPortTimeToWait = INFINITE; // In seconds
DWORD dwTickCount;
PINIPORT pIniPort;
PINIJOB pIniJob;
PINIPORT pIniNextPort = NULL;
BOOL bJobScheduled = FALSE;
UpdateJobList();
// If Jobs have been waiting for 1 minute and nothing has been scheduled in
// that time, schedule one of the waiting jobs.
dwTickCount = GetTickCount();
if (pWaitingList &&
((dwTickCount - pWaitingList->dwWaitTime) > ONE_MINUTE) &&
((dwTickCount - dwLastScheduleTime) > ONE_MINUTE)) {
if (GetJobFromWaitingList(&pIniPort, &pIniJob, SPL_FIRST_JOB)) {
bJobScheduled = (SchedulerCheckPort(pIniSpooler, pIniPort, pIniJob, &ThisPortTimeToWait)
|| bJobScheduled);
*pdwSchedulerTimeout = min(*pdwSchedulerTimeout, ThisPortTimeToWait);
}
}
// Use the available memory to schedule waiting jobs
while (GetJobFromWaitingList(&pIniPort, &pIniJob, SPL_USE_MEMORY)) {
bJobScheduled = (SchedulerCheckPort(pIniSpooler, pIniPort, pIniJob, &ThisPortTimeToWait)
|| bJobScheduled);
*pdwSchedulerTimeout = min(*pdwSchedulerTimeout, ThisPortTimeToWait);
}
// Loop thru the ports and get the list of jobs that can be scheduled
for (pIniPort = pIniSpooler->pIniPort;
pIniPort;
pIniPort = pIniNextPort) {
pIniNextPort = pIniPort->pNext;
//
// SchedulerCheckPort can leave the critical section and the iniPort can
// be removed from the list in the meanwhile. So, maintain the Ref on it.
//
if (pIniNextPort) {
INCPORTREF(pIniNextPort);
}
bJobScheduled = (SchedulerCheckPort(pIniSpooler, pIniPort, NULL, &ThisPortTimeToWait)
|| bJobScheduled);
*pdwSchedulerTimeout = min(*pdwSchedulerTimeout, ThisPortTimeToWait);
if (pIniNextPort) {
DECPORTREF(pIniNextPort);
}
}
// If there any jobs left try to reschedule latest after one minute.
if (pWaitingList) {
*pdwSchedulerTimeout = min(*pdwSchedulerTimeout, 60);
}
return bJobScheduled;
}
VOID
InitializeSchedulingGlobals(
)
/*++
Function Description: Initializes globals used for EMF scheduling
Parameters: NONE
Return Values: NONE
--*/
{
MEMORYSTATUS msBuffer;
HKEY hPrintRegKey = NULL;
DWORD dwType, dwData, dwcbData;
bUseEMFScheduling = TRUE; // default value
dwcbData = sizeof(DWORD);
// Check the registry for the flag for turning off EMF scheduling. If the
// key is not present/Reg Apis fail default to using the scheduling.
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
szRegistryRoot,
0,
KEY_READ,
&hPrintRegKey) == ERROR_SUCCESS) {
if (RegQueryValueEx(hPrintRegKey,
szEMFThrottle,
NULL,
&dwType,
(LPBYTE) &dwData,
&dwcbData) == ERROR_SUCCESS) {
if (dwData == 0) {
// Scheduling has been turned off
bUseEMFScheduling = FALSE;
}
}
}
// Get the memory status
GlobalMemoryStatus(&msBuffer);
// Use half the physical memory in megabytes
TotalMemoryForRendering = msBuffer.dwTotalPhys / ( 2048 * 1024);
AvailMemoryForRendering = TotalMemoryForRendering;
dwNumberOfEMFJobsRendering = 0;
pWaitingList = NULL;
pScheduleList = NULL;
dwLastScheduleTime = GetTickCount();
if (hPrintRegKey) {
RegCloseKey(hPrintRegKey);
}
return;
}
DWORD
GetMemoryEstimate(
LPDEVMODE pDevMode
)
/*++
Function Description: Computes a rough estimate of the memory required for rendering a
single page based on the DPI and color settings
Parameters: pDevMode -- pointer to the devmode of the job
Return Values: Memory estimate
--*/
{
DWORD dwRequired, dwXRes, dwYRes, dwMaxRes;
DWORD dwXIndex, dwYIndex;
DWORD MemHeuristic[3][2] = {{8 , 4},
{12, 6},
{16, 8}};
// Get the max resolution on either axis
dwXRes = dwYRes = 300;
if (pDevMode) {
if (pDevMode->dmFields & DM_PRINTQUALITY) {
switch (pDevMode->dmPrintQuality) {
case DMRES_DRAFT:
case DMRES_LOW:
case DMRES_MEDIUM:
dwXRes = dwYRes = 300;
break;
case DMRES_HIGH:
dwXRes = dwYRes = 600;
break;
default:
dwXRes = dwYRes = (DWORD) pDevMode->dmPrintQuality;
break;
}
}
if (pDevMode->dmFields & DM_YRESOLUTION) {
dwYRes = (DWORD) pDevMode->dmYResolution;
}
}
dwMaxRes = (dwXRes >= dwYRes) ? dwXRes : dwYRes;
if (dwMaxRes <= 300) {
dwXIndex = 0;
} else if (dwMaxRes <= 600) {
dwXIndex = 1;
} else {
dwXIndex = 2;
}
// Get the color setting
dwYIndex = 1;
if (pDevMode) {
if ((pDevMode->dmFields & DM_COLOR) &&
(pDevMode->dmColor == DMCOLOR_COLOR)) {
dwYIndex = 0;
}
}
dwRequired = MemHeuristic[dwXIndex][dwYIndex];
return dwRequired;
}
VOID
CheckMemoryAvailable(
PINIJOB *ppIniJob,
BOOL bFixedJob
)
/*++
Function Description: Checks for availability of memory required for rendering the
job. Performs some scheduling based on resource requirements.
Parameters: ppIniJob - pointer to the PINIJOB to be scheduled
bFixedJob - flag to disable memory requirement checks
Return Values: NONE
--*/
{
PINIJOB pIniJob;
SIZE_T Required;
SplInSem();
if (ppIniJob) {
pIniJob = *ppIniJob;
} else {
// should not happen
return;
}
// Dont use scheduling algorithm if it has been explicitly turned off
if (!bUseEMFScheduling) {
return;
}
// Dont use scheduling algorithm for non EMF jobs
if (!pIniJob->pDatatype ||
(wstrcmpEx(pIniJob->pDatatype, gszNT4EMF, FALSE) &&
wstrcmpEx(pIniJob->pDatatype, L"NT EMF 1.006", FALSE) &&
wstrcmpEx(pIniJob->pDatatype, L"NT EMF 1.007", FALSE) &&
wstrcmpEx(pIniJob->pDatatype, gszNT5EMF, FALSE)) ) {
return;
}
Required = GetMemoryEstimate(pIniJob->pDevMode);
if (bFixedJob) {
// This job has to be assigned without memory availability checks
RemoveFromJobList(pIniJob, JOB_WAITING_LIST);
AddToJobList(pIniJob, Required, JOB_SCHEDULE_LIST);
return;
}
// Check if the job has to wait, based on
// 1. Some jobs are already waiting OR
// 2. There is insufficient memory available due to currently rendering jobs
if ((pWaitingList != NULL) ||
((AvailMemoryForRendering < Required) &&
(dwNumberOfEMFJobsRendering > 0))) {
AddToJobList(pIniJob, Required, JOB_WAITING_LIST);
*ppIniJob = NULL;
return;
}
// The job can be scheduled right away
AddToJobList(pIniJob, Required, JOB_SCHEDULE_LIST);
return;
}
PINIJOB
AssignFreeJobToFreePort(
PINIPORT pIniPort,
DWORD *pSecsToWait
)
/*++
Note: You must ensure that the port is free. This function will not
assign a job to this port, but if there exists one, it will return a
pointer to the INIJOB. Irrespective of whether it finds a job or not,
it will return the minimum timeout value that the scheduler thread
should sleep for.
--*/
{
DWORD CurrentTime; // Time in seconds
DWORD Timeout = INFINITE; // Time in seconds
DWORD SecsToWait; // Time in seconds
PINIPRINTER pTopIniPrinter, pIniPrinter;
PINIJOB pTopIniJob, pIniJob;
PINIJOB pTopIniJobOnThisPrinter, pTopIniJobSpooling;
DWORD i;
SplInSem();
if( pIniPort->Status & PP_ERROR ){
*pSecsToWait = INFINITE;
return NULL;
}
pTopIniPrinter = NULL;
pTopIniJob = NULL;
for (i = 0; i < pIniPort->cPrinters ; i++) {
pIniPrinter = pIniPort->ppIniPrinter[i];
//
// if this printer is in a state not to print skip it
//
if ( PrinterStatusBad(pIniPrinter->Status) ||
(pIniPrinter->Attributes & PRINTER_ATTRIBUTE_WORK_OFFLINE) ) {
continue;
}
//
// if we haven't found a top-priority printer yet,
// or this printer is higher priority than the top-priority
// printer, see if it has jobs to go. If we find any, the
// highest priority one will become the top priority job and
// this printer will become the top-priority printer.
//
if (!pTopIniPrinter ||
(pIniPrinter->Priority > pTopIniPrinter->Priority)) {
pTopIniJobOnThisPrinter = NULL;
pTopIniJobSpooling = NULL;
pIniJob = pIniPrinter->pIniFirstJob;
while (pIniJob) {
if (!(pIniPort->Status & PP_FILE) &&
(pIniJob->Status & JOB_PRINT_TO_FILE)) {
pIniJob = pIniJob->pIniNextJob;
continue;
}
if ((pIniPort->Status & PP_FILE) &&
!(pIniJob->Status & JOB_PRINT_TO_FILE)) {
pIniJob = pIniJob->pIniNextJob;
continue;
}
// Make sure the spooler isn't offline.
// Find a job which is not PAUSED, PRINTING etc.
// Let jobs that are DIRECT & CANCELLED through
// For RapidPrint also allow SPOOLING jobs to print
if (!(pIniJob->pIniPrinter->pIniSpooler->SpoolerFlags & SPL_OFFLINE) &&
(!(pIniJob->Status & JOB_PENDING_DELETION) || (pIniJob->pIniPrinter->Attributes&PRINTER_ATTRIBUTE_DIRECT)) &&
!(pIniJob->Status & ( JOB_PAUSED | JOB_PRINTING | JOB_COMPLETE |
JOB_PRINTED | JOB_TIMEOUT |
JOB_DESPOOLING | JOB_BLOCKED_DEVQ | JOB_COMPOUND )) &&
((!(pIniJob->pIniPrinter->Attributes & PRINTER_ATTRIBUTE_DIRECT) &&
!(pIniJob->pIniPrinter->Attributes & PRINTER_ATTRIBUTE_QUEUED)) ||
!(pIniJob->Status & JOB_SPOOLING))) {
//
// if we find such a job, then determine how much
// time, we need to wait before this job can actually
// print.
//
CurrentTime = GetCurrentTimeInSeconds();
#if DBG
if (MODULE_DEBUG & DBG_TIME)
DbgPrintTime();
#endif
SecsToWait = GetTimeToWait(CurrentTime, pIniPrinter, pIniJob);
if (SecsToWait == 0) {
// if we needn't wait at all, then we make this job the
// TopIniJob if either there is no TopIniJob or this job
// has a higher priority than an existing TopIniJob on this
// printer.
// Keep both the Highest Priority Spooling and Non
// spooling job in case we want to favour non spooling
// jobs over spooling jobs
if ( pIniJob->Status & JOB_SPOOLING ) {
if ( pTopIniJobSpooling == NULL ) {
pTopIniJobSpooling = pIniJob;
} else if ( pIniJob->pIniPrinter->Attributes & PRINTER_ATTRIBUTE_DO_COMPLETE_FIRST ) {
//
// For DO_COMPLETE_FIRST we'll take larger jobs
// first over pure priority based
//
if (( pIniJob->dwValidSize > pTopIniJobSpooling->dwValidSize ) ||
(( pIniJob->dwValidSize == pTopIniJobSpooling->dwValidSize ) &&
( pIniJob->Priority > pTopIniJobSpooling->Priority ))) {
pTopIniJobSpooling = pIniJob;
}
// For Priority Based, pick a higher priority job if it has some
// at least our minimum requirement
} else if (( pIniJob->Priority > pTopIniJobSpooling->Priority ) &&
( pIniJob->dwValidSize >= dwFastPrintSlowDownThreshold )) {
pTopIniJobSpooling = pIniJob;
}
} else {
if (!pTopIniJobOnThisPrinter ||
(pIniJob->Status & JOB_PENDING_DELETION) ||
(pIniJob->Priority > pTopIniJobOnThisPrinter->Priority)) {
pTopIniJobOnThisPrinter = pIniJob;
}
}
} else {
//
// if we have to wait then keep track of how long we
// can doze off before the next job that is to be
// scheduled later.
//
Timeout = min(Timeout, SecsToWait);
}
}
//
// loop thru all jobs on this printer.
//
pIniJob = pIniJob->pIniNextJob;
}
//
// We've already established that this printer has a
// higher priority than any previous TopIniPrinter or
// that there is no TopIniPrinter yet.
// if we did find a TopIniJobOnThisPrinter for this pIniPrinter
// update the TopIniPrinter and TopIniJob pointers
//
// We don't want to schedule Spooling Jobs whose size doesn't meet
// our minimum size requirement
if (( pTopIniJobSpooling != NULL ) &&
( dwFastPrintSlowDownThreshold > pTopIniJobSpooling->Size )) {
pTopIniJobSpooling = NULL ;
}
if ( pTopIniJobOnThisPrinter == NULL ) {
pTopIniJobOnThisPrinter = pTopIniJobSpooling;
} else {
// For FastPrint we can choose to favour Completed jobs over
// Spooling jobs
if ( !( pIniPrinter->Attributes & PRINTER_ATTRIBUTE_DO_COMPLETE_FIRST ) &&
( pTopIniJobSpooling ) &&
( pTopIniJobSpooling->Priority >= pTopIniJobOnThisPrinter->Priority )) {
pTopIniJobOnThisPrinter = pTopIniJobSpooling;
}
}
if (pTopIniJobOnThisPrinter) {
pTopIniPrinter = pIniPrinter;
pTopIniJob = pTopIniJobOnThisPrinter;
}
}
//
// This ends the if clause for finding a printer with higher priority
// than the current TopIniPrinter. Loop back and process all printers
}
//
// End of For Loop for all Printers
//
//
// if we have a TopIniJob at this stage, it means we have a job that can be
// assigned to the IniPort. We will return a pointer to this job back
// We will also copy the Timeout value that has been computed for this
// IniPort back to the SchedulerThread.
*pSecsToWait = Timeout;
return(pTopIniJob);
}
DWORD
GetCurrentTimeInSeconds(
)
/*++
Note: This function returns a value representing the time in seconds
--*/
{
SYSTEMTIME st;
GetSystemTime(&st);
return ((((st.wHour * 60) + st.wMinute) * 60) + st.wSecond);
}
/* GetTimeToWait
*
* Determines how long it is in seconds from the current time
* before the specified job should be printed on the specified printer.
*
* Parameters:
*
* CurrentTime - Current system time in seconds
*
* pIniPrinter - Pointer to INIPRINTER structure for the printer.
* This contains the StartTime and UntilTime fields.
*
* pIniJob - Pointer to INIJOB structure for the job.
* This contains the StartTime and UntilTime fields.
*
* Return value:
*
* The number of seconds till the job should be printed.
* If the job can be printed immediately, this will be 0.
* We don't support specifying the day the job should be printed,
* so the return value should always be in the following range:
*
* 0 <= return value < 86400 (60 * 60 * 24)
*
* Remarks:
*
* The user can specify hours on both the printer and the job.
* Thus a printer may be configured to print only at night,
* say between the hours 20:00 and 06:00.
* Any job submitted to the printer outside those hours
* will not print until 20:00.
* If, in addition, the user specifies the hours when the job
* may print (e.g. through Printer Properties -> Details
* in Print Manager), the job will print when the two periods
* overlap.
*
* This routine finds the two wait periods determined by the
* printer hours and the job hours respectively.
* The actual time to wait is the longer of the two.
* It therefore assumes that the two periods overlap.
* This doesn't matter if the routine is called again
* when the scheduler thread wakes up again.
*
* CHANGED: 14 June 1993
*
* The printer times are now ignored.
* When a job is submitted it inherits the printer's hours.
* These are all we need to check. Now if the printer's hours
* are changed, any already existing jobs on that printer
* will still print within the originally assigned times.
*
*
*/
DWORD
GetTimeToWait(
DWORD CurrentTime,
PINIPRINTER pIniPrinter,
PINIJOB pIniJob
)
{
/* Printer and job start and until times are in minutes.
* Convert them to seconds, so that we can start printing
* bang on the minute.
*/
DWORD PrinterStartTime = (pIniPrinter->StartTime * 60);
DWORD PrinterUntilTime = (pIniPrinter->UntilTime * 60);
DWORD JobStartTime = (pIniJob->StartTime * 60);
DWORD JobUntilTime = (pIniJob->UntilTime * 60);
DWORD PrinterTimeToWait = 0;
DWORD JobTimeToWait = 0;
DWORD TimeToWait = 0;
/* Current time must be within the window between StartTime and UntilTime
* of both the printer and the job.
* But if StartTime and UntilTime are identical, any time is valid.
*/
#ifdef IGNORE_PRINTER_TIMES
if (PrinterStartTime > PrinterUntilTime) {
/* E.g. StartTime = 20:00
* UntilTime = 06:00
*
* This spans midnight, so check we're not in the period
* between UntilTime and StartTime:
*/
if ((CurrentTime < PrinterStartTime)
&&(CurrentTime >= PrinterUntilTime)) {
/* It's after 06:00, but before 20:00:
*/
PrinterTimeToWait = (PrinterStartTime - CurrentTime);
}
} else if (PrinterStartTime < PrinterUntilTime) {
/* E.g. StartTime = 08:00
* UntilTime = 18:00
*/
if (CurrentTime < PrinterStartTime) {
/* It's after midnight, but before printing hours:
*/
PrinterTimeToWait = (PrinterStartTime - CurrentTime);
} else if (CurrentTime >= PrinterUntilTime) {
/* It's before midnight, and after printing hours.
* In this case, time to wait is the period until
* midnight plus the start time:
*/
PrinterTimeToWait = ((MIDNIGHT - CurrentTime) + PrinterStartTime);
}
}
#endif /* IGNORE_PRINTER_TIMES
/* Do the same for the job time constraints:
*/
if (JobStartTime > JobUntilTime) {
if ((CurrentTime < JobStartTime)
&&(CurrentTime >= JobUntilTime)) {
JobTimeToWait = (JobStartTime - CurrentTime);
}
} else if (JobStartTime < JobUntilTime) {
if (CurrentTime < JobStartTime) {
JobTimeToWait = (JobStartTime - CurrentTime);
} else if (CurrentTime >= JobUntilTime) {
JobTimeToWait = ((MIDNIGHT - CurrentTime) + JobStartTime);
}
}
TimeToWait = max(PrinterTimeToWait, JobTimeToWait);
DBGMSG(DBG_TRACE, ("Checking time to print %ws\n"
"\tCurrent time: %02d:%02d:%02d\n"
"\tPrinter hours: %02d:%02d to %02d:%02d\n"
"\tJob hours: %02d:%02d to %02d:%02d\n"
"\tTime to wait: %02d:%02d:%02d\n\n",
pIniJob->pDocument ?
pIniJob->pDocument :
L"(NULL)",
FORMAT_HOUR_MIN_SEC(CurrentTime),
FORMAT_HOUR_MIN(PrinterStartTime),
FORMAT_HOUR_MIN(PrinterUntilTime),
FORMAT_HOUR_MIN(JobStartTime),
FORMAT_HOUR_MIN(JobUntilTime),
FORMAT_HOUR_MIN_SEC(TimeToWait)));
return TimeToWait;
}
#if DBG
VOID DbgPrintTime(
)
{
SYSTEMTIME st;
GetLocalTime(&st);
DBGMSG( DBG_TIME,
( "Time: %02d:%02d:%02d\n", st.wHour, st.wMinute, st.wSecond ));
}
#endif
VOID UpdateJobList()
/*++
Function Description: Remove Jobs from the scheduled list which take more than 7 minutes.
This figure can be tuned up based in performance. There might be
some minor wrapping up discrepencies after 49.7 days which can be
safely ignored.
It also removes deleted, printed and abandoned jobs from the waiting
list.
This function should be called inside SplSem.
Parameters: NONE
Return Values: NONE
--*/
{
PJOBDATA *pJobList, pJobData;
DWORD dwTickCount;
SplInSem();
dwTickCount = GetTickCount();
pJobList = &pScheduleList;
while (pJobData = *pJobList) {
if ((dwTickCount - pJobData->dwScheduleTime) >= SEVEN_MINUTES) {
// Dont hold up resources for this job any more.
RemoveFromJobList(pJobData->pIniJob, JOB_SCHEDULE_LIST);
continue;
}
pJobList = &(pJobData->pNext);
}
pJobList = &pWaitingList;
while (pJobData = *pJobList) {
if (pJobData->pIniJob->Status & (JOB_PRINTING | JOB_PRINTED | JOB_COMPLETE |
JOB_ABANDON | JOB_PENDING_DELETION)) {
RemoveFromJobList(pJobData->pIniJob, JOB_WAITING_LIST);
continue;
}
pJobList = &(pJobData->pNext);
}
return;
}
BOOL AddToJobList(
PINIJOB pIniJob,
SIZE_T Required,
DWORD dwJobList)
/*++
Function Description: This function adds pIniJob to the list specified by dwJobList. It also
updates the number of rendering EMF jobs and memory available
for rendering.
This function should be called in SplSem.
Parameters: pIniJob -- Job to be removed
dwRequired -- Estimate of the memory required to render the job
dwJobList -- List to add to (Waiting List or Schedule List)
Return Values: TRUE if the node was added or already present
FALSE otherwise
--*/
{
PJOBDATA *pJobList, pJobData;
SIZE_T MemoryUse;
DWORD dwTickCount;
BOOL bReturn = TRUE;
SplInSem();
if (!pIniJob) {
return bReturn;
}
if (dwJobList == JOB_SCHEDULE_LIST) {
pJobList = &pScheduleList;
} else { // JOB_WAITING_LIST
pJobList = &pWaitingList;
}
while (pJobData = *pJobList) {
if (pJobData->pIniJob == pIniJob) {
// The job is already on the list. Dont add duplicates
break;
}
pJobList = &(pJobData->pNext);
}
if (!pJobData) {
// Append a new node to the list
if (pJobData = AllocSplMem(sizeof(JOBDATA))) {
pJobData->pIniJob = pIniJob;
pJobData->MemoryUse = Required;
pJobData->dwNumberOfTries = 0;
dwTickCount = GetTickCount();
if (dwJobList == JOB_SCHEDULE_LIST) {
pJobData->dwScheduleTime = dwTickCount;
pJobData->dwWaitTime = 0;
} else { // JOB_WAIT_TIME
pJobData->dwWaitTime = dwTickCount;
pJobData->dwScheduleTime = 0;
}
pJobData->pNext = *pJobList;
*pJobList = pJobData;
INCJOBREF(pIniJob);
if (dwJobList == JOB_SCHEDULE_LIST) {
// Update the scheduling globals
++dwNumberOfEMFJobsRendering;
if (AvailMemoryForRendering > Required) {
AvailMemoryForRendering -= Required;
} else {
AvailMemoryForRendering = 0;
}
dwLastScheduleTime = dwTickCount;
}
} else {
bReturn = FALSE;
}
}
return bReturn;
}
VOID RemoveFromJobList(
PINIJOB pIniJob,
DWORD dwJobList)
/*++
Function Description: This function removes pIniJob from the list specified by dwJobList
It also updates the number of rendering EMF jobs and memory available
for rendering. The scheduler is awakened if necessary.
This function should be called inside SplSem.
Parameters: pIniJob -- Job to be removed
dwJobList -- List to remove from (Waiting List or Schedule List)
Return Values: NONE
--*/
{
PJOBDATA *pJobList, pJobData;
SIZE_T Memory;
SplInSem();
if (!pIniJob) {
return;
}
if (dwJobList == JOB_SCHEDULE_LIST) {
pJobList = &pScheduleList;
} else { // JOB_WAITING_LIST
pJobList = &pWaitingList;
}
while (pJobData = *pJobList) {
if (pJobData->pIniJob == pIniJob) {
// Remove from the list
*pJobList = pJobData->pNext;
DECJOBREF(pIniJob);
if (dwJobList == JOB_SCHEDULE_LIST) {
// Update available memory and number of rendering jobs
Memory = AvailMemoryForRendering + pJobData->MemoryUse;
AvailMemoryForRendering = min(Memory, TotalMemoryForRendering);
--dwNumberOfEMFJobsRendering;
// Awaken the scheduler since more memory if available
CHECK_SCHEDULER();
}
FreeSplMem(pJobData);
// Break since there are no duplicates in the list
break;
}
pJobList = &(pJobData->pNext);
}
return;
}
BOOL GetJobFromWaitingList(
PINIPORT *ppIniPort,
PINIJOB *ppIniJob,
DWORD dwPriority)
/*++
Function Description: This function picks up the first job in the Waiting List that can
be assigned to some free port. It should be called from within the
SplSem.
Parameters: ppIniPort - pointer to pIniPort where the job can be scheduled
ppIniJob - pointer to pIniJob which can be scheduled
dwPriority - flag to use memory availability check
Return Values: TRUE if a job can be scheduled
FALSE otherwise
--*/
{
BOOL bReturn = FALSE;
DWORD dwIndex, CurrentTime, SecsToWait;
PINIPORT pIniPort = NULL;
PINIJOB pIniJob = NULL;
PINIPRINTER pIniPrinter = NULL;
PJOBDATA pJobData;
SplInSem();
// Initialize the port and job pointers;
*ppIniPort = NULL;
*ppIniJob = NULL;
for (pJobData = pWaitingList;
pJobData;
pJobData = pJobData->pNext) {
pIniJob = pJobData->pIniJob;
pIniPrinter = pIniJob->pIniPrinter;
// Check for memory availability
if (dwPriority == SPL_USE_MEMORY) {
if ((pJobData->MemoryUse > AvailMemoryForRendering) &&
(dwNumberOfEMFJobsRendering != 0)) {
// Insufficient memory
continue;
}
} else { // SPL_FIRST_JOB
if (pJobData->dwNumberOfTries > 2) {
continue;
}
}
// If this job cant be printed, go to the next one
if (pIniJob->Status & ( JOB_PAUSED | JOB_PRINTING |
JOB_PRINTED | JOB_TIMEOUT |
JOB_DESPOOLING | JOB_PENDING_DELETION |
JOB_BLOCKED_DEVQ | JOB_COMPOUND | JOB_COMPLETE)) {
continue;
}
// If we cant print to this printer, skip the job
if (!pIniPrinter ||
PrinterStatusBad(pIniPrinter->Status) ||
(pIniPrinter->Attributes & PRINTER_ATTRIBUTE_WORK_OFFLINE) ||
(pIniPrinter->pIniSpooler->SpoolerFlags & SPL_OFFLINE)) {
continue;
}
// For direct printing dont consider spooling jobs
if (( (pIniPrinter->Attributes & PRINTER_ATTRIBUTE_QUEUED) ||
(pIniPrinter->Attributes & PRINTER_ATTRIBUTE_DIRECT) ) &&
(pIniJob->Status & JOB_SPOOLING)) {
continue;
}
// Check if the job can print immediately
CurrentTime = GetCurrentTimeInSeconds();
SecsToWait = GetTimeToWait(CurrentTime, pIniPrinter, pIniJob);
if (SecsToWait != 0) {
continue;
}
// Check if any port attached to this printer can print this job
for (dwIndex = 0;
dwIndex < pIniPrinter->cPorts;
++dwIndex) {
pIniPort = pIniPrinter->ppIniPorts[dwIndex];
if (!pIniPort || (pIniPort->Status & PP_ERROR)) {
continue;
}
if (!(pIniPort->Status & PP_FILE) &&
(pIniJob->Status & JOB_PRINT_TO_FILE)) {
continue;
}
if ((pIniPort->Status & PP_FILE) &&
!(pIniJob->Status & JOB_PRINT_TO_FILE)) {
continue;
}
// Check if the port is already processing some job
if ( (pIniPort->pIniJob) &&
!(pIniPort->Status & PP_WAITING )){
continue;
}
// Check if the port has some chained jobs other than the current one
if ((pIniPort->Status & PP_THREADRUNNING) &&
(pIniPort->Status & PP_WAITING)) {
if ((pIniPort->pIniJob != NULL) &&
(pIniPort->pIniJob != pIniJob)) {
continue;
} else {
// We have found a port and a job to schedule
break;
}
}
}
if (dwIndex < pIniPrinter->cPorts) {
// We have a port and job
bReturn = TRUE;
pJobData->dwNumberOfTries += 1;
*ppIniJob = pIniJob;
*ppIniPort = pIniPort;
break;
}
}
return bReturn;
}