// -*-C++-*- 

/*  src/platform/threads/PThread.cpp  */

/*
 * Author: Philogelos A. <Philogelos@yahoo.com>
 * Maintainer: Philogelos A.
 * Keywords: C++, library, containers
 *
 * Copyright (C) 1999 Philogelos A.
 *
 * This file is part of Quercus Robusta.
 *
 * Quercus Robusta is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this software; see the file COPYING.LIB.  If not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 */



/* $Id$ */
#if !defined(_INLINE)
static char cvsid[] = "@(#)$Id$";
static char debugFileId[] = __FILE__;
#endif

#include "platform/threads/PThread.hpp"

#if defined( USE_PTHREADS )

#include "containers/lists/List.hpp"
#include "exceptions/NotImplemented.hpp"
#include "platform/threads/RecMutex.hpp"
#include "platform/threads/MGuard.hpp"
#include "platform/threads/ThreadDeath.hpp"
#include "exceptions/Errno.hpp"
#include "LinkManager.hpp"
#include "PGuard.hpp"
#include "Debug.hpp"
#include "Backtrace.hpp"

#include <errno.h>
#include <time.h>

extern "C" int nanosleep( const struct timespec *rqtp,
						  struct timespec *rmtp );

#define PTHC PThreadsSystem::pthrCall

static void pthreadCleanupHandler( void *aKiller )
{
  DLOGOBJECT
   	( "pthreadCleanupHandler(): %s", ( ThreadDeath * ) aKiller );
  throw ( ThreadDeath * ) aKiller;
}

static void *pthreadStartupTrampoline( void *anArg )
{
  preC_( anArg != NULL );

  CFuncCall *call;
  void *result;
  void *object;
  void *( *func )( void *arg );
  void *arg;
  CondVar *startup;
  CondVar *condition;
  CondVar *back;
  ThreadDeath *death;

  Backtrace::initThreadBacktrace();

  if( pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, ( int * ) NULL ) != 0 )
	{
	  DLOG
		( "Error in pthread_setcanceltype() in pthreadStartupTrampoline()" );
	  return ( void * ) NULL;
	}
  result = NULL;

  call = ( CFuncCall * ) anArg;
  func = call -> func;
  test_( func != ( void * (*)( void * ) ) NULL );

  arg = call -> arg;

  condition = call -> condition;
  test_( condition != ( CondVar * ) NIL );

  back = call -> back;
  test_( back != ( CondVar * ) NIL );

  startup = call -> startup;
  test_( startup != ( CondVar * ) NIL );

  {
	MGuard _waitForCreation( condition -> getMutex(), nil );
	{
	  MGuard _notifyAboutCreation( startup -> getMutex(), nil );
	  startup -> signal();
	}

	condition -> wait();

	object = *( call -> object );
	test_( object != ( Thread * ) NULL );
  }

  /* XXX need method to destroy this later */
  // death = new ThreadDeath( ( Thread * ) object, "cancellation", null );
  death = NULL;

  pthread_cleanup_push( pthreadCleanupHandler, death );
  if( pthread_setspecific( PThreadFactory::backRefKey, object ) != 0 )
	{
	  DLOG
		( "Error in pthread_setspecific() in pthreadStartupTrampoline()" );
	  return ( void * ) NULL;
	}
  {
	MGuard _signalReadiness( back -> getMutex(), nil );
	back -> signal();
  }
  try
	{
#if defined( DEBUG_THREADS )
	  DLOG( "Thread starts." );
#endif	  
	  result = ( *func )( arg );
	}
  catch( ThreadDeath *aBanshee )
	{
	  DLOG( "Cancel intercepted" );
	}
  catch( Exception *aSomeJunk )
	{
	  DLOG( "Uncatched exception in thread" );
	  Debug::getLogger() -> exception( aSomeJunk );
	}
  catch( ... )
	{
	  DLOG( "Uncacthed something catched by ellipsis" );
	}
  pthread_cleanup_pop( 0 );
#if defined( DEBUG_THREADS )
	  DLOG( "Thread ends." );
#endif	  
  return result;
}

PThreadFactory::PThreadFactory()
{
  FINIT( pthreadsSystem, new PThreadsSystem() );
  pthread_once( &initClassOnce, initClass );
}

PThreadFactory::~PThreadFactory()
{
  FDONE( pthreadsSystem );
}

Thread *PThreadFactory::createFromFunc( void * ( *aStartFunc )( void *anArg ), 
										void *anArg )
{
  pthread_attr_t attribute;
  pthread_t id;
  Thread *thread;
  CFuncCall call;
  PCondVar startup( new PMutex() );
  PCondVar condition( new PMutex() );
  PCondVar backCond( new PMutex() );

  PTHC( "pthread_attr_init", pthread_attr_init( &attribute ) );

  /* do _not_ create threads detached by default */
  //   PTHC( "pthread_attr_setdetachstate", 
  // 		pthread_attr_setdetachstate( &attribute, PTHREAD_CREATE_DETACHED ) );
  
  call.func = aStartFunc;
  call.arg = anArg;
  call.object = &thread;

  call.startup = &startup;
  call.condition = &condition;
  call.back = &backCond;

  thread = ( Thread * ) NULL;

  {
	MGuard _startup( startup.getMutex(), this );
	PTHC( "pthread_create", pthread_create
		  ( &id, &attribute, pthreadStartupTrampoline, &call ) );
	PTHC( "pthread_attr_destroy", pthread_attr_destroy( &attribute ) );
	startup.wait();
  }
  thread = new PThread( id );
  {
	MGuard _back( backCond.getMutex(), this );
	{
	  MGuard _signal( condition.getMutex(), this );
	  condition.signal();
	}
	backCond.wait();
  }
  PGuard _thread( thread, this );
  threads -> append( thread );
  return thread;
}

ThreadsSystem *PThreadFactory::getThreadSystem()
{
  return pthreadsSystem;
}

void PThreadFactory::initClass()
{
  pthread_key_create( &backRefKey, ( void (*)( void * ) ) NULL );
}

pthread_key_t PThreadFactory::backRefKey;
/* PTHREAD_ONCE_INIT is broken on Solaris. It expands to 
 { 0, 0, 0, 0 }, but each member of pthread_once_t struct is itself
 union! (u_pad64) */
pthread_once_t PThreadFactory::initClassOnce = PTHREAD_ONCE_INIT;

int PThreadsSystem::pthrCall( const char *callName, int retCode )
{
  if( ( retCode != 0 ) && ( retCode != EINTR ) )
	{
	  DLOG
		( "PThreadsSystem::pthrCall(): %s: %i", callName, retCode );
	  throw new Errno
		( String( "Error in call to POSIX threads: " ) + callName, 
		  null, retCode );
	}
  return retCode;
}

PThreadsSystem::PThreadsSystem()
{}

PThreadsSystem::~PThreadsSystem()
{}

void PThreadsSystem::onceFunc( void ( * )() )
{
  throw new NotImplemented( "PThreadsSystem::onceFunc()", null );
}

Mutex *PThreadsSystem::mutex()
{
  return new PMutex();
}

Mutex *PThreadsSystem::recMutex()
{
#if defined( __linux__ )
  pthread_mutexattr_t attribute;
  PMutex *result;

  PTHC( "pthread_mutexattr_init", pthread_mutexattr_init( &attribute ) );
  PTHC( "pthread_mutexattr_setkind_np", 
		pthread_mutexattr_setkind_np( &attribute, PTHREAD_MUTEX_RECURSIVE_NP ) );
  result = new PMutex( &attribute );
  PTHC( "pthread_mutexattr_destroy", pthread_mutexattr_destroy( &attribute ) );
  return result;
#else
  return new RecMutex();
#endif
}

CondVar *PThreadsSystem::condition()
{
  return new PCondVar( new PMutex() );
}

Semaphore *PThreadsSystem::semaphore( Index anInitialCount )
{
  preC_( anInitialCount >= 0 );
  return new PSemaphore( false, anInitialCount );
}

Thread *PThreadsSystem::getSelf()
{
  void *result;

  result = pthread_getspecific( PThreadFactory::backRefKey );
  if( result == ( Thread * ) NULL )
	{
	  return mainThread;
	}
  else
	{
	  return ( Thread * ) result;
	}
}

void PThreadsSystem::cancel( Thread *aVictim )
{
  preC_( aVictim != ( Thread * ) NULL );
  preC_( DCAST( aVictim, PThread ) != ( PThread * ) NULL );

  //  DLOGOBJECT( "cancelling: %s", aVictim );
  PTHC( "pthread_cancel", pthread_cancel( aVictim -> getId() ) );
}

void PThreadsSystem::sleep( Index aSecond, Index aNanoseconds )
{
  struct timespec delay;

  delay.tv_sec = aSecond;
  delay.tv_nsec = aNanoseconds;

  if( ::nanosleep( &delay, ( timespec * ) NULL ) == -1 )
	{
	  if( errno != EINTR )
		{
		  throw new Errno( "nanosleep() in PThreadsSystem::sleep()", null );
		}
	}
}

Thread *PThreadsSystem::getById( pthread_t )
{
  throw new NotImplemented( "PThreadsSystem::getById()", null );
}

void PThreadsSystem::exitCurrent( void *aStatus )
{
  pthread_exit( aStatus );
}


PThread *PThreadsSystem::mainThread = new PThread( -1 );

PThread::PThread( pthread_t anId )
{
  id = anId;
  name = String( "id: %i" ).sprintf( 1, id );
}

PThread::~PThread()
{}

Index PThread::getId() const
{
  return id;
}

void PThread::setId( pthread_t anId )
{
  id = anId;
}

boolean PThread::isSystemThread() const
{
  return( this == PThreadsSystem::mainThread );
}

void PThread::join()
{
  void *status;

  PTHC( "pthread_join", pthread_join( getId(), &status ) );
}

void PThread::setName( String aName )
{
  name = aName;
}

String PThread::getName() const
{
  return name;
}

String  PThread::toString() const
{
  return Object::toString() + " " + getName() +
	String( " id: " ) + String( getId(), 10 );
}

String  PThread::getClassName() const
{
  return "PThread";
}

#if defined( TESTING )
#include "OGuard.hpp"
#include "Backtrace.hpp"

#include <time.h>
#include <unistd.h>

void *t( void *arg )
{
  for( int i = 0 ; i < 100 ; ++i )
	{
	  DLOG( "t: %s", ( char * ) arg );
	  sleep( 1 );
	}
  //  throw new Errno( "test", null );
  
  return ( void * ) NULL;
}

boolean PThread::tester( int ) const
{
  while( true )
	{
	  Thread *t2;

// 	  ThreadFactory::getDefaultFactory() -> 
// 		createFromFunc( t, ( void * ) "foo bar baz ..." );
	  t2 = ThreadFactory::getDefaultFactory() -> 
		createFromFunc( t, ( void * ) "bla bla bla ..." );
	  sleep( 1 );
	  DLOG( "Hop!" );

	  DLOG( "t2: %p", t2 );
	  DLOGOBJECT( "t2: %s", t2 );
	  ThreadFactory::getDefaultFactory() -> getThreadSystem()
		-> cancel( t2 );
	  sleep( 1000 );
	  return true;
	}
}
#endif

PMutex::PMutex()
{
  PTHC( "pthread_mutex_init", pthread_mutex_init( &core, NULL ) );
}

PMutex::PMutex( pthread_mutexattr_t *anAttribute )
{
  PTHC( "pthread_mutex_init", pthread_mutex_init( &core, anAttribute ) );
}

PMutex::~PMutex()
{
  PTHC( "pthread_mutex_destroy", pthread_mutex_destroy( &core ) );
}

void PMutex::enter( const Top * )
{
  //  DLOGOBJECT( "PMutex::enter: %s", this );
  PTHC( "pthread_mutex_lock", pthread_mutex_lock( &core ) );
}

void PMutex::leave( const Top * )
{
  PTHC( "pthread_mutex_unlock", pthread_mutex_unlock( &core ) );
  //  DLOGOBJECT( "PMutex::leave: %s", this );
}

boolean PMutex::tryEnter( const Top * )
{
  int retCode;

  retCode = pthread_mutex_trylock( &core );
  if( retCode == 0 )
	{
	  return true;
	}
  else if( retCode == EBUSY )
	{
	  return false;
	}
  PTHC( "pthread_mutex_trylock", retCode );
  return true;
}

String  PMutex::toString() const
{
  return Object::toString() +
	" pthread_mutex_t: " + String( ( long int ) &core, 16 );
}


PCondVar::PCondVar( PMutex *aMutex )
{
  FNNINIT( mutex, aMutex );

  PTHC( "pthread_cond_init", 
		pthread_cond_init( &core, ( pthread_condattr_t * ) NULL ) );
}

PCondVar::~PCondVar()
{
  PTHC( "pthread_cond_destroy",
		pthread_cond_destroy( &core ) );
  FDONE( mutex );
}

Mutex *PCondVar::getMutex() const
{
  return mutex;
}

void PCondVar::wait()
{
  PTHC( "pthread_cond_wait", 
		pthread_cond_wait( &core, &( mutex -> core ) ) );
}

void PCondVar::signal()
{
  PTHC( "pthread_cond_signal", 
		pthread_cond_signal( &core ) );
}

void PCondVar::broadcast()
{
  PTHC( "pthread_cond_broadcast", 
		pthread_cond_broadcast( &core ) );
}

String  PCondVar::toString() const
{
  return Object::toString() +
	" mutex: " + mutex -> toString() + 
	" pthread_cond_t: " + String( ( long int ) &core, 16 );
}


PSemaphore::PSemaphore( boolean aShared, Index anInitialCount )
{
  PTHC( "sem_init",	sem_init( &core, aShared, anInitialCount ) );
}

PSemaphore::~PSemaphore()
{
  PTHC( "sem_destroy",	sem_destroy( &core ) );
}

void PSemaphore::inc()
{
  PTHC( "sem_post",	sem_post( &core ) );
}

void PSemaphore::dec()
{
  PTHC( "sem_wait",	sem_wait( &core ) );
}

boolean PSemaphore::tryDec()
{
  int retCode;

  retCode = sem_trywait( &core );
  if( retCode == 0 )
	{
	  return true;
	}
  else if( retCode == -1 )
	{
	  if( errno == EAGAIN )
		{
		  return false;
		}
	  else
		{
		  return PTHC( "sem_trywait", retCode );
		}
	}
  impossible_;
  return false;
}

#if defined(_INLINE)
#include "../src/Debug.ipp"
#endif

/* USE_PTHREADS */
#endif

/* $Log$ */
