526 lines
16 KiB
C
526 lines
16 KiB
C
/*************************************************************************
|
|
* Microsoft Windows NT *
|
|
* *
|
|
* Copyright(c) Microsoft Corp., 1994-1997. *
|
|
* *
|
|
* Revision History: *
|
|
* *
|
|
* Jan. 24,94 Koti Created *
|
|
* 03-May-97 MohsinA Performance Thread Pooling *
|
|
* Description: *
|
|
* *
|
|
* This file contains functions that process requests from LPR clients *
|
|
* *
|
|
*************************************************************************/
|
|
|
|
|
|
|
|
#include "lpd.h"
|
|
|
|
VOID CleanupConn( PSOCKCONN pscConn);
|
|
|
|
// ========================================================================
|
|
//
|
|
// SYNOPSIS: Thread Pooling Performance Fix.
|
|
// AUTHOR: MohsinA, 25-Apr-97.
|
|
// HISTORY: Boeing needs scalable lpd servers.
|
|
//
|
|
// Notes:
|
|
// This is a worker thread
|
|
// that pulls pscConn from the global queue and services them.
|
|
// It is created from LoopOnAccept when there are many jobs and
|
|
// too few WorkerThread(s).
|
|
// WorkerThread dies when there are too many idle threads or
|
|
// when shutting down.
|
|
//
|
|
|
|
DWORD WorkerThread( PSOCKCONN pscConn )
|
|
{
|
|
DWORD threadid = GetCurrentThreadId();
|
|
int stayalive = 1; // bool, loop break in cs.
|
|
int fIamLastThread= 0;
|
|
|
|
int SLEEP_TIME = 4000; // in ms, constant per thread.
|
|
int time_slept = 0; // in ms, sum.
|
|
int num_jobs = 0; // ordinal sum.
|
|
|
|
COMMON_LPD local_common;
|
|
|
|
#ifdef PROFILING
|
|
time_t time_start = time(NULL);
|
|
time_t time_done = 0;
|
|
#endif
|
|
|
|
// We randomize the sleep time, as we don't want all the
|
|
// threads to wake up together. srand must be seeded for each thread.
|
|
|
|
#ifdef PROFILING
|
|
srand( time_start );
|
|
#endif
|
|
SLEEP_TIME = 2000 + (rand() & 0x7ff); // 2000 to 4000.
|
|
|
|
|
|
// We can't use this pscConn as another thread could have pulled it out.
|
|
// Instead we go and pull another pscConn from the queue.
|
|
|
|
pscConn = NULL;
|
|
|
|
while( stayalive ){
|
|
|
|
//
|
|
// Shutdown after emptying the queue below.
|
|
// fShuttingDownGLB will clean the job in ServiceTheClient.
|
|
//
|
|
|
|
EnterCriticalSection( &csConnSemGLB );
|
|
{
|
|
if( scConnHeadGLB.pNext ){
|
|
|
|
// == Remove one from the head.
|
|
|
|
pscConn = scConnHeadGLB.pNext;
|
|
scConnHeadGLB.pNext = pscConn->pNext;
|
|
|
|
pscConn->pNext = NULL;
|
|
Common.QueueLength--;
|
|
|
|
// == Remove one from the tail.
|
|
// PSOCKCONN x = &scConnHeadGLB;
|
|
// int count = Common.QueueLength;
|
|
//
|
|
// while( x->pNext->pNext ){
|
|
// x = x->pNext;
|
|
// --count;
|
|
// assert( 0 < count );
|
|
// }
|
|
// pscConn = x->pNext;
|
|
// Common.QueueLength--;
|
|
// x->pNext = NULL;
|
|
}else{
|
|
|
|
//
|
|
// One thread dies after 16 idle SLEEP_TIME.
|
|
//
|
|
|
|
if( fShuttingDownGLB || ( Common.IdleCounter > 32 ) ){
|
|
Common.IdleCounter /= 2;
|
|
stayalive = 0;
|
|
}else{
|
|
Common.IdleCounter++;
|
|
}
|
|
pscConn = NULL;
|
|
}
|
|
assert( Common.AliveThreads >= 0 );
|
|
assert( Common.QueueLength >= 0 );
|
|
local_common = Common; // struct copy, for readonly.
|
|
}
|
|
LeaveCriticalSection( &csConnSemGLB );
|
|
|
|
if( pscConn )
|
|
{
|
|
num_jobs++;
|
|
ServiceTheClient( pscConn );
|
|
}
|
|
else if( stayalive )
|
|
{
|
|
// LOGIT(( "PROFILING: thread %3d sleeping %d, IdleCounter=%d\n",
|
|
// threadid, SLEEP_TIME, local_common.IdleCounter ));
|
|
Sleep( SLEEP_TIME );
|
|
time_slept += SLEEP_TIME;
|
|
}
|
|
|
|
} // while stayalive.
|
|
|
|
// ====================================================================
|
|
|
|
#ifdef PROFILING
|
|
time_done = time(NULL);
|
|
LOGIT(("PROFILING: thread %3d ends, jobs=%d, life=%d, slept=%d,\n"
|
|
" AliveThreads=%d -1, MaxThreads=%d,\n"
|
|
" TotalAccepts=%d, TotalErrors=%d, IdleCounter=%d\n"
|
|
" Time now is %s"
|
|
,
|
|
threadid, num_jobs, time_done - time_start, time_slept/1000,
|
|
local_common.AliveThreads, local_common.MaxThreads,
|
|
local_common.TotalAccepts, local_common.TotalErrors,
|
|
local_common.IdleCounter,
|
|
ctime(&time_done)
|
|
));
|
|
#else
|
|
LOGIT(("WorkerThread: thread %3d ends.\n", threadid ));
|
|
#endif
|
|
|
|
EnterCriticalSection( &csConnSemGLB );
|
|
{
|
|
Common.AliveThreads--;
|
|
|
|
fIamLastThread = (Common.AliveThreads < 1 );
|
|
}
|
|
LeaveCriticalSection( &csConnSemGLB );
|
|
|
|
|
|
if( fIamLastThread && fShuttingDownGLB ){
|
|
LOGIT(("WorkerThread: Last worker thread exiting\n"));
|
|
SetEvent( hEventLastThreadGLB );
|
|
}
|
|
|
|
return NO_ERROR; // Thread Ends.
|
|
}
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* *
|
|
* ServiceTheClient(): *
|
|
* This function reads and interprets the request from the LPR client and *
|
|
* takes appropriate action. In that sense, this routine is the heart of *
|
|
* LPD service. *
|
|
* *
|
|
* Returns: *
|
|
* NO_ERROR (always) *
|
|
* *
|
|
* Parameters: *
|
|
* pscConn (IN-OUT): pointer to SOCKCONN structure for this connection *
|
|
* *
|
|
* History: *
|
|
* Jan.24, 94 Koti Created *
|
|
* *
|
|
****************************************************************************
|
|
*/
|
|
|
|
|
|
|
|
DWORD ServiceTheClient( PSOCKCONN pscConn )
|
|
{
|
|
|
|
DWORD dwErrcode;
|
|
DWORD dwResponse;
|
|
CHAR chCmdCode;
|
|
DWORD threadid = GetCurrentThreadId();
|
|
|
|
|
|
pscConn->fLogGenericEvent = TRUE;
|
|
pscConn->dwThread = threadid;
|
|
pscConn->hPrinter = (HANDLE)INVALID_HANDLE_VALUE;
|
|
|
|
#ifdef PROFILING
|
|
pscConn->time_start = time(NULL);
|
|
#endif
|
|
|
|
if ( fShuttingDownGLB ){
|
|
LOGIT(("ServiceTheClient: Thread %3d shutting down.\n", threadid ));
|
|
goto ServiceTheClient_BAIL;
|
|
}
|
|
|
|
// who are we connected to?
|
|
|
|
GetClientInfo( pscConn );
|
|
|
|
//
|
|
// get server ip address, since print clustering allows one
|
|
// node to have multiple ip addresses. Depending on the ip
|
|
// address, we'll go to different sets of print queues on the node.
|
|
//
|
|
// Albert Ting cluster change, MohsinA, 07-Mar-97.
|
|
//
|
|
|
|
GetServerInfo( pscConn );
|
|
|
|
// get command from the client
|
|
// ----------------- command 02 => "Receive Job"
|
|
// | 02 | Queue LF | Queue => Queue or Printer to print on
|
|
// -----------------
|
|
|
|
if ( GetCmdFromClient( pscConn ) != NO_ERROR )
|
|
{
|
|
// didn't get a command from client: it's bad news!
|
|
|
|
LPD_DEBUG( "GetCmdFromClient() failed in ServiceTheClient()!\n" );
|
|
|
|
goto ServiceTheClient_BAIL;
|
|
}
|
|
|
|
|
|
// get name of the queue (printer) from the command. If it's not
|
|
// formatted properly, quit!
|
|
|
|
if ( !ParseQueueName( pscConn ) )
|
|
{
|
|
PCHAR aszStrings[2]={ pscConn->szIPAddr, NULL };
|
|
|
|
LpdReportEvent( LPDLOG_BAD_FORMAT, 1, aszStrings, 0 );
|
|
|
|
pscConn->fLogGenericEvent = FALSE;
|
|
|
|
LPD_DEBUG( "ParseQueueName() failed in ServiceTheClient()!\n" );
|
|
|
|
goto ServiceTheClient_BAIL;
|
|
}
|
|
|
|
|
|
// ====================================================================
|
|
|
|
chCmdCode = pscConn->pchCommand[0];
|
|
|
|
switch( chCmdCode )
|
|
{
|
|
case LPDC_RECEIVE_JOB:
|
|
|
|
pscConn->wState = LPDS_RECEIVE_JOB;
|
|
|
|
ProcessJob( pscConn );
|
|
CleanupConn( pscConn );
|
|
|
|
if ( pscConn->wState != LPDS_ALL_WENT_WELL )
|
|
{
|
|
AbortThisJob( pscConn );
|
|
|
|
if ( pscConn->fLogGenericEvent )
|
|
{
|
|
PCHAR aszStrings[2]={ pscConn->szIPAddr, NULL };
|
|
|
|
LpdReportEvent( LPDLOG_DIDNT_WORK, 1, aszStrings, 0 );
|
|
}
|
|
}
|
|
|
|
if (pscConn->fMustFreeLicense)
|
|
{
|
|
NtLSFreeHandle(pscConn->LicenseHandle);
|
|
}
|
|
break;
|
|
|
|
|
|
case LPDC_RESUME_PRINTING:
|
|
|
|
pscConn->wState = LPDS_RESUME_PRINTING;
|
|
|
|
if ( fAllowPrintResumeGLB )
|
|
{
|
|
dwResponse = ( ResumePrinting( pscConn ) == NO_ERROR ) ?
|
|
LPD_ACK : LPD_NAK;
|
|
}
|
|
else
|
|
{
|
|
dwResponse = LPD_NAK;
|
|
}
|
|
|
|
dwErrcode = ReplyToClient( pscConn, (WORD)dwResponse );
|
|
|
|
break;
|
|
|
|
|
|
case LPDC_SEND_SHORTQ:
|
|
|
|
case LPDC_SEND_LONGQ:
|
|
|
|
pscConn->wState = LPDS_SEND_LONGQ;
|
|
|
|
if ( ParseQueueRequest( pscConn, FALSE ) != NO_ERROR )
|
|
{
|
|
LPD_DEBUG( "ServiceTheClient(): ParseQueueRequest() failed\n" );
|
|
|
|
break;
|
|
}
|
|
|
|
SendQueueStatus( pscConn, LPD_LONG );
|
|
|
|
break;
|
|
|
|
|
|
case LPDC_REMOVE_JOBS:
|
|
|
|
if ( !fJobRemovalEnabledGLB )
|
|
{
|
|
break;
|
|
}
|
|
|
|
pscConn->wState = LPDS_REMOVE_JOBS;
|
|
|
|
if ( ParseQueueRequest( pscConn, TRUE ) != NO_ERROR )
|
|
{
|
|
LPD_DEBUG( "ServiceTheClient(): ParseQueueRequest() failed\n" );
|
|
|
|
break;
|
|
}
|
|
|
|
if ( RemoveJobs( pscConn ) == NO_ERROR )
|
|
{
|
|
ReplyToClient( pscConn, LPD_ACK );
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
}
|
|
|
|
// ====================================================================
|
|
|
|
if ( pscConn->wState != LPDS_ALL_WENT_WELL ){
|
|
goto ServiceTheClient_BAIL;
|
|
}
|
|
|
|
#ifdef PROFILING
|
|
pscConn->time_done = time(NULL);
|
|
LOGIT(("PROFILING: ok, thread %3d, time_queued %s"
|
|
" wait=%d, work=%d\n",
|
|
threadid,
|
|
ctime(&pscConn->time_queued),
|
|
pscConn->time_start - pscConn->time_queued,
|
|
pscConn->time_done - pscConn->time_start
|
|
));
|
|
#endif
|
|
|
|
// close the connection down and terminate the thread
|
|
|
|
TerminateConnection( pscConn );
|
|
pscConn = NULL;
|
|
|
|
return NO_ERROR;
|
|
|
|
// ====================================================================
|
|
// if we reached here, then a non-recoverable error occured somewhere:
|
|
// try to inform the client (by sending a NAK) and terminate the thread
|
|
|
|
ServiceTheClient_BAIL:
|
|
|
|
#ifdef PROFILING
|
|
pscConn->time_done = time(NULL);
|
|
LOGIT(("PROFILING: bail, thread %3d, job times %8d, wait=%d work=%d\n",
|
|
threadid,
|
|
pscConn->time_queued,
|
|
pscConn->time_start - pscConn->time_queued,
|
|
pscConn->time_done - pscConn->time_start
|
|
));
|
|
#endif
|
|
|
|
LPD_DEBUG( "Reached ServiceTheClient_BAIL.\n" );
|
|
ReplyToClient( pscConn, LPD_NAK );
|
|
TerminateConnection( pscConn );
|
|
pscConn = NULL;
|
|
|
|
return NO_ERROR;
|
|
|
|
} // end ServiceTheClient()
|
|
|
|
|
|
/*****************************************************************************
|
|
* *
|
|
* TerminateConnection(): *
|
|
* This function releases all the memory that was allocated while *
|
|
* processing the client's requests, closes the printer, closes the *
|
|
* socket connection, removes its structure (pscConn) from the global *
|
|
* linked list and frees the memory allocated for pscConn itself. *
|
|
* Also, if the main thread is waiting on this thread for shutdown then *
|
|
* this function sets hEventLastThreadGLB event to tell the main thread *
|
|
* that this thread is done. *
|
|
* *
|
|
* Returns: *
|
|
* Nothing *
|
|
* *
|
|
* Parameters: *
|
|
* pscConn (IN-OUT): pointer to SOCKCONN structure for this connection *
|
|
* *
|
|
* History: *
|
|
* Jan.24, 94 Koti Created *
|
|
* *
|
|
*****************************************************************************/
|
|
|
|
VOID TerminateConnection( PSOCKCONN pscConn )
|
|
{
|
|
|
|
// PSOCKCONN pscCurrent;
|
|
// BOOL fIamLastThread=FALSE;
|
|
|
|
// it should never be NULL at this point! But check it anyway!
|
|
|
|
if ( pscConn == (PSOCKCONN) NULL )
|
|
{
|
|
LPD_DEBUG( "TerminateConnection(): pscConn NULL at entry\n" );
|
|
return;
|
|
}
|
|
|
|
ShutdownPrinter( pscConn );
|
|
|
|
if ( pscConn->hPrinter != (HANDLE)INVALID_HANDLE_VALUE )
|
|
{
|
|
LPD_DEBUG( "TerminateConnection: hPrinter not closed\n" );
|
|
}
|
|
|
|
// close the socket
|
|
|
|
if ( pscConn->sSock != INVALID_SOCKET )
|
|
{
|
|
SureCloseSocket( pscConn->sSock );
|
|
}
|
|
|
|
//
|
|
// release memory in every field of the structure
|
|
//
|
|
|
|
if ( pscConn->pchCommand != NULL )
|
|
LocalFree( pscConn->pchCommand );
|
|
|
|
if ( pscConn->pchPrinterName != NULL )
|
|
LocalFree( pscConn->pchPrinterName );
|
|
|
|
//
|
|
// no memory was allocated for ppchUsers[] and adwJobIds[]. They just
|
|
// pointed to parts of what's freed by ( pscConn->pchCommand ) above.
|
|
//
|
|
|
|
if ( pscConn->pqStatus != NULL )
|
|
LocalFree( pscConn->pqStatus );
|
|
|
|
// EnterCriticalSection( &csConnSemGLB );
|
|
// {
|
|
//
|
|
// if( Common.AliveThreads <= 1 ){
|
|
// fIamLastThread = TRUE;
|
|
// }
|
|
//
|
|
// //
|
|
// // // remove this structure from the link
|
|
// //
|
|
// // pscCurrent = &scConnHeadGLB;
|
|
// //
|
|
// // while( pscCurrent ){
|
|
// // if (pscConn == pscCurrent->pNext)
|
|
// // break;
|
|
// // pscCurrent = pscCurrent->pNext;
|
|
// //
|
|
// // // what if we can't find our pscConn in the list at all?
|
|
// // // this should NEVER ever happen, but good to check!
|
|
// //
|
|
// // if( pscCurrent == NULL)
|
|
// // {
|
|
// // LocalFree( pscConn );
|
|
// // LPD_DEBUG( "TerminateConnection(): "
|
|
// // "couldn't find pscConn "
|
|
// // " in the list!\n" );
|
|
// // LeaveCriticalSection( &csConnSemGLB );
|
|
// // return;
|
|
// // }
|
|
// // }
|
|
// // pscCurrent->pNext = pscConn->pNext;
|
|
// }
|
|
// LeaveCriticalSection( &csConnSemGLB );
|
|
|
|
memset( pscConn, 0, sizeof( SOCKCONN ) );
|
|
|
|
LocalFree( pscConn );
|
|
|
|
// //
|
|
// // if shutdown is in progress and we are the last active thread, tell
|
|
// // the poor main thread (blocked for us to finish) that we're done!
|
|
// //
|
|
//
|
|
// if( fIamLastThread && fShuttingDownGLB ){
|
|
// LOGIT(("TerminateConnection: Last worker thread exiting\n"));
|
|
// SetEvent( hEventLastThreadGLB );
|
|
// }
|
|
|
|
return;
|
|
}
|