/************************************************************************* * 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; }