/*
 * Copyright 1995, 2000 Perforce Software.  All rights reserved.
 *
 * This file is part of Perforce - the FAST SCM System.
 */

/*
 * threading.h -- handle multiple users at the same time
 *
 * These classes are meant to take the vicious ifdef hacking required
 * to get threading (forking, whatever) to work on different platforms.  
 *
 * Some terms:
 *
 *	"thread": a process on UNIX; a thread on NT
 *	"leader": the parent process on UNIX; the only process on NT
 *
 * Classes defined:
 *
 *	Threading: caller interface to launch multiple threads
 *
 *	Threader: implementation of threading (not public) 
 *
 *  	Thread (abstract): glues caller's execution object so that Threading 
 *  	can run and then delete it.
 *
 *  	Process (abstract): callbacks into caller's environment to signal 
 *  	process related changes.
 *
 * Public methods:
 *
 *	Thread::Run() - do what was supposed to happen in the thread
 *	Thread::~Thread() - delete user's class, cleaning up thread exit
 *
 *	Process::Child() - indicates that Run() will be a child process
 *	Process::Cancel() - indicates that leader should stop launching
 *
 *	Threading::Launch() - create a thread/process and call Thread::Run().
 *	
 *	Threading::Cancelled() - returns true (in leader) if Cancel()
 *	Threading::Restarted() - returns true (in leader) if Restart()
 *
 *	Threading::Cancel() - can be called from any thread to tell the 
 *			leader to stop; leader calls Process::Cancel()
 *	Threading::Restart() - can be called from any thread to tell the 
 *			leader to restart; leader calls Process::Restart()
 *	Threading::NoDowngrade() - can be called from any thread to tell the 
 *			leader that the next restart should not be downgraded
 *			to a shutdown if threads linger on Windows
 *
 *	Threading::Reap() - called in leader to kill children
 *
 *	Threading::GetThreadCount() - returns the current number of threads
 *                                    Only valid in the parent/main process!
 *
 * The current termination ritual:
 *
 *	Someone, somewhere calls Threading::Cancel(), which is static
 *	and always available.  It can get called by the leader
 *	catching SIGTERM, or by anyone at the user's request.
 *
 *	If a child gets Threading::Cancel() on UNIX, it sends a SIGTERM
 *	to its parent so that the leader gets Threading::Cancel() called.
 *
 *	In the leader, Threading::Cancel() sets the "cancelled" flag and
 *	calls Process::Cancel(), so that (in fact) the listen socket
 *	descriptor gets closed, breaking the accept() loop.
 *
 *	The leader, out of its thread creation loop, can call Reap()
 *	in order to kill and collect all the child processes.  It should
 *	only do that if the database is safely locked from child process
 *	access.
 *
 * Restart is just like Cancel but the leader re-starts all processing rather
 * than exiting.
 */

enum ThreadMode {
	TmbSingle,	// just single threading
	TmbMulti,	// multi threading (fork, threads)
	TmbDaemon,	// fork, then forking multi threading (UNIX)
	TmbThreads,	// multi threading (pthreads, threads)
	TmbDaemonSafe	// fork, then multi threading closing stdio (UNIX)
} ;

class Thread {

    public:

	virtual		~Thread();
	virtual void	Run() = 0;

} ;

class Process {

    public:

	virtual		~Process();
	virtual void	Child() = 0;
	virtual void	Cancel() = 0;

# ifndef OS_NT
	virtual void	Cleanup ( pid_t pid ) {}
# endif
} ;

class Threader {

    protected:

    friend class Threading;

			Threader()
	                { cancelled = 0; restarted = 0; canDowngrade = 1;
	                  threadCount = 0; process = 0; }

	virtual		~Threader();
	virtual void	Launch( Thread *t );
	virtual void	Cancel();
	virtual void	Restart();
	virtual void	NoDowngrade();
	virtual void	Quiesce();
	virtual void	Reap();

# ifndef OS_NT
	virtual void	Cleanup( pid_t pid );
# endif
	virtual int	GetThreadCount(); // varies on each system

	int		cancelled;
	int		restarted;
	int		canDowngrade;
	Process		*process;

	int		threadCount; // not used by all implementations...

    public:
	static int	IsDaemon( ThreadMode tmb )
	    { return tmb == TmbDaemon || tmb == TmbDaemonSafe; }
} ;

class Threading {

    public:
			Threading( ThreadMode tmb, Process *p );
			~Threading() { delete threader; }

	void		Launch( Thread *t ) { threader->Launch( t ); }
	int		Cancelled() { return threader->cancelled; }
	int		Restarted() { return threader->restarted; }
	void		Quiesce() { threader->Quiesce(); }
	void		Reap() { threader->Reap(); }

	static void	Cancel() { if( current ) current->Cancel(); }
	static void	Restart() { if( current ) current->Restart(); }
	static void	NoDowngrade() { if( current ) current->NoDowngrade(); }
	static int	WasCancelled() { if( current ) return current->cancelled; else return 0; }
	static int	WasRestarted() { if( current ) return current->restarted; else return 0; }

	static int	GetThreadCount()
	                { return current ? current->GetThreadCount() : -1; }
	static void	LaunchCurrentThread( Thread *t ) { if( current ) current->Launch( t ); }

# ifndef OS_NT
	static void	Cleanup( pid_t pid ) { if( current ) current->Cleanup( pid ); }
# endif

    private:

	Threader 	*threader;

	static Threader *current;

} ;

