/*
* Copyright 1995, 1996 Perforce Software. All rights reserved.
*
* This file is part of Perforce - the FAST SCM System.
*/
/*
* Threader.cc -- handle multiple users at the same time
*/
# define NEED_SIGNAL
# define NEED_FORK
# define NEED_SLEEP
# define NEED_ERRNO
# include <stdhdrs.h>
# include <pid.h>
# include <strbuf.h>
# include <error.h>
# include <errorlog.h>
# include <msgserver.h>
# include <datetime.h>
# include <threading.h>
/*
* Regarding GetThreadCount:
*
* Different threaders maintain their thread counts in different ways. The
* implementations all try to keep an accurate thread count, but it is
* possible that the thread count could be inaccurate. Callers of this method
* should try to use the information primarily for monitoring and diagnosis,
* and for performance analysis.
*
* Currently, GetThreadCount is used for:
* - server.maxcommands,
* - thread high-water-mark tracing in the server log
*/
/*
*
* Threader - single Threader
*
*/
Threader::~Threader()
{
}
void
Threader::Launch( Thread *t )
{
threadCount++;
t->Run();
threadCount--;
delete t;
}
void
Threader::Cancel()
{
cancelled = 1;
process->Cancel();
}
void
Threader::Restart()
{
restarted = 1;
process->Cancel();
}
void
Threader::Quiesce()
{
// no special work for single threading
}
void
Threader::Reap()
{
// no special termination for single threading
}
int
Threader::GetThreadCount()
{
return threadCount;
}
/*
*
* MultiThreader - threading on NT, using (uh) threads
*
*/
# ifdef OS_NT
# define HAVE_MULTITHREADER
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <process.h>
# include "ntthdlist.h"
static NtThreadList *NT_ThreadList = 0;
unsigned int WINAPI
NtThreadProc( void *param )
{
Thread *t = (Thread *)param;
// Since we are running in the same address space as parent
// process we don't want to call DisService.
NT_ThreadList->AddThread( t, GetCurrentThreadId() );
t->Run();
if( !NT_ThreadList->RemoveThread( t ) )
{
Error e;
char msg[128];
sprintf( msg, "Can't remove thread entry %d", GetCurrentThreadId() );
e.Set( E_FATAL, msg );
AssertLog.Report( &e );
}
delete t;
_endthreadex(0);
return 0;
}
class MultiThreader : public Threader {
public:
MultiThreader( ThreadMode tmb )
{
delete NT_ThreadList;
NT_ThreadList = new NtThreadList();
}
void Launch( Thread *t )
{
unsigned int ThreadId;
HANDLE h = (HANDLE)_beginthreadex(
NULL,
0,
NtThreadProc,
(void *)t,
0,
&ThreadId );
if( !h )
{
// create thread failed
Error e;
e.Sys( "_beginthreadex()", "NtThreadProc" );
e.Set( E_FATAL, "Can't create process" );
AssertLog.Report( &e );
delete t;
return;
}
CloseHandle( h );
}
// By now threads have been notified to self terminate.
// We wait 60 seconds for the thread count to go to 0.
// In practice most threads will terminate immediately.
// Any hold out threads will have to be suspended in Reap.
void Quiesce()
{
Error e;
DateTime date;
StrBuf dt;
date.SetNow();
date.Fmt( dt.Alloc( DateTimeBufSize ) );
e.Set( MsgServer::Quiescing )
<< dt.Text()
<< Pid().GetProcID()
<< GetThreadCount();
AssertLog.Report( &e );
int retries = 0;
while( ! NT_ThreadList->Empty() && retries++ < 60 )
sleep( 2 );
if( retries < 60 )
return;
e.Clear();
e.Set( MsgServer::QuiesceFailed )
<< dt.Text()
<< Pid().GetProcID();
AssertLog.Report( &e );
}
// Locks have been taken, threads should not proceed further.
// Continue with the restart if there are no hold out threads,
// otherwise suspend any hold out threads and shutdown.
void Reap()
{
if( NT_ThreadList->Empty() )
return;
if( restarted )
{
Error e;
DateTime date;
StrBuf dt;
date.SetNow();
date.Fmt( dt.Alloc( DateTimeBufSize ) );
e.Set( MsgServer::ReDowngrade )
<< dt.Text()
<< Pid().GetProcID();
AssertLog.Report( &e );
restarted = 0;
cancelled = 1;
}
NT_ThreadList->SuspendThreads();
}
int GetThreadCount()
{
return NT_ThreadList->GetThreadCount();
}
} ;
# endif
# ifdef OS_BEOS
# define HAVE_MULTITHREADER
# include <OS.h>
static status_t
BeOSRunBinder( void *param )
{
Thread *t = (Thread *)param;
// Since we are running in the same address space as parent
// process we don't want to call DisService.
t->Run();
delete t;
return B_OK;
}
class MultiThreader : public Threader {
public:
MultiThreader( ThreadMode tmb )
{
}
void Launch( Thread *t )
{
thread_id threadID;
threadID = spawn_thread(
BeOSRunBinder, "p4d task",
B_NORMAL_PRIORITY,
(void *)t);
if( threadID < 0 )
{
// Create thread failed;
t->Run();
delete t;
return;
}
resume_thread(threadID);
}
int GetThreadCount() { return -1; } // not implemented on BEOS
} ;
# endif /* OS_BEOS */
/*
*
* MultiThreader - multi Threader on God-fearing UNIX
*
* Everything is easy in the forking scheme, except termination.
*
* Launching is done by forking. We catch SIGCHILD just to do the
* requisite wait() calls to reap the child's exit status.
*
* Termination is orchestrated by the parent. If a child's Cancel()
* is called, it makes itself immune from SIGTERM and then send SIGTERM
* to its parent. The parent, on SIGTERM, resends SIGTERM to its process
* group, killing everyone. Why doesn't the child just send SIGTERM to
* the process group? Not sure yet.
*
* Restart is like termination, but it uses SIGHUP, not SIGTERM.
*/
# if defined( SIGCHLD ) && \
!defined( OS_BEOS ) && \
!defined( OS_AS400 ) && \
!defined( OS_VMS )
# define HAVE_MULTITHREADER
# define HAVE_SIGHUP_HANDLER
/* For MVS, which must have different C/C++ linkage */
/* This precludes making them static, alas */
extern "C" void HandleSigChld( int flag );
extern "C" void HandleSigTerm( int flag );
extern "C" void HandleSigHup( int flag );
static int *threadCountPtr = 0;
void
HandleSigChld( int flag )
{
int status;
pid_t pid;
# if defined(OS_CYGWIN) || defined(OS_LINUX26)
// Cygwin (2.95.3) doesn't restore errno on return from interrupt.
// This leads accept() to return ECHILD rather than EINTR.
int save_errno = errno;
# endif
/*
* Note: Changed waitpid code below from waiting on all child processes
* (-1) to waiting on processes in process group (0). This change protects
* the child process p4zk which detaches itself from the parent's process
* group. When p4d is restarting we want to kill all child processes but
* keep the p4zk process going. (If invoking shutdown, p4zk will notice
* when connection to p4d closes and will exit)
*/
while( ( pid = waitpid( (pid_t)0, &status, WNOHANG ) ) > 0 )
{
if( threadCountPtr )
--(*threadCountPtr);
if( WIFSIGNALED( status ) )
{
Error e;
if( WTERMSIG( status ) != SIGTERM )
e.Set( E_FATAL,
"Process %pid% exited on a signal %signal%!" );
else
e.Set( E_INFO,
"Process %pid% terminated normally during server shutdown." );
e << pid << WTERMSIG(status);
AssertLog.Report( &e );
}
}
# if defined(OS_CYGWIN) || defined(OS_LINUX26)
errno = save_errno;
# endif
// Reinstate signal handler. Necessary on some SysV boxes (HP/UX at
// least) and does no harm elsewhere.
signal( SIGCHLD, HandleSigChld );
}
void
HandleSigTerm( int flag )
{
// The child just plain exits on sigterm
// The parent invokes the global stopping logic.
Threading::Cancel();
}
void
HandleSigHup( int flag )
{
Threading::Restart();
}
class MultiThreader : public Threader {
public:
MultiThreader( ThreadMode tmb )
{
// Daemon? Parent forks and exits, leaving child to
// call the shots.
if( tmb == TmbDaemon && fork() > 0 )
exit( 0 );
threadCountPtr = &threadCount; // SIGCHLD handler will decrement it
// become the leader of our process group
// This way we can kill everyone with one stroke.
setpgid( 0, getpid() );
// We'll catch this once
signal( SIGTERM, HandleSigTerm );
signal( SIGHUP, HandleSigHup );
}
void Launch( Thread *t )
{
// We'll catch this for each child.
// BSD reinstalls this after each signal, but others don't.
// Some OS's allow you to ignore the signal and will reap
// children automatically. This is good for AIX, because
// manual reaping just doesn't seem to work. Note that due to
// this, AIX doesn't provide a valid GetThreadCount() value.
# if defined ( OS_AIX41 ) || ( OS_AIX43 ) || ( OS_AIX53 )
signal( SIGCHLD, SIG_IGN );
# else
signal( SIGCHLD, HandleSigChld );
threadCount++;
# endif
# if defined ( OS_LINUX26 )
//
// Reap any dead children
// (works around Linux kernel bug fixed in 2.6.11).
//
int status;
pid_t pid;
/*
* Note: Changed waitpid code below from waiting on all child processes
* (-1) to waiting on processes in process group (0). This change protects
* the child process p4zk which detaches itself from the parent's process
* group. When p4d is restarting we want to kill all child processes but
* keep the p4zk process going. (If invoking shutdown, p4zk will notice
* when connection to p4d closes and will exit)
*/
while( ( pid = waitpid( (pid_t)0, &status, WNOHANG ) ) > 0 )
{
if( WIFSIGNALED( status ) )
{
Error e;
e.Set( E_FATAL,
"Process %pid% exited on a signal %signal%!" );
e << pid << WTERMSIG(status);
AssertLog.Report( &e );
}
threadCount--;
}
# endif
switch( fork() )
{
case -1:
/*
* Fork failed. We could bail out here, but since we have
* the wherewithall to run the request, we'll just go ahead
* and do it, hoping that the crisis will abate.
*/
{
Error e;
e.Set( E_FATAL, "fork() failed, single threading!\n");
AssertLog.Report( &e );
}
t->Run();
threadCount--;
delete t;
break;
case 0:
/*
* The child. Note it so that we know to handle Stop() by
* sending a signal to our parent. To be nice, we drop the
* listen socket needed by the parent, using Unlisten().
* We then run the request, delete the handler, and exit.
*
* Revert SIGCHLD here, as on OSF it will spoil RunCmd().
*/
process->Child();
signal( SIGTERM, SIG_DFL );
signal( SIGHUP, SIG_DFL );
signal( SIGCHLD, SIG_DFL );
t->Run();
delete t;
exit(0);
break;
default:
/*
* The parent. Delete the handler, which will close the
* child connection, and return so that we can go back
* to servicing further requests.
*/
delete t;
break;
}
}
# ifdef OS_SUNOS
// sunos requires an argument
# define getpgrp() getpgrp(0)
# endif
# if defined ( OS_AIX41 ) || ( OS_AIX43 ) || ( OS_AIX53 )
// Dumbo getpgrp() returns garbage on AIX41.
# define getpgrp() getpgid(0)
# endif
void Cancel()
{
if( getpgrp() != getpid() )
{
// The child really has no control over what's going
// on, so it just sends a SIGTERM to the parent to tell
// it to shut down. We want this child to exit nicely, so
// we block the SIGTERM the parent will send _us_.
signal( SIGTERM, SIG_IGN );
kill( getpgrp(), SIGTERM );
}
else
{
// The parent knows what to do:
Threader::Cancel();
}
}
void Restart()
{
if( getpgrp() != getpid() )
{
signal( SIGTERM, SIG_IGN );
kill( getpgrp(), SIGHUP );
}
else
{
Threader::Restart();
}
}
void Quiesce()
{
// Someday we might handle the sub-processes here.
}
void Reap()
{
// Kill all child processes.
// 1. Block SIGTERM so #2 doesn't get us.
// 2. Kill everyone in our process group.
// 3. Wait for them to die.
signal( SIGTERM, SIG_IGN );
kill( 0, SIGTERM );
int status;
/*
* Note: Changed waitpid code below from waiting on all child processes
* (-1) to waiting on processes in process group (0). This change protects
* the child process p4zk which detaches itself from the parent's process
* group. When p4d is restarting we want to kill all child processes but
* keep the p4zk process going. (If invoking shutdown, p4zk will notice
* when connection to p4d closes and will exit)
*
* We now signal SIGTERM periodically in order to encourage our
* children to die; this avoids a deadlock during shutdown or restart
* when the parent has locked db.* but a child is trying to lock one
* of them. The parent was waiting for the child to die, and the child
* was waiting for the parent to release the lock.
*/
int pid = 0;
int error = 0;
do
{
errno = 0;
pid = waitpid( (pid_t)0, &status, WNOHANG );
error = errno;
if( pid == 0 ) // sleep only if no child exited this time around
{
kill( 0, SIGTERM ); // die! die! my darling!
msleep( 50 );
}
} while( (pid >= 0) || (error != ECHILD) );
}
int GetThreadCount()
{
# if defined ( OS_AIX41 ) || ( OS_AIX43 ) || ( OS_AIX53 )
// Since SIGCHLD doesn't work on AIX, thread count is unavailable.
return -1;
# else
return threadCount;
# endif
}
} ;
# endif /* UNIX */
# ifndef HAVE_MULTITHREADER
class MultiThreader : public Threader
{
public:
MultiThreader( ThreadMode tmb )
{
}
} ;
# endif
/*
*
* Threader -- generic top level threading
*
*/
Threader *Threading::current = 0;
Threading::Threading( ThreadMode tmb, Process *p )
{
switch( tmb )
{
case TmbSingle:
# ifdef HAVE_SIGHUP_HANDLER
signal( SIGHUP, HandleSigHup );
# endif
threader = new Threader;
break;
case TmbMulti:
case TmbDaemon:
threader = new MultiThreader( tmb );
break;
}
threader->process = p;
current = threader;
}
Thread::~Thread() {}
Process::~Process() {}