// -*- c++ -*- /* * MICO --- an Open Source CORBA implementation * Copyright (c) 1997-2018 by The Mico Team * * OSThread: An abstract Thread class for MICO * Copyright (C) 1999 Andy Kersting & Andreas Schultz * * This library 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 of the License, or (at your option) any later version. * * This library 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 library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * For more information, visit the MICO Home Page at * http://www.mico.org/ * * support for POSIX Draft 10ish Pthreads for Linux as developed by * Xavier LeRoy@inria.fr * */ #ifndef __OS_THREAD_PTHREADS_H__ #define __OS_THREAD_PTHREADS_H__ void _init (); void _cleanup (); // // Mutex // class CondVar; //! // \ingroup micomt // // The Mutex class is a simple synchronization object used to protect // critical sections. // class Mutex #ifdef DEBUG_NAMES : public NamedObject #endif // DEBUG_NAMES { public: friend class CondVar; /*! * \enum Attribute * * The Attribute enum defines a specific attribute for the Mutex * class. An attribute may be either Normal or Recursive. A normal * mutex may only be locked once by a thread. Attempts to lock it * more than once will result in a deadlock. A recursive mutex may * be locked multiple times by the same thread. */ enum Attribute { Normal, Recursive }; /*! * \enum ErrorType * * Error types associated with a mutex. */ enum ErrorType { NoError, //!< No error on the mutex AlreadyLocked, //!< The mutex is already locked. TryAgain, //!< Try locking again. Invalid, //!< Invalid mutex. Fault, //!< A fault occurred. DeadLock, //!< The operation would result in deadlock UnknownError //!< An unknown error occurred }; //! \name Constructor/Destructor //@{ Mutex(MICO_Boolean locked = FALSE, Attribute attr = Normal); ~Mutex(); //@} //! \name Mutex Operations //@{ /*! * The trylock method is used to sample the mutex to see if it * is locked. If the mutex is not locked the thread will obtain * the lock, returning true. Otherwise, an error condition will * be returned, and the thread will not own the lock. * * \return True if the thread obtains the lock. */ MICO_ULong trylock() { ErrorType ret = NoError; #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME (<< name ()) << ": Mutex::trylock ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG switch (pthread_mutex_trylock(&_mutex)) { case 0: ret = MICOMT::Mutex::NoError; break; case EBUSY: ret = MICOMT::Mutex::AlreadyLocked; break; case EAGAIN: ret = MICOMT::Mutex::TryAgain; break; case EINVAL: ret = MICOMT::Mutex::Invalid; break; case EFAULT: ret = MICOMT::Mutex::Fault; break; case EDEADLK: ret = MICOMT::Mutex::DeadLock; break; default: ret = MICOMT::Mutex::UnknownError; } return ret; } /*! * The lock method is used to lock the mutex. A thread locking * the mutex will have ownership of the lock until it is unlocked. * Any thread trying to subsequently lock the mutex will block * until the owning thread has unlocked it. */ void lock() { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME(<< name ()) << ": Mutex::lock ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG #ifdef SOLARIS_MUTEX if (_rec) { if (_id == pthread_self()) { // we already have the look _cnt++; return; } pthread_mutex_lock(&_mutex); assert(_cnt == 0); // paranoia _id = pthread_self(); _cnt = 1; } else { pthread_mutex_lock(&_mutex); } #else // SOLARIS_MUTEX pthread_mutex_lock(&_mutex); #endif // SOLARIS_MUTEX } /*! * The unlock method is used to release the ownership of a mutex. * Threads waiting on the locked mutex are re-woken and may attempt * to lock the mutex themselves. */ void unlock() { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME(<< name ()) << ": Mutex::unlock ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG #ifdef SOLARIS_MUTEX if (_rec) { _cnt--; if (!_cnt) { _id = 0; // hopefully 0 is not a valid thread id pthread_mutex_unlock(&_mutex); } } else { pthread_mutex_unlock(&_mutex); } #else // SOLARIS_MUTEX pthread_mutex_unlock(&_mutex); #endif // SOLARIS_MUTEX } //@} protected: pthread_mutex_t _mutex; //!< Pthread mutex object #ifdef SOLARIS_MUTEX pthread_t _id; //!< Id of thread which hold ownership unsigned int _cnt; //!< Counter of recursive mutex unsigned int _rec; //!< Is mutex recursive? #endif // SOLARIS_MUTEX public: #ifdef USE_SHARED_MUTEX_ATTRIBUTE static pthread_mutexattr_t S_normal_mutex_attr_; static bool S_normal_mutex_attr_initialized_; static pthread_mutexattr_t S_recursive_mutex_attr_; static bool S_recursive_mutex_attr_initialized_; #endif // USE_SHARED_MUTEX_ATTRIBUTE }; // // Conditional Variable // /*! * \ingroup micomt * * A condition variable is a synchronization object that associates * a condition with a mutex. */ class CondVar #ifdef DEBUG_NAMES : public NamedObject #endif // DEBUG_NAMES { public: //! \name Constructor/Destructor //@{ CondVar(MICOMT::Mutex* mut); ~CondVar(); //@} //! \name Condition Variable Operations //@{ /*! * The wait method blocks the calling thread until it is woken * up by another thread signalling or broadcasting to the condition * variable. * * \return True on success, false on failure */ MICO_Boolean wait() { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME(<< name ()) << ": CondVar::wait ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG return (pthread_cond_wait(&_cond,&_mutex->_mutex) == 0); }; /*! * The timedwait method blocks the calling thread until it * is woken up by another thread signalling or broadcasting to * the condition variable. It can also be woken up after the * elapsed time specified by tmout. * * \param tmout Milliseconds to wait for a signal. * \return True on success, false on failure. */ MICO_Boolean timedwait(MICO_ULong tmout); /*! * The broadcast method wakes up all threads waiting on the * condition variable. */ void broadcast() { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME(<< name ()) << ": CondVar::broadcast ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG pthread_cond_broadcast(&_cond); }; /*! * The signal method wakes up exactly one thread waiting * on the condition variable. */ void signal() { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME(<< name ()) << ": CondVar::signal ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG pthread_cond_signal(&_cond); }; //@} protected: pthread_cond_t _cond; //!< The pthread condition variable MICOMT::Mutex* _mutex; //!< The mutex which will condition use }; // // reader/writer lock // /*! * \ingroup micomt * * The RWLock class implements a mutex-like object that can be locked * for either reading or writing. */ class RWLock #ifdef DEBUG_NAMES : public NamedObject #endif // DEBUG_NAMES { public: //! \name Constructor/Destructor //@{ RWLock(); ~RWLock(); //@} /*! * The rdlock method locks the reading side of the lock. */ void rdlock() { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME(<< name ()) << ": RWLock::rdlock ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG pthread_rwlock_rdlock(&_rwlock); } /*! * The wrlock method locks the writing side of the lock. */ void wrlock() { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME(<< name ()) << ": RWLock::wrlock ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG pthread_rwlock_wrlock(&_rwlock); } /*! * The unlock method releases the lock. */ void unlock() { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME(<< name ()) << ": RWLock::unlock ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG pthread_rwlock_unlock(&_rwlock); } private: pthread_rwlock_t _rwlock; //!< The pthread read/write lock }; // // Semaphore // /*! * \ingroup micomt * * The semaphore class is a synchronization object. A semaphore is a * counter for resources shared between threads. */ #if defined(HAVE_SEMAPHORE_H) && !(defined(__APPLE__) && defined(__MACH__)) // the OS provides POSIX semaphore implementation // in semaphore.h header file // kcg: in the case of MacOS X 10.2 (Darwin 6.8) we have an issue with // POSIX semaphore initialization, so we use MICO's own semaphore // implementation on this platform class Semaphore #ifdef DEBUG_NAMES : public NamedObject #endif // DEBUG_NAMES { private: sem_t _s; //!< The system semaphore public: /*! * \enum ErrorType * * Error types associated with a semaphore. */ enum ErrorType { NoError, //!< No error on semaphore NoPermission, //!< Permission denied TryAgain, //!< Try again SemInvalid, //!< Invalide semaphore Interrupted, //!< Interrupted by signal UnknownError //!< Unknow error }; //! \name Constructor/Destructor //@{ Semaphore(unsigned int val = 0); ~Semaphore(); //@} //! \name Semaphore Operations //@{ /*! * The wait method waits until the semaphore count is non-zero, * then decrement the count. */ void wait() { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME(<< name ()) << ": Semaphore::wait ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG while (sem_wait(&_s) == EINTR) { } } /*! * If the semaphore count is zero, this method will not block. * Instead it returns ErrorType::TryAgain. */ Semaphore::ErrorType trylock() { Semaphore::ErrorType ret = MICOMT::Semaphore::NoError; #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME (<< name ()) << ": Semaphore::trylock ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG if (sem_trywait(&_s) < 0) { switch (errno) { case EPERM: ret = MICOMT::Semaphore::NoPermission; break; case EAGAIN: ret = MICOMT::Semaphore::TryAgain; break; case EINVAL: ret = MICOMT::Semaphore::SemInvalid; break; case EINTR: ret = MICOMT::Semaphore::Interrupted; break; default: ret = MICOMT::Semaphore::UnknownError; } } return ret; } /*! * The post method increases the count of the semaphore. */ void post() { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME(<< name ()) << ": Semaphore::post ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG sem_post(&_s); } //@} }; #else // HAVE_SEMAPHORE_H && !(__APPLE__ && __MACH__) // the OS doesn't provide POSIX semaphore implementation // in semaphore.h header file so we will use own // implementation based on mutex and condition variable // // This code is w/o doxygen doc because Doxyfile defines // HAVE_SEMAPHORE_H and so this code is not used for generating // documentation // class Semaphore #ifdef DEBUG_NAMES : public NamedObject #endif { Mutex _mutex; CondVar _condition; unsigned int _counter; public: enum ErrorType { NoError, NoPermission, TryAgain, SemInvalid, Interrupted, UnknownError }; Semaphore(unsigned int value = 0) : _condition(&_mutex), _counter(value) {} ~Semaphore() {} void wait(); Semaphore::ErrorType trylock(); void post(); }; #endif // HAVE_SEMAPHORE_H && !(__APPLE__ && __MACH__) // // Thread // /*! * \ingroup micomt * * The thread class is a thread abstraction for system thread types. * Each thread object encapsulates a single execution context. */ class Thread #ifdef DEBUG_NAMES : public NamedObject #endif { public: /*! * \enum DetachFlag * * A thread may either be created Detached or NotDetached (attached) * to its parent thread. The DetachFlag is used to determine the * threads behavior when it exits. */ enum DetachFlag { Detached, //!< Detached threads are capable of //!< reclaiming their resources on exit. NotDetached //!< Attached threads must be re-joined with //!< the parent thread, allowing on-exit //!< synchronization. }; /*! * \enum ErrorType * * Error types associated with the Thread class. */ enum ErrorType { NoError, //!< No error on the thread ThreadFailure, //!< The thread has failed InvalidPriority, //!< The thread was given an invalid priority MutexAlreadyLocked, //!< Mutex is already locked NotStarted, //!< The thread has not started UnknownError //!< Unknown error. }; //! \name Constructor/Destructor //@{ Thread(DetachFlag detached = NotDetached); virtual ~Thread (); //@} /*! * \type ThreadId * * An opaque type for thread ids. */ typedef pthread_t ThreadID; /*! * \type ThreadKey * * An opaque type for thread key types. A thread key * is used to create and access thread specific data. */ typedef pthread_key_t ThreadKey; /* * \type ThreadNo * * An opaque type for internal thread numbering in * MICO. */ typedef MICO_ULong ThreadNo; //! \name Priority Methods //@{ /*! * This function is used to get the maximum priority for * the operating system. * * \return The maximum thread priority */ static int get_priority_max() { int policy; pthread_t id = pthread_self(); struct sched_param params; pthread_getschedparam(id, &policy, ¶ms); return sched_get_priority_max(policy); }; /*! * This function is used to get the minimum priority for * the operating system. * * \return The minimum thread priority */ static int get_priority_min() { int policy; pthread_t id = pthread_self(); struct sched_param params; pthread_getschedparam(id, &policy, ¶ms); return sched_get_priority_min(policy); }; /*! * The priority method is used to set new priority of the thread */ virtual void priority(MICO_Long new_priority); //@} //! \name Thread Specific Data Methods //@{ /*! * The create_key method creates a thread specific data key. This key can * be associated with an arbitrary data value using set_specific() * method. * * NOTE: It might be worthwhile to re-consider the designe of TSD * to just be a single class that maintains the association - sort * of a safe pointer class. It would at least be a fun programming * excercise. * * \param key The key to create * \param cleanup A cleanup method for the key */ static void create_key(ThreadKey& key, void (*__cleanup) (void *) = 0) { int result = pthread_key_create(&key, __cleanup); assert(result == 0); }; /*! * The delete_key method is used to delete a key, but not the data * associated with it. That is done on thread exit or cancellation. * * \param key The key to delete */ static void delete_key(ThreadKey& key) { int result = pthread_key_delete(key); assert(result == 0); }; /*! * The get_specific method is used to retrieve thread specific data * for a key. * * \param key The key for which to get data * \return A pointer to the thread specific data */ static void* get_specific(const ThreadKey& key) { return pthread_getspecific(key); }; /*! * The set_specific method is used to associate thread specific * data with a key. * * \param key The key to associate with data * \param value Thread specific data */ static void set_specific(const ThreadKey& key, void *value) { int result = pthread_setspecific(key, value); assert(result == 0); }; //@} //! \name Thread Identification Methods //@{ /*! * This method returns the threads system (pthread) id. * * \return The thread's system id */ ThreadID id() const { return _id; }; /*! * This method returns the threads system (pthread) id. This * method may be called without a thread object since it is * static. * * \return The thread's system id. */ static ThreadID self() { return pthread_self(); }; /*! * This method returns the thread number. * * \return The thread number (MICO specific) */ ThreadNo no() const { return _no; }; /*! * Sets the thread number to n (MICO specific). * * \param n The new thread number */ void no(const ThreadNo n) { _no = n; }; //@} //! \name Operators //@{ virtual MICO_Boolean operator==(const Thread &thread) const; //@} //! \name Thread Execution Methods //!@{ /*! * This method calls the run method. * * \param arg Thread parameters */ // create a new thread with _run a its start method // maybe we should throw a exception instead of returning an error code void _thr_startup(void *arg); /*! * This method is used to actually create the thread context and * start the thread initialization. * * \return A thread error condition */ ErrorType create_thread() { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) << "Thread::create_thread ()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG #ifdef _THR_CREATE_AND_BLOCK _ready.lock(); #endif // _THR_CREATE_AND_BLOCK if (pthread_create(&_id, NULL, ThreadWrapper, this) != 0) { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) << "Thread: pthread_create () returned: " << strerror (r) << endl; __mtdebug_unlock(); } #endif // MTDEBUG return ThreadFailure; } #ifdef PTW32_STATIC_LIB pthread_win32_thread_attach_np(); #endif if (_detached == Detached) pthread_detach (_id); return NoError; }; /*! * Yield to another process or thread. This is considerate thing * to do for threads stuck in long loops. */ void yield() { #ifdef HAVE_SCHED_H sched_yield(); #endif // HAVE_SCHED_H }; /*! * The start method will start thread a-la Java * * \return possible error while starting thread */ virtual ErrorType start(void* arg = NULL); /*! * The _run method is method which every thread executes. It's the * right place to do the work. The user can easily use Thread * class similar in Java i.e. inherit from Thread to custom class, * overload _run method and call start on class instance. * * Example: * \code * class MyThread : public virtual MICOMT::Thread * { * public: * void _run(void* arg = NULL) * { * // do the work here * } * }; * .... * MyThread* thr = new MyThread; * thr->start(); * thr->wait(); * \endcode */ virtual void _run(void* arg) = 0; static void* ThreadWrapper(void* arg); /*! * This method effectively cancels the thread from outside the * thread's context, effectively preventing cleanup. */ void terminate(); /*! * This method blocks the current thread until the thread being called * upon exits. * * \param exitval A place to store the exit value of the finished * thread */ void wait(void** exitval = NULL) { #ifdef MTDEBUG if (MICO::Logger::IsLogged (MICO::Logger::Thread)) { __mtdebug_lock(); MICO::Logger::Stream (MICO::Logger::Thread) __NAME(<< name ()) << ": Thread::wait()" << endl; __mtdebug_unlock(); } #endif // MTDEBUG // we need to call at least one pthread_join per pthread_exit // on Solaris to get rid of dead/zombie LWP if (!_joined) { // We absolutely need to avoid calling of pthread_join // several times. Hence the mutex and dual-checking of // _joined variable. Linux/glibc is especially sensitive // on that and POSIX says clearly that this leads to // "undefined behavior". _join_lock.lock(); if (_joined) { _join_lock.unlock(); return; } pthread_join(this->id(), exitval); _joined = true; _join_lock.unlock(); } }; //@} bool finished() { return _finished; } protected: void* _arg; //!< Parameters passed in on start ThreadID _id; //!< The system thread id ThreadNo _no; //!< MICO thread number DetachFlag _detached; //!< Thread attached or detached? bool _finished; //!< Is thread finished? Mutex _finished_lock; bool _joined; Mutex _join_lock; //!< protects simultaneous calls to //!pthread_join bool _terminated; //!< set to true after successfull //!pthread_cancel call Mutex _terminated_lock; #ifdef _THR_CREATE_AND_BLOCK Mutex _ready; //!< Blocks thread on create (if specified) ErrorType _start_error; //!< Used to carry starting error from ctor to start method #endif // _THR_CREATE_AND_BLOCK }; #endif // __OS_THREAD_PTHREADS_H__