[Orxonox-commit 782] r3307 - trunk/src/core
landauf at orxonox.net
landauf at orxonox.net
Sun Jul 19 02:12:50 CEST 2009
Author: landauf
Date: 2009-07-19 02:12:50 +0200 (Sun, 19 Jul 2009)
New Revision: 3307
Added:
trunk/src/core/TclThreadList.h
Modified:
trunk/src/core/CorePrereqs.h
trunk/src/core/Game.cc
trunk/src/core/IRC.cc
trunk/src/core/TclBind.cc
trunk/src/core/TclThreadManager.cc
trunk/src/core/TclThreadManager.h
Log:
Completely rewrote TclThreadManager. This fixes several bugs from the initial implementation. The main features work fine now, but some tasks are still open (for example destroying a thread or implementing a queue size limit).
I hope this doesn't cause any troubles because I used the TclThreadManager-version from netp6 as a draft for the reimplementation. I've also applied the c-style-cast-fixes from core.
I tested this with boost 1.37, I hope it also works with 1.35 (I haven't seen any remarkable changes in the log). However it won't work with boost 1.34.1 and lower, but this change already happened back in netp6, so please update your dependencies if you're still using an old version (the current release is 1.39 btw).
Modified: trunk/src/core/CorePrereqs.h
===================================================================
--- trunk/src/core/CorePrereqs.h 2009-07-18 23:13:43 UTC (rev 3306)
+++ trunk/src/core/CorePrereqs.h 2009-07-19 00:12:50 UTC (rev 3307)
@@ -149,6 +149,9 @@
template <class T>
class SubclassIdentifier;
class TclBind;
+ struct TclInterpreterBundle;
+ template <class T>
+ class TclThreadList;
class TclThreadManager;
class Template;
class Tickable;
@@ -200,7 +203,7 @@
// Boost
namespace boost
-{
+{
namespace filesystem
{
struct path_traits;
Modified: trunk/src/core/Game.cc
===================================================================
--- trunk/src/core/Game.cc 2009-07-18 23:13:43 UTC (rev 3306)
+++ trunk/src/core/Game.cc 2009-07-19 00:12:50 UTC (rev 3307)
@@ -245,12 +245,22 @@
}
// UPDATE, Core first
+ bool threwException = false;
try
{
this->core_->update(*this->gameClock_);
}
+ catch (const std::exception& ex)
+ {
+ threwException = true;
+ COUT(0) << "Exception while ticking the Core: " << ex.what() << std::endl;
+ }
catch (...)
{
+ threwException = true;
+ }
+ if (threwException)
+ {
COUT(0) << "An exception occured while ticking the Core. This should really never happen!" << std::endl;
COUT(0) << "Closing the program." << std::endl;
this->stop();
Modified: trunk/src/core/IRC.cc
===================================================================
--- trunk/src/core/IRC.cc 2009-07-18 23:13:43 UTC (rev 3306)
+++ trunk/src/core/IRC.cc 2009-07-19 00:12:50 UTC (rev 3307)
@@ -52,8 +52,7 @@
void IRC::initialize()
{
unsigned int threadID = IRC_TCL_THREADID;
- TclThreadManager::createID(threadID);
- this->interpreter_ = TclThreadManager::getInstance().getTclInterpreter(threadID);
+ this->interpreter_ = TclThreadManager::createWithId(threadID);
try
{
Modified: trunk/src/core/TclBind.cc
===================================================================
--- trunk/src/core/TclBind.cc 2009-07-18 23:13:43 UTC (rev 3306)
+++ trunk/src/core/TclBind.cc 2009-07-19 00:12:50 UTC (rev 3307)
@@ -78,11 +78,13 @@
this->interpreter_->def("orxonox::query", TclBind::tcl_query, Tcl::variadic());
this->interpreter_->def("orxonox::crossquery", TclThreadManager::tcl_crossquery, Tcl::variadic());
this->interpreter_->def("execute", TclBind::tcl_execute, Tcl::variadic());
+ this->interpreter_->def("orxonox::crossexecute", TclThreadManager::tcl_crossexecute, Tcl::variadic());
try
{
- this->interpreter_->eval("proc query args { orxonox::query $args }");
- this->interpreter_->eval("proc crossquery {id args} { orxonox::crossquery 0 $id $args }");
+ this->interpreter_->eval("proc query args { orxonox::query [join $args] }");
+ this->interpreter_->eval("proc crossquery {id args} { orxonox::crossquery 0 $id [join $args] }");
+ this->interpreter_->eval("proc crossexecute {id args} { orxonox::crossquery 0 $id [join $args] }");
this->interpreter_->eval("set id 0");
this->interpreter_->eval("rename exit tcl::exit; proc exit {} { execute exit }");
this->interpreter_->eval("redef_puts");
Added: trunk/src/core/TclThreadList.h
===================================================================
--- trunk/src/core/TclThreadList.h (rev 0)
+++ trunk/src/core/TclThreadList.h 2009-07-19 00:12:50 UTC (rev 3307)
@@ -0,0 +1,263 @@
+/*
+ * ORXONOX - the hottest 3D action shooter ever to exist
+ * > www.orxonox.net <
+ *
+ *
+ * License notice:
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Author:
+ * Fabian 'x3n' Landau
+ * Co-authors:
+ * ...
+ *
+ */
+
+#ifndef _TclThreadList_H__
+#define _TclThreadList_H__
+
+#include "CorePrereqs.h"
+
+#include <list>
+
+#include <boost/thread/condition_variable.hpp>
+#include <boost/thread/shared_mutex.hpp>
+#include <boost/thread/locks.hpp>
+
+namespace orxonox
+{
+ template <class T>
+ class TclThreadList
+ {
+ public:
+ void push_front(const T& value);
+ void push_back(const T& value);
+ template <class InputIterator> void insert(typename std::list<T>::iterator position, InputIterator begin, InputIterator end);
+
+ void wait_and_pop_front(T* value);
+ void wait_and_pop_back(T* value);
+ bool try_pop_front(T* value);
+ bool try_pop_back(T* value);
+ void clear();
+
+ size_t size() const;
+ bool empty() const;
+ bool is_in(const T& value) const;
+
+ /**
+ @brief Returns a reference to the list. Don't forget to lock the mutex (see @ref getMutex).
+ */
+ inline std::list<T>& getList()
+ { return this->list_; }
+
+ /**
+ @brief Returns a reference to the list. Don't forget to lock the mutex (see @ref getMutex).
+ */
+ inline const std::list<T>& getList() const
+ { return this->list_; }
+
+ /**
+ @brief Returns a reference to the mutex which might be useful if you want to iterate through the list (see @ref begin and @ref end).
+ */
+ inline boost::shared_mutex& getMutex() const
+ { return this->mutex_; }
+
+ private:
+ std::list<T> list_; ///< A standard list for type T
+ mutable boost::shared_mutex mutex_; ///< A mutex to grant exclusive access to the list
+ boost::condition_variable_any condition_; ///< A condition variable to wake threads waiting for the mutex to become ready
+ };
+
+ /**
+ @brief Pushes a new element to the front of the list. A unique_lock is needed.
+ */
+ template <class T>
+ void TclThreadList<T>::push_front(const T& value)
+ {
+ boost::unique_lock<boost::shared_mutex> lock(this->mutex_);
+ this->list_.push_front(value);
+ lock.unlock(); // unlock the mutex...
+ this->condition_.notify_all(); // ...then call notify_all to wake threads waiting in wait_and_pop_front/back
+ }
+
+ /**
+ @brief Pushes a new element to the back of the list. A unique_lock is needed.
+ */
+ template <class T>
+ void TclThreadList<T>::push_back(const T& value)
+ {
+ boost::unique_lock<boost::shared_mutex> lock(this->mutex_);
+ this->list_.push_back(value);
+ lock.unlock(); // unlock the mutex...
+ this->condition_.notify_all(); // ...then call notify_all to wake threads waiting in wait_and_pop_front/back
+ }
+
+ /**
+ @brief Inserts new elements into the list. A unique_lock is needed.
+ */
+ template <class T>
+ template <class InputIterator> void TclThreadList<T>::insert(typename std::list<T>::iterator position, InputIterator begin, InputIterator end)
+ {
+ boost::unique_lock<boost::shared_mutex> lock(this->mutex_);
+ this->list_.insert(position, begin, end);
+ lock.unlock(); // unlock the mutex...
+ this->condition_.notify_all(); // ...then call notify_all to wake threads waiting in wait_and_pop_front/back
+ }
+
+ /**
+ @brief Waits until the list contains at least one element and then pops and returns the front element.
+ @param value The front value will be stored in the variable referenced by this pointer.
+ */
+ template <class T>
+ void TclThreadList<T>::wait_and_pop_front(T* value)
+ {
+ boost::unique_lock<boost::shared_mutex> lock(this->mutex_);
+
+ while (this->list_.empty()) // check if there's an element in the list
+ this->condition_.wait(lock); // wait until the condition becomes true (a notification comes from push_front, push_back or insert
+
+ *value = this->list_.front();
+ this->list_.pop_front();
+ }
+
+ /**
+ @brief Waits until the list contains at least one element and then pops and returns the back element.
+ @param value The back value will be stored in the variable referenced by this pointer.
+ */
+ template <class T>
+ void TclThreadList<T>::wait_and_pop_back(T* value)
+ {
+ boost::unique_lock<boost::shared_mutex> lock(this->mutex_);
+
+ while (this->list_.empty()) // check if there's an element in the list
+ this->condition_.wait(lock); // wait until the condition becomes true (a notification comes from push_front, push_back or insert
+
+ *value = this->list_.back();
+ this->list_.pop_back();
+ }
+
+ /**
+ @brief Pops and returns the front element if there's at least one element in the list.
+ @param value The front value will be stored in the variable referenced by this pointer.
+ @return Returns true if there was at least one element in the list (which got poped). If the list was empty, false is returned immediately.
+
+ Needs a unique_lock if there's an element to pop. If not, shared_lock is sufficient.
+ */
+ template <class T>
+ bool TclThreadList<T>::try_pop_front(T* value)
+ {
+ boost::upgrade_lock<boost::shared_mutex> lock(this->mutex_); // gain shared lock
+
+ if (this->list_.empty())
+ {
+ // No elements - return immediately
+ return false;
+ }
+ else
+ {
+ // At least one element - write it into the passed variable and pop it from the list
+ boost::upgrade_to_unique_lock<boost::shared_mutex> unique_lock(lock); // upgrade to unique lock to modify the list
+ *value = this->list_.front();
+ this->list_.pop_front();
+ }
+ return true;
+ }
+
+ /**
+ @brief Pops and returns the back element if there's at least one element in the list.
+ @param value The back value will be stored in the variable referenced by this pointer.
+ @return Returns true if there was at least one element in the list (which got poped). If the list was empty, false is returned immediately.
+
+ Needs a unique_lock if there's an element to pop. If not, shared_lock is sufficient.
+ */
+ template <class T>
+ bool TclThreadList<T>::try_pop_back(T* value)
+ {
+ boost::upgrade_lock<boost::shared_mutex> lock(this->mutex_); // gain shared lock
+
+ if (this->list_.empty())
+ {
+ // No elements - return immediately
+ return false;
+ }
+ else
+ {
+ // At least one element - write it into the passed variable and pop it from the list
+ boost::upgrade_to_unique_lock<boost::shared_mutex> unique_lock(lock); // upgrade to unique lock to modify the list
+ *value = this->list_.back();
+ this->list_.pop_back();
+ }
+ return true;
+ }
+
+ /**
+ @brief Clears the list. A unique_lock is needed.
+ */
+ template <class T>
+ void TclThreadList<T>::clear()
+ {
+ boost::unique_lock<boost::shared_mutex> lock(this->mutex_);
+ this->list_.clear();
+ }
+
+ /**
+ @brief Returns the size of the list. A shared_lock is needed.
+
+ Warning: Don't change the list based on the result of size(). Use an atomic function instead. Other threads may change the list
+ beween your call to size() and your further actions, so be careful and use this function only if you really want nothing else than
+ just the size of the list.
+ */
+ template <class T>
+ size_t TclThreadList<T>::size() const
+ {
+ boost::shared_lock<boost::shared_mutex> lock(this->mutex_);
+ return this->list_.size();
+ }
+
+ /**
+ @brief Returns true if the list is empty, false otherwise. A shared_lock is needed.
+
+ Warning: Don't change the list based on the result of empty(). Use an atomic function instead. Other threads may change the list
+ beween your call to empty() and your further actions, so be careful and use this function only if you really want nothing else than
+ just if the list is empty or not.
+ */
+ template <class T>
+ bool TclThreadList<T>::empty() const
+ {
+ boost::shared_lock<boost::shared_mutex> lock(this->mutex_);
+ return this->list_.empty();
+ }
+
+ /**
+ @brief Returns true if a given element is in the list, false otherwise. A shared_lock is needed.
+
+ Warning: The result of this function might be wrong just one instruction after the call. Use this function just to get information
+ about a temporary snapshot and don't change the list based on the result of this function.
+ */
+ template <class T>
+ bool TclThreadList<T>::is_in(const T& value) const
+ {
+ boost::shared_lock<boost::shared_mutex> lock(this->mutex_);
+
+ for (typename std::list<T>::const_iterator it = this->list_.begin(); it != this->list_.end(); ++it)
+ if (*it == value)
+ return true;
+
+ return false;
+ }
+}
+
+#endif /* _TclThreadList_H__ */
Modified: trunk/src/core/TclThreadManager.cc
===================================================================
--- trunk/src/core/TclThreadManager.cc 2009-07-18 23:13:43 UTC (rev 3306)
+++ trunk/src/core/TclThreadManager.cc 2009-07-19 00:12:50 UTC (rev 3307)
@@ -29,23 +29,22 @@
#include "TclThreadManager.h"
#include <boost/bind.hpp>
-#include <boost/thread/condition.hpp>
-#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
+#include <boost/thread/locks.hpp>
+#include <boost/thread/shared_mutex.hpp>
#include <OgreTimer.h>
#include <cpptcl/cpptcl.h>
#include "util/Convert.h"
-#include "util/Debug.h"
#include "Clock.h"
#include "CommandExecutor.h"
#include "ConsoleCommand.h"
#include "CoreIncludes.h"
#include "TclBind.h"
+#include "TclThreadList.h"
namespace orxonox
{
- const unsigned int TCLTHREADMANAGER_MAX_QUEUE_LENGTH = 100;
const float TCLTHREADMANAGER_MAX_CPU_USAGE = 0.50f;
SetConsoleCommandShortcutAlias(TclThreadManager, execute, "tclexecute").argumentCompleter(0, autocompletion::tclthreads());
@@ -54,604 +53,546 @@
SetConsoleCommand(TclThreadManager, destroy, false).argumentCompleter(0, autocompletion::tclthreads());
SetConsoleCommand(TclThreadManager, execute, false).argumentCompleter(0, autocompletion::tclthreads());
SetConsoleCommand(TclThreadManager, query, false).argumentCompleter(0, autocompletion::tclthreads());
- SetConsoleCommand(TclThreadManager, status, false);
- SetConsoleCommand(TclThreadManager, dump, false).argumentCompleter(0, autocompletion::tclthreads());
- SetConsoleCommand(TclThreadManager, flush, false).argumentCompleter(0, autocompletion::tclthreads());
+ /**
+ @brief A struct containing all informations about a Tcl-interpreter
+ */
struct TclInterpreterBundle
{
- unsigned int id_;
+ TclInterpreterBundle()
+ {
+ this->lock_ = new boost::unique_lock<boost::mutex>(this->mutex_, boost::defer_lock);
+ this->bRunning_ = true;
+ }
- std::list<std::string> queue_;
- boost::mutex queueMutex_;
+ ~TclInterpreterBundle()
+ {
+ delete this->lock_;
+ }
- Tcl::interpreter* interpreter_;
- std::string interpreterName_;
- boost::try_mutex interpreterMutex_;
-
- std::list<unsigned int> queriers_;
- boost::mutex queriersMutex_;
-
- bool running_;
- boost::mutex runningMutex_;
-
- bool finished_;
- boost::mutex finishedMutex_;
- boost::condition finishedCondition_;
+ unsigned int id_; ///< The id of the interpreter
+ Tcl::interpreter* interpreter_; ///< The Tcl-interpreter
+ boost::mutex mutex_; ///< A mutex to lock the interpreter while it's being used
+ boost::unique_lock<boost::mutex>* lock_; ///< The corresponding lock for the mutex
+ TclThreadList<std::string> queue_; ///< The command queue for commands passed by execute(command)
+ TclThreadList<unsigned int> queriers_; ///< A list containing the id's of all other threads sending a query to this interpreter (to avoid circular queries and deadlocks)
+ bool bRunning_; ///< This variable stays true until destroy() gets called
};
+ TclThreadManager* TclThreadManager::singletonPtr_s = 0;
- static boost::thread::id threadID_g;
- static boost::mutex bundlesMutex_g;
- static boost::condition fullQueueCondition_g;
- static boost::condition orxonoxEvalCondition_g;
-
- TclThreadManager* TclThreadManager::singletonRef_s = 0;
-
+ /**
+ @brief Constructor
+ @param interpreter A pointer to the standard Tcl-interpreter (see @ref TclBind)
+ */
TclThreadManager::TclThreadManager(Tcl::interpreter* interpreter)
- : orxonoxInterpreterBundle_(new TclInterpreterBundle())
{
RegisterRootObject(TclThreadManager);
- assert(singletonRef_s == 0);
- singletonRef_s = this;
+ assert(TclThreadManager::singletonPtr_s == 0);
+ TclThreadManager::singletonPtr_s = this;
- this->threadCounter_ = 0;
- this->orxonoxInterpreterBundle_->id_ = 0;
- this->orxonoxInterpreterBundle_->interpreter_ = interpreter;
- threadID_g = boost::this_thread::get_id();
- }
+ this->numInterpreterBundles_ = 0;
- TclThreadManager::~TclThreadManager()
- {
- unsigned int threadID;
- {
- boost::mutex::scoped_lock bundles_lock(bundlesMutex_g);
- if (this->interpreterBundles_.begin() == this->interpreterBundles_.end())
- return;
- else
- threadID = this->interpreterBundles_.begin()->first;
- }
- this->destroy(threadID);
+ this->interpreterBundlesMutex_ = new boost::shared_mutex();
+ this->mainInterpreterMutex_ = new boost::mutex();
+ this->messageQueue_ = new TclThreadList<std::string>();
- delete this->orxonoxInterpreterBundle_;
+ TclInterpreterBundle* newbundle = new TclInterpreterBundle();
+ newbundle->id_ = 0;
+ newbundle->interpreter_ = interpreter;
+ newbundle->lock_->lock();
- singletonRef_s = 0;
+ {
+ boost::unique_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_);
+ this->interpreterBundles_[0] = newbundle;
+ }
}
- unsigned int TclThreadManager::create()
+ /**
+ @brief Destructor
+ */
+ TclThreadManager::~TclThreadManager()
{
- boost::mutex::scoped_lock bundles_lock(bundlesMutex_g);
- TclThreadManager::getInstance().threadCounter_++;
- std::string name = multi_cast<std::string>(TclThreadManager::getInstance().threadCounter_);
+ TclThreadManager::singletonPtr_s = 0;
- TclInterpreterBundle* bundle = new TclInterpreterBundle;
- bundle->id_ = TclThreadManager::getInstance().threadCounter_;
- bundle->interpreter_ = TclThreadManager::getInstance().createNewTclInterpreter(name);
- bundle->interpreterName_ = name;
- bundle->running_ = true;
- bundle->finished_ = true;
-
- TclThreadManager::getInstance().interpreterBundles_[TclThreadManager::getInstance().threadCounter_] = bundle;
- COUT(0) << "Created new Tcl-interpreter with ID " << TclThreadManager::getInstance().threadCounter_ << std::endl;
- return TclThreadManager::getInstance().threadCounter_;
+ delete this->interpreterBundlesMutex_;
+ delete this->mainInterpreterMutex_;
+ delete this->messageQueue_;
}
- unsigned int TclThreadManager::createID(unsigned int threadID)
+ /**
+ @brief The "main loop" of the TclThreadManager. Creates new threads if needed and handles queries and queued commands for the main interpreter.
+ */
+ void TclThreadManager::update(const Clock& time)
{
- unsigned int temp = TclThreadManager::getInstance().threadCounter_;
- TclThreadManager::getInstance().threadCounter_ = threadID - 1;
- TclThreadManager::create();
- TclThreadManager::getInstance().threadCounter_ = temp;
- return threadID;
- }
-
- void TclThreadManager::destroy(unsigned int threadID)
- {
- TclInterpreterBundle* bundle = TclThreadManager::getInstance().getInterpreterBundle(threadID);
+ // Get the bundle of the main interpreter (0)
+ TclInterpreterBundle* bundle = this->getInterpreterBundle(0);
if (bundle)
{
+ // Unlock the mutex to allow other threads accessing the main interpreter
+ bundle->lock_->unlock();
+
+ // Lock the main interpreter mutex once to synchronize with threads that want to query the main interpreter
{
- boost::mutex::scoped_lock running_lock(bundle->runningMutex_);
- bundle->running_ = false;
+ boost::unique_lock<boost::mutex> lock(*this->mainInterpreterMutex_);
}
- while (true)
+
+ // Lock the mutex again to gain exclusive access to the interpreter for the rest of the mainloop
+ bundle->lock_->lock();
+
+ // Execute commands in the queues of the threaded interpreters
{
+ boost::shared_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_);
+ for (std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = this->interpreterBundles_.begin(); it != this->interpreterBundles_.end(); ++it)
{
- boost::mutex::scoped_lock finished_lock(bundle->finishedMutex_);
- if (bundle->finished_)
+ if (it->first == 0)
+ continue; // We'll handle the default interpreter later (and without threads of course)
+
+ TclInterpreterBundle* bundle = it->second;
+ if (!bundle->queue_.empty())
{
- boost::mutex::scoped_lock bundles_lock(bundlesMutex_g);
- boost::mutex::scoped_try_lock interpreter_lock(bundle->interpreterMutex_);
+ // There are commands in the queue
try
{
- while (!interpreter_lock.try_lock())
+ if (!bundle->lock_->owns_lock() && bundle->lock_->try_lock())
{
- orxonoxEvalCondition_g.notify_one();
- boost::this_thread::yield();
+ // We sucessfully obtained a lock for the interpreter
+ std::string command;
+ if (bundle->queue_.try_pop_front(&command))
+ {
+ // Start a thread to execute the command
+ boost::thread(boost::bind(&tclThread, bundle, command));
+ }
+ else
+ {
+ // Somehow the queue become empty (maybe multiple consumers) - unlock the mutex
+ bundle->lock_->unlock();
+ }
}
- } catch (...) {}
- delete bundle->interpreter_;
- delete bundle;
- TclThreadManager::getInstance().interpreterBundles_.erase(threadID);
- break;
+ }
+ catch (...)
+ {
+ // A lock error occurred - this is possible if the lock gets locked between !bundle->lock_->owns_lock() and bundle->lock_->try_lock()
+ // This isn't too bad, just continue
+ }
}
}
-
- orxonoxEvalCondition_g.notify_one();
- boost::this_thread::yield();
}
- COUT(0) << "Destroyed Tcl-interpreter with ID " << threadID << std::endl;
- }
- }
-
- void TclThreadManager::execute(unsigned int threadID, const std::string& _command)
- {
- std::string command = stripEnclosingBraces(_command);
-
- if (threadID == 0)
- TclThreadManager::getInstance().pushCommandToQueue(command);
- else
- TclThreadManager::getInstance().pushCommandToQueue(threadID, command);
- }
-
- std::string TclThreadManager::query(unsigned int threadID, const std::string& command)
- {
- return TclThreadManager::getInstance().evalQuery(TclThreadManager::getInstance().orxonoxInterpreterBundle_->id_, threadID, command);
- }
-
- void TclThreadManager::status()
- {
- COUT(0) << "Thread ID" << '\t' << "Queue size" << '\t' << "State" << std::endl;
-
- std::string output = "Orxonox";
- output += "\t\t";
- {
- boost::mutex::scoped_lock queue_lock(TclThreadManager::getInstance().orxonoxInterpreterBundle_->queueMutex_);
- output += multi_cast<std::string>(TclThreadManager::getInstance().orxonoxInterpreterBundle_->queue_.size());
- }
- output += "\t\t";
- output += "busy";
- COUT(0) << output << std::endl;
-
- boost::mutex::scoped_lock bundles_lock(bundlesMutex_g);
- for (std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = TclThreadManager::getInstance().interpreterBundles_.begin(); it != TclThreadManager::getInstance().interpreterBundles_.end(); ++it)
- {
- std::string output = multi_cast<std::string>((*it).first);
- output += "\t\t";
+ // Execute commands in the message queue
+ if (!this->messageQueue_->empty())
{
- boost::mutex::scoped_lock queue_lock((*it).second->queueMutex_);
- output += multi_cast<std::string>((*it).second->queue_.size());
+ std::string command;
+ while (true)
+ {
+ // Pop the front value from the list (break the loop if there are no elements in the list)
+ if (!this->messageQueue_->try_pop_front(&command))
+ break;
+
+ // Execute the command
+ CommandExecutor::execute(command, false);
+ }
}
- output += "\t\t";
- {
- boost::mutex::scoped_try_lock interpreter_lock((*it).second->interpreterMutex_);
- if (interpreter_lock.try_lock())
- output += "ready";
- else
- output += "busy";
- }
- COUT(0) << output << std::endl;
- }
- }
- void TclThreadManager::dump(unsigned int threadID)
- {
- TclInterpreterBundle* bundle = 0;
- if (threadID == 0)
- {
- bundle = TclThreadManager::getInstance().orxonoxInterpreterBundle_;
- COUT(0) << "Queue dump of Orxonox:" << std::endl;
- }
- else
- {
- if ((bundle = TclThreadManager::getInstance().getInterpreterBundle(threadID)))
+ // Execute commands in the queue of the main interpreter
+ if (!bundle->queue_.empty())
{
- COUT(0) << "Queue dump of Tcl-thread " << threadID << ":" << std::endl;
- }
- else
- return;
- }
+ // Calculate the time we have until we reach the maximal cpu usage
+ unsigned long maxtime = (unsigned long)(time.getDeltaTime() * 1000000 * TCLTHREADMANAGER_MAX_CPU_USAGE);
- boost::mutex::scoped_lock queue_lock(bundle->queueMutex_);
- unsigned int index = 0;
- for (std::list<std::string>::const_iterator it = bundle->queue_.begin(); it != bundle->queue_.end(); ++it)
- {
- index++;
- COUT(0) << index << ": " << (*it) << std::endl;
- }
- }
+ Ogre::Timer timer;
+ std::string command;
- void TclThreadManager::flush(unsigned int threadID)
- {
- TclInterpreterBundle* bundle = 0;
- if (threadID == 0)
- bundle = TclThreadManager::getInstance().orxonoxInterpreterBundle_;
- else
- if (!(bundle = TclThreadManager::getInstance().getInterpreterBundle(threadID)))
- return;
+ while (timer.getMicroseconds() < maxtime)
+ {
+ // Pop the front value from the list (break the loop if there are no elements in the list)
+ if (!bundle->queue_.try_pop_front(&command))
+ break;
- boost::mutex::scoped_lock queue_lock(bundle->queueMutex_);
- bundle->queue_.clear();
- if (threadID == 0)
- {
- COUT(0) << "Flushed queue of Orxonox Tcl-interpreter." << std::endl;
+ // Execute the command
+ CommandExecutor::execute(command, false);
+ }
+ }
}
- else
- {
- COUT(0) << "Flushed queue of Tcl-interpreter " << threadID << "." << std::endl;
- }
}
- void TclThreadManager::tcl_execute(Tcl::object const &args)
+ /**
+ @brief Creates a new Tcl-interpreter.
+ */
+ unsigned int TclThreadManager::create()
{
- TclThreadManager::getInstance().pushCommandToQueue(stripEnclosingBraces(args.get()));
+ TclThreadManager::getInstance().numInterpreterBundles_++;
+ TclThreadManager::createWithId(TclThreadManager::getInstance().numInterpreterBundles_);
+ COUT(0) << "Created new Tcl-interpreter with ID " << TclThreadManager::getInstance().numInterpreterBundles_ << std::endl;
+ return TclThreadManager::getInstance().numInterpreterBundles_;
}
- std::string TclThreadManager::tcl_query(int querierID, Tcl::object const &args)
- {
- return TclThreadManager::getInstance().evalQuery(static_cast<unsigned int>(querierID), stripEnclosingBraces(args.get()));
- }
+ /**
+ @brief Creates a new Tcl-interpreter with a given id.
- std::string TclThreadManager::tcl_crossquery(int querierID, int threadID, Tcl::object const &args)
+ Use with caution - if the id collides with an already existing interpreter, this call will fail.
+ This will also be a problem, if the auto-numbered interpreters (by using create()) reach an id
+ which was previously used in this function. Use high numbers to be safe.
+ */
+ Tcl::interpreter* TclThreadManager::createWithId(unsigned int id)
{
- return TclThreadManager::getInstance().evalQuery(static_cast<unsigned int>(querierID), static_cast<unsigned int>(threadID), stripEnclosingBraces(args.get()));
- }
+ TclInterpreterBundle* newbundle = new TclInterpreterBundle();
+ newbundle->id_ = id;
+ newbundle->interpreter_ = new Tcl::interpreter(TclBind::getInstance().getTclLibPath());
- bool TclThreadManager::tcl_running(int threadID)
- {
- TclInterpreterBundle* bundle = TclThreadManager::getInstance().getInterpreterBundle(static_cast<unsigned int>(threadID));
- if (bundle)
- {
- boost::mutex::scoped_lock running_lock(bundle->runningMutex_);
- return bundle->running_;
- }
- return false;
- }
-
- Tcl::interpreter* TclThreadManager::createNewTclInterpreter(const std::string& threadID)
- {
- Tcl::interpreter* i = 0;
- i = new Tcl::interpreter(TclBind::getInstance().getTclLibPath());
-
+ // Initialize the new interpreter
try
{
- i->def("orxonox::query", TclThreadManager::tcl_query, Tcl::variadic());
- i->def("orxonox::crossquery", TclThreadManager::tcl_crossquery, Tcl::variadic());
- i->def("orxonox::execute", TclThreadManager::tcl_execute, Tcl::variadic());
- i->def("orxonox::running", TclThreadManager::tcl_running);
+ std::string id_string = getConvertedValue<unsigned int, std::string>(id);
- i->def("execute", TclThreadManager::tcl_execute, Tcl::variadic());
- i->eval("proc query args { orxonox::query " + threadID + " $args }");
- i->eval("proc crossquery {id args} { orxonox::crossquery " + threadID + " $id $args }");
- i->eval("set id " + threadID);
+ // Define the functions which are implemented in C++
+ newbundle->interpreter_->def("orxonox::execute", TclThreadManager::tcl_execute, Tcl::variadic());
+ newbundle->interpreter_->def("orxonox::crossexecute", TclThreadManager::tcl_crossexecute, Tcl::variadic());
+ newbundle->interpreter_->def("orxonox::query", TclThreadManager::tcl_query, Tcl::variadic());
+ newbundle->interpreter_->def("orxonox::crossquery", TclThreadManager::tcl_crossquery, Tcl::variadic());
+ newbundle->interpreter_->def("orxonox::running", TclThreadManager::tcl_running);
- i->eval("rename exit tcl::exit");
- i->eval("proc exit {} { orxonox TclThreadManager destroy " + threadID + " }");
+ // Create threadspecific shortcuts for the functions above
+ newbundle->interpreter_->def("execute", TclThreadManager::tcl_execute, Tcl::variadic());
+ newbundle->interpreter_->def("crossexecute", TclThreadManager::tcl_crossexecute, Tcl::variadic());
+ newbundle->interpreter_->eval("proc query args { orxonox::query " + id_string + " $args }");
+ newbundle->interpreter_->eval("proc crossquery {id args} { orxonox::crossquery " + id_string + " $id $args }");
- i->eval("redef_puts");
+ // Define a variable containing the thread id
+ newbundle->interpreter_->eval("set id " + id_string);
-// i->eval("rename while tcl::while");
-// i->eval("proc while {test command} { tcl::while {[uplevel 1 expr $test]} {uplevel 1 $command} }"); // (\"$test\" && [orxonox::running " + threadID + "]])
-// i->eval("rename for tcl::for");
-// i->eval("proc for {start test next command} { uplevel tcl::for \"$start\" \"$test\" \"$next\" \"$command\" }");
- }
- catch (Tcl::tcl_error const &e)
- { COUT(1) << "Tcl error while creating Tcl-interpreter (" << threadID << "): " << e.what() << std::endl; }
- catch (std::exception const &e)
- { COUT(1) << "Error while creating Tcl-interpreter (" << threadID << "): " << e.what() << std::endl; }
+ // Use our own exit function to avoid shutting down the whole program instead of just the interpreter
+ newbundle->interpreter_->eval("rename exit tcl::exit");
+ newbundle->interpreter_->eval("proc exit {} { execute TclThreadManager destroy " + id_string + " }");
- return i;
- }
+ // Redefine some native functions
+ newbundle->interpreter_->eval("redef_puts");
- TclInterpreterBundle* TclThreadManager::getInterpreterBundle(unsigned int threadID)
- {
- boost::mutex::scoped_lock bundles_lock(bundlesMutex_g);
- std::map<unsigned int, TclInterpreterBundle*>::iterator it = this->interpreterBundles_.find(threadID);
- if (it != this->interpreterBundles_.end())
- {
- return (*it).second;
+// newbundle->interpreter_->eval("rename while tcl::while");
+// newbundle->interpreter_->eval("proc while {test command} { tcl::while {[uplevel 1 expr $test]} {uplevel 1 $command} }"); // (\"$test\" && [orxonox::running " + id + "]])
+// newbundle->interpreter_->eval("rename for tcl::for");
+// newbundle->interpreter_->eval("proc for {start test next command} { uplevel tcl::for \"$start\" \"$test\" \"$next\" \"$command\" }");
}
- else
- {
- this->error("Error: No Tcl-interpreter with ID " + multi_cast<std::string>(threadID) + " existing.");
- return 0;
- }
- }
+ catch (const Tcl::tcl_error& e)
+ { newbundle->interpreter_ = 0; COUT(1) << "Tcl error while creating Tcl-interpreter (" << id << "): " << e.what() << std::endl; }
+ catch (const std::exception& e)
+ { newbundle->interpreter_ = 0; COUT(1) << "Error while creating Tcl-interpreter (" << id << "): " << e.what() << std::endl; }
+ catch (...)
+ { newbundle->interpreter_ = 0; COUT(1) << "An error occurred while creating a new Tcl-interpreter (" << id << ")" << std::endl; }
- Tcl::interpreter* TclThreadManager::getTclInterpreter(unsigned int threadID)
- {
- return this->getInterpreterBundle(threadID)->interpreter_;
- }
-
- std::string TclThreadManager::dumpList(const std::list<unsigned int>& list)
- {
- std::string output = "";
- for (std::list<unsigned int>::const_iterator it = list.begin(); it != list.end(); ++it)
{
- if (it != list.begin())
- output += " ";
-
- output += multi_cast<std::string>(*it);
+ // Add the new bundle to the map
+ boost::unique_lock<boost::shared_mutex> lock(*TclThreadManager::getInstance().interpreterBundlesMutex_);
+ TclThreadManager::getInstance().interpreterBundles_[id] = newbundle;
}
- return output;
- }
- void TclThreadManager::error(const std::string& error)
- {
- if (boost::this_thread::get_id() != threadID_g)
- {
- boost::mutex::scoped_lock queue_lock(this->orxonoxInterpreterBundle_->queueMutex_);
- if (this->orxonoxInterpreterBundle_->queue_.size() >= TCLTHREADMANAGER_MAX_QUEUE_LENGTH)
- {
- boost::this_thread::yield();
- return;
- }
- }
-
- this->forceCommandToFrontOfQueue("error " + error);
+ return newbundle->interpreter_;
}
- void TclThreadManager::debug(const std::string& error)
+ /**
+ @brief Stops and destroys a given Tcl-interpreter
+ */
+ void TclThreadManager::destroy(unsigned int id)
{
- if (boost::this_thread::get_id() != threadID_g)
- {
- boost::mutex::scoped_lock queue_lock(this->orxonoxInterpreterBundle_->queueMutex_);
- if (this->orxonoxInterpreterBundle_->queue_.size() >= TCLTHREADMANAGER_MAX_QUEUE_LENGTH)
- {
- boost::this_thread::yield();
- return;
- }
- }
-
- this->forceCommandToFrontOfQueue("debug " + error);
+ // TODO
+ // Not yet implemented
}
- void TclThreadManager::pushCommandToQueue(const std::string& command)
+ /**
+ @brief Sends a command to the queue of a given Tcl-interpreter
+ @param id The id of the target interpreter
+ @param command The command to be sent
+ */
+ void TclThreadManager::execute(unsigned int target_id, const std::string& command)
{
- boost::mutex::scoped_lock queue_lock(this->orxonoxInterpreterBundle_->queueMutex_);
- while (this->orxonoxInterpreterBundle_->queue_.size() >= TCLTHREADMANAGER_MAX_QUEUE_LENGTH)
- fullQueueCondition_g.wait(queue_lock);
-
- this->orxonoxInterpreterBundle_->queue_.push_back(command);
+ TclThreadManager::getInstance()._execute(target_id, command);
}
- void TclThreadManager::forceCommandToFrontOfQueue(const std::string& command)
- {
- boost::mutex::scoped_lock queue_lock(this->orxonoxInterpreterBundle_->queueMutex_);
- this->orxonoxInterpreterBundle_->queue_.push_front(command);
- }
+ /**
+ @brief This function can be called from Tcl to execute a console command.
- std::string TclThreadManager::popCommandFromQueue()
+ Commands which shall be executed are put into a queue and processed as soon as the
+ main thread feels ready to do so. The queue may also be full which results in a temporary
+ suspension of the calling thread until the queue gets ready again.
+ */
+ void TclThreadManager::tcl_execute(const Tcl::object& args)
{
- boost::mutex::scoped_lock queue_lock(this->orxonoxInterpreterBundle_->queueMutex_);
- std::string temp = this->orxonoxInterpreterBundle_->queue_.front();
- this->orxonoxInterpreterBundle_->queue_.pop_front();
- fullQueueCondition_g.notify_one();
- return temp;
+ TclThreadManager::getInstance()._execute(0, stripEnclosingBraces(args.get()));
}
- bool TclThreadManager::queueIsEmpty()
+ /**
+ @brief This function can be called from Tcl to send a command to the queue of any interpreter.
+ @param target_id The id of the target thread
+ */
+ void TclThreadManager::tcl_crossexecute(int target_id, const Tcl::object& args)
{
- boost::mutex::scoped_lock queue_lock(this->orxonoxInterpreterBundle_->queueMutex_);
- return this->orxonoxInterpreterBundle_->queue_.empty();
+ TclThreadManager::getInstance()._execute(static_cast<unsigned int>(target_id), stripEnclosingBraces(args.get()));
}
- void TclThreadManager::pushCommandToQueue(unsigned int threadID, const std::string& command)
+ /**
+ @brief Sends a command to the queue of a given Tcl-interpreter
+ @param id The id of the target interpreter
+ @param command The command to be sent
+ */
+ void TclThreadManager::_execute(unsigned int target_id, const std::string& command)
{
- TclInterpreterBundle* bundle = this->getInterpreterBundle(threadID);
+ TclInterpreterBundle* bundle = this->getInterpreterBundle(target_id);
if (bundle)
- {
- boost::mutex::scoped_lock queue_lock(bundle->queueMutex_);
- if (bundle->queue_.size() >= TCLTHREADMANAGER_MAX_QUEUE_LENGTH)
- {
- this->error("Error: Queue of Tcl-interpreter " + multi_cast<std::string>(threadID) + " is full, couldn't add command.");
- return;
- }
-
bundle->queue_.push_back(command);
- }
}
- std::string TclThreadManager::popCommandFromQueue(unsigned int threadID)
- {
- TclInterpreterBundle* bundle = this->getInterpreterBundle(threadID);
- if (bundle)
- {
- boost::mutex::scoped_lock queue_lock(bundle->queueMutex_);
- std::string temp = bundle->queue_.front();
- bundle->queue_.pop_front();
- return temp;
- }
- return "";
- }
- bool TclThreadManager::queueIsEmpty(unsigned int threadID)
+ /**
+ @brief Sends a query to a given Tcl-interpreter and waits for the result
+ @param id The id of the target interpreter
+ @param command The command to be sent
+ @return The result of the command
+ */
+ std::string TclThreadManager::query(unsigned int target_id, const std::string& command)
{
- TclInterpreterBundle* bundle = this->getInterpreterBundle(threadID);
- if (bundle)
- {
- boost::mutex::scoped_lock queue_lock(bundle->queueMutex_);
- return bundle->queue_.empty();
- }
- return true;
+ return TclThreadManager::getInstance()._query(0, target_id, command);
}
- bool TclThreadManager::updateQueriersList(TclInterpreterBundle* querier, TclInterpreterBundle* target)
- {
- if (querier == target)
- return false;
+ /**
+ @brief This function can be called from Tcl to send a query to the main thread.
+ @param source_id The id of the calling thread
- boost::mutex::scoped_lock queriers_lock(target->queriersMutex_);
-
- {
- boost::mutex::scoped_lock queriers_lock(querier->queriersMutex_);
- target->queriers_.insert(target->queriers_.end(), querier->queriers_.begin(), querier->queriers_.end());
- }
-
- target->queriers_.insert(target->queriers_.end(), querier->id_);
-
- if (std::find(target->queriers_.begin(), target->queriers_.end(), target->id_) != target->queriers_.end())
- {
- this->error("Error: Circular query (" + this->dumpList(target->queriers_) + " -> " + multi_cast<std::string>(target->id_) + "), couldn't query Tcl-interpreter with ID " + multi_cast<std::string>(target->id_) + " from other interpreter with ID " + multi_cast<std::string>(querier->id_) + ".");
- return false;
- }
-
- return true;
+ A query waits for the result of the command. This means, the calling thread will be blocked until
+ the main thread answers the query. In return, the main thread sends the result of the console
+ command back to Tcl.
+ */
+ std::string TclThreadManager::tcl_query(int source_id, const Tcl::object& args)
+ {
+ return TclThreadManager::getInstance()._query(static_cast<unsigned int>(source_id), 0, stripEnclosingBraces(args.get()), true);
}
- std::string TclThreadManager::evalQuery(unsigned int querierID, const std::string& command)
+ /**
+ @brief This function can be called from Tcl to send a query to another thread.
+ @param source_id The id of the calling thread
+ @param target_id The id of the target thread
+ */
+ std::string TclThreadManager::tcl_crossquery(int source_id, int target_id, const Tcl::object& args)
{
- TclInterpreterBundle* querier = this->getInterpreterBundle(querierID);
- std::string output = "";
- if (querier)
- {
- if (this->updateQueriersList(querier, this->orxonoxInterpreterBundle_))
- {
- boost::mutex::scoped_lock interpreter_lock(this->orxonoxInterpreterBundle_->interpreterMutex_);
- orxonoxEvalCondition_g.wait(interpreter_lock);
-
- if (!CommandExecutor::execute(command, false))
- this->error("Error: Can't execute command \"" + command + "\"!");
-
- if (CommandExecutor::getLastEvaluation().hasReturnvalue())
- output = CommandExecutor::getLastEvaluation().getReturnvalue().getString();
- }
-
- boost::mutex::scoped_lock queriers_lock(this->orxonoxInterpreterBundle_->queriersMutex_);
- this->orxonoxInterpreterBundle_->queriers_.clear();
- }
- return output;
+ return TclThreadManager::getInstance()._query(static_cast<unsigned int>(source_id), static_cast<unsigned int>(target_id), stripEnclosingBraces(args.get()));
}
- std::string TclThreadManager::evalQuery(unsigned int querierID, unsigned int threadID, const std::string& command)
+ /**
+ @brief This function performs a query to any Tcl interpreter
+ @param source_id The id of the calling thread
+ @param target_id The id of the target thread
+ @param command The command to send as a query
+ @param bUseCommandExecutor Only used if the target_id is 0 (which references the main interpreter). In this case it means if the command should be passed to the CommandExecutor (true) or to the main Tcl interpreter (false). This is true when called by tcl_query and false when called by tcl_crossquery.
+ */
+ std::string TclThreadManager::_query(unsigned int source_id, unsigned int target_id, const std::string& command, bool bUseCommandExecutor)
{
- TclInterpreterBundle* target = 0;
- if (threadID)
- target = this->getInterpreterBundle(threadID);
- else
- target = this->orxonoxInterpreterBundle_;
+ TclInterpreterBundle* source_bundle = this->getInterpreterBundle(source_id);
+ TclInterpreterBundle* target_bundle = this->getInterpreterBundle(target_id);
+ std::string output;
- std::string output = "";
- if (target)
+ if (source_bundle && target_bundle)
{
- TclInterpreterBundle* querier = 0;
- if (querierID)
- querier = this->getInterpreterBundle(querierID);
- else
- querier = this->orxonoxInterpreterBundle_;
+ // At this point we assume the mutex of source_bundle to be locked (because it's executing this query right now an waits for the return value)
+ // We can safely use it's querier list (because there's no other place in the code using the list except this query - and the interpreter can't start more than one query)
- if (querier)
+ if ((source_bundle->id_ == target_bundle->id_) || source_bundle->queriers_.is_in(target_bundle->id_))
{
- if (this->updateQueriersList(querier, target))
+ // This query would lead to a deadlock - return with an error
+ this->error("Error: Circular query (" + this->dumpList(source_bundle->queriers_.getList()) + " " + getConvertedValue<unsigned int, std::string>(source_bundle->id_) \
+ + " -> " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) \
+ + "), couldn't query Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) \
+ + " from other interpreter with ID " + getConvertedValue<unsigned int, std::string>(source_bundle->id_) + ".");
+ }
+ else
+ {
+ boost::unique_lock<boost::mutex> lock(target_bundle->mutex_, boost::try_to_lock);
+ boost::unique_lock<boost::mutex> mainlock(*this->mainInterpreterMutex_, boost::defer_lock);
+
+ if (!lock.owns_lock() && source_bundle->id_ != 0)
{
- boost::mutex::scoped_try_lock interpreter_lock(target->interpreterMutex_);
- bool successfullyLocked = false;
- try
+ // We couldn't obtain the try_lock immediately and we're not the main interpreter - wait until the lock becomes possible (note: the main interpreter won't wait and instead returns an error - see below)
+ if (target_bundle->id_ == 0)
{
- if (querierID == 0 || std::find(querier->queriers_.begin(), querier->queriers_.end(), 0U) != querier->queriers_.end())
- successfullyLocked = interpreter_lock.try_lock();
- else
- {
- while (!interpreter_lock.try_lock())
- {
- boost::this_thread::yield();
- }
+ // We're querying the main interpreter - use the main interpreter mutex to synchronize
+ mainlock.lock();
+ lock.lock();
+ }
+ else
+ {
+ // We're querying a threaded interpreter - no synchronization needed
+ lock.lock();
+ }
+ }
- successfullyLocked = true;
- }
- } catch (...) {}
+ if (lock.owns_lock())
+ {
+ // Now the mutex of target_bundle is also locked an we can update the querier list
+ target_bundle->queriers_.insert(target_bundle->queriers_.getList().begin(), source_bundle->queriers_.getList().begin(), source_bundle->queriers_.getList().end());
+ target_bundle->queriers_.push_back(source_bundle->id_);
- if (successfullyLocked)
+ // Perform the query (note: this happens in the main thread because we need the returnvalue)
+ if (target_bundle->id_ == 0 && bUseCommandExecutor)
{
- this->debug("TclThread_query: " + command);
- try
- { output = static_cast<std::string>(target->interpreter_->eval(command)); }
- catch (Tcl::tcl_error const &e)
- { this->error("Tcl error: " + static_cast<std::string>(e.what())); }
- catch (std::exception const &e)
- { this->error("Error while executing Tcl: " + static_cast<std::string>(e.what())); }
+ // It's a query to the CommandExecutor
+ this->debug("TclThread_query -> CE: " + command);
+ if (!CommandExecutor::execute(command, false))
+ this->error("Error: Can't execute command \"" + command + "\"!");
+
+ if (CommandExecutor::getLastEvaluation().hasReturnvalue())
+ output = CommandExecutor::getLastEvaluation().getReturnvalue().getString();
}
else
{
- this->error("Error: Couldn't query Tcl-interpreter with ID " + multi_cast<std::string>(threadID) + ", interpreter is busy right now.");
+ // It's a query to a Tcl interpreter
+ this->debug("TclThread_query: " + command);
+
+ output = this->eval(target_bundle, command);
}
- }
- boost::mutex::scoped_lock queriers_lock(target->queriersMutex_);
- target->queriers_.clear();
+ // Clear the queriers list of the target
+ target_bundle->queriers_.clear();
+
+ // Unlock the mutex of the target_bundle
+ lock.unlock();
+
+ // Finally unlock the main interpreter lock if necessary
+ if (mainlock.owns_lock())
+ mainlock.unlock();
+ }
+ else
+ {
+ // This happens if the main thread tries to query a busy interpreter
+ // To avoid a lock of the main thread, we simply don't proceed with the query in this case
+ this->error("Error: Couldn't query Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) + ", interpreter is busy right now.");
+ }
}
+
}
+
return output;
}
- void TclThreadManager::update(const Clock& time)
+ /**
+ @brief This function can be called from Tcl to ask if the thread is still suposed to be running.
+ @param id The id of the thread in question
+ */
+ bool TclThreadManager::tcl_running(int id)
{
+ TclInterpreterBundle* bundle = TclThreadManager::getInstance().getInterpreterBundle(static_cast<unsigned int>(id));
+ if (bundle)
+ return bundle->bRunning_;
+ else
+ return false;
+ }
+
+ /**
+ @brief Returns the interpreter bundle with the given id.
+ @param id The id of the interpreter
+ @return The interpreter or 0 if the id doesn't exist
+ */
+ TclInterpreterBundle* TclThreadManager::getInterpreterBundle(unsigned int id)
+ {
+ boost::shared_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_);
+
+ std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = this->interpreterBundles_.find(id);
+ if (it != this->interpreterBundles_.end())
{
- orxonoxEvalCondition_g.notify_one();
- boost::this_thread::yield();
+ return it->second;
}
-
+ else
{
- boost::mutex::scoped_lock bundles_lock(bundlesMutex_g);
- for (std::map<unsigned int, TclInterpreterBundle*>::iterator it = this->interpreterBundles_.begin(); it != this->interpreterBundles_.end(); ++it)
- {
- boost::mutex::scoped_lock queue_lock((*it).second->queueMutex_);
- if (!(*it).second->queue_.empty())
- {
- std::string command = (*it).second->queue_.front();
- (*it).second->queue_.pop_front();
- {
- boost::mutex::scoped_lock finished_lock((*it).second->finishedMutex_);
- (*it).second->finished_ = false;
- }
- boost::thread(boost::bind(&tclThread, (*it).second, command));
- }
- }
+ this->error("Error: No Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(id) + " existing.");
+ return 0;
}
+ }
+ /**
+ @brief Returns a string containing all elements of a unsigned-integer-list separated by spaces.
+ */
+ std::string TclThreadManager::dumpList(const std::list<unsigned int>& list)
+ {
+ std::string output = "";
+ for (std::list<unsigned int>::const_iterator it = list.begin(); it != list.end(); ++it)
{
- boost::mutex::scoped_lock interpreter_lock(this->orxonoxInterpreterBundle_->interpreterMutex_);
- unsigned long maxtime = static_cast<unsigned long>(time.getDeltaTime() * 1000000 * TCLTHREADMANAGER_MAX_CPU_USAGE);
- Ogre::Timer timer;
- while (!this->queueIsEmpty())
- {
- CommandExecutor::execute(this->popCommandFromQueue(), false);
- if (timer.getMicroseconds() > maxtime)
- break;
- }
+ if (it != list.begin())
+ output += " ";
+
+ output += getConvertedValue<unsigned int, std::string>(*it);
}
+ return output;
}
+ /**
+ @brief Returns a list with the numbers of all existing Tcl-interpreters.
+
+ This function is used by the auto completion function.
+ */
std::list<unsigned int> TclThreadManager::getThreadList() const
{
- boost::mutex::scoped_lock bundles_lock(bundlesMutex_g);
+ boost::shared_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_);
+
std::list<unsigned int> threads;
for (std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = this->interpreterBundles_.begin(); it != this->interpreterBundles_.end(); ++it)
- threads.push_back((*it).first);
+ if (it->first > 0 && it->first <= this->numInterpreterBundles_) // only list autonumbered interpreters (created with create()) - exclude the default interpreter 0 and all manually numbered interpreters)
+ threads.push_back(it->first);
return threads;
}
- void tclThread(TclInterpreterBundle* interpreterBundle, std::string command)
+ /**
+ @brief A helper function to print errors in a thread safe manner.
+ */
+ void TclThreadManager::error(const std::string& error)
{
- TclThreadManager::getInstance().debug("TclThread_execute: " + command);
- boost::mutex::scoped_lock interpreter_lock(interpreterBundle->interpreterMutex_);
- try
+ this->messageQueue_->push_back("error " + error);
+ }
+
+ /**
+ @brief A helper function to print debug information in a thread safe manner.
+ */
+ void TclThreadManager::debug(const std::string& error)
+ {
+ this->messageQueue_->push_back("debug " + error);
+ }
+
+ /**
+ @brief Evaluates a Tcl command without throwing exceptions (which may rise problems on certain machines).
+ @return The Tcl return value
+
+ Errors are reported through the @ref error function.
+ */
+ std::string TclThreadManager::eval(TclInterpreterBundle* bundle, const std::string& command)
+ {
+ Tcl_Interp* interpreter = bundle->interpreter_->get();
+ int cc = Tcl_Eval(interpreter, command.c_str());
+
+ Tcl::details::result result(interpreter);
+
+ if (cc != TCL_OK)
{
- interpreterBundle->interpreter_->eval(command);
+ this->error("Tcl error (execute, ID " + getConvertedValue<unsigned int, std::string>(bundle->id_) + "): " + static_cast<std::string>(result));
+ return "";
}
- catch (Tcl::tcl_error const &e)
+ else
{
- TclThreadManager::getInstance().error("Tcl (ID " + multi_cast<std::string>(interpreterBundle->id_) + ") error: " + e.what());
+ return result;
}
- catch (std::exception const &e)
- {
- TclThreadManager::getInstance().error("Error while executing Tcl (ID " + multi_cast<std::string>(interpreterBundle->id_) + "): " + e.what());
- }
+ }
- boost::mutex::scoped_lock finished_lock(interpreterBundle->finishedMutex_);
- interpreterBundle->finished_ = true;
- interpreterBundle->finishedCondition_.notify_all();
+ ////////////////
+ // The Thread //
+ ////////////////
+
+ /**
+ @brief The main function of the thread. Executes a Tcl command.
+ @param bundle The interpreter bundle containing all necessary variables
+ @param command the Command to execute
+ */
+ void tclThread(TclInterpreterBundle* bundle, std::string command)
+ {
+ TclThreadManager::getInstance().debug("TclThread_execute: " + command);
+
+ TclThreadManager::getInstance().eval(bundle, command);
+
+ bundle->lock_->unlock();
}
}
Modified: trunk/src/core/TclThreadManager.h
===================================================================
--- trunk/src/core/TclThreadManager.h 2009-07-18 23:13:43 UTC (rev 3306)
+++ trunk/src/core/TclThreadManager.h 2009-07-19 00:12:50 UTC (rev 3307)
@@ -32,35 +32,37 @@
#include "CorePrereqs.h"
#include <cassert>
-#include <list>
#include <map>
#include <string>
-#include "core/OrxonoxClass.h"
-namespace orxonox
+#include "OrxonoxClass.h"
+
+namespace boost
{
- // Internal struct
- struct TclInterpreterBundle;
+ // forward declarations
+ class mutex;
+ class shared_mutex;
+ class condition_variable;
+}
+namespace orxonox
+{
class _CoreExport TclThreadManager : public OrxonoxClass
{
- friend class IRC;
friend class TclBind;
+ friend void tclThread(TclInterpreterBundle* bundle, std::string command);
public:
TclThreadManager(Tcl::interpreter* interpreter);
- ~TclThreadManager();
+ virtual ~TclThreadManager();
- static TclThreadManager& getInstance() { assert(singletonRef_s); return *singletonRef_s; }
+ static TclThreadManager& getInstance() { assert(TclThreadManager::singletonPtr_s); return *TclThreadManager::singletonPtr_s; }
- static unsigned int create();
- static unsigned int createID(unsigned int threadID);
- static void destroy(unsigned int threadID);
- static void execute(unsigned int threadID, const std::string& command);
- static std::string query(unsigned int threadID, const std::string& command);
- static void status();
- static void dump(unsigned int threadID);
- static void flush(unsigned int threadID);
+ static unsigned int create();
+ static Tcl::interpreter* createWithId(unsigned int id);
+ static void destroy(unsigned int id);
+ static void execute(unsigned int target_id, const std::string& command);
+ static std::string query(unsigned int target_id, const std::string& command);
void error(const std::string& error);
void debug(const std::string& error);
@@ -70,40 +72,30 @@
std::list<unsigned int> getThreadList() const;
private:
- TclThreadManager(const TclThreadManager& other);
+ static void tcl_execute(const Tcl::object& args);
+ static void tcl_crossexecute(int target_id, const Tcl::object& args);
+ static std::string tcl_query(int source_id, const Tcl::object& args);
+ static std::string tcl_crossquery(int source_id, int target_id, const Tcl::object& args);
+ static bool tcl_running(int id);
- static void tcl_execute(Tcl::object const &args);
- static std::string tcl_query(int querierID, Tcl::object const &args);
- static std::string tcl_crossquery(int querierID, int threadID, Tcl::object const &args);
- static bool tcl_running(int threadID);
+ void _execute(unsigned int target_id, const std::string& command);
+ std::string _query(unsigned int source_id, unsigned int target_id, const std::string& command, bool bUseCommandExecutor = false);
- Tcl::interpreter* createNewTclInterpreter(const std::string& threadID);
- Tcl::interpreter* getTclInterpreter(unsigned int threadID);
- TclInterpreterBundle* getInterpreterBundle(unsigned int threadID);
+ TclInterpreterBundle* getInterpreterBundle(unsigned int id);
std::string dumpList(const std::list<unsigned int>& list);
- void pushCommandToQueue(const std::string& command);
- void forceCommandToFrontOfQueue(const std::string& command);
- std::string popCommandFromQueue();
- bool queueIsEmpty();
+ std::string eval(TclInterpreterBundle* bundle, const std::string& command);
- void pushCommandToQueue(unsigned int threadID, const std::string& command);
- std::string popCommandFromQueue(unsigned int threadID);
- bool queueIsEmpty(unsigned int threadID);
+ static TclThreadManager* singletonPtr_s; ///< Singleton pointer
- bool updateQueriersList(TclInterpreterBundle* querier, TclInterpreterBundle* target);
-
- std::string evalQuery(unsigned int querierID, const std::string& command);
- std::string evalQuery(unsigned int querierID, unsigned int threadID, const std::string& command);
-
- unsigned int threadCounter_;
- TclInterpreterBundle* orxonoxInterpreterBundle_;
- std::map<unsigned int, TclInterpreterBundle*> interpreterBundles_;
-
- static TclThreadManager* singletonRef_s;
+ unsigned int numInterpreterBundles_; ///< Number of created Tcl-interpreters (only used for auto-numbered interpreters, not affected by @ref createWithId)
+ std::map<unsigned int, TclInterpreterBundle*> interpreterBundles_; ///< A map containing all Tcl-interpreters
+ boost::shared_mutex* interpreterBundlesMutex_; ///< A mutex used to synchronize threads when accessing @ref interpreterBundles_
+ TclThreadList<std::string>* messageQueue_; ///< A queue to pass messages from Tcl-threads to the main thread
+ boost::mutex* mainInterpreterMutex_; ///< A mutex to synchronize queries to the main interpreter
};
- _CoreExport void tclThread(TclInterpreterBundle* interpreterBundle, std::string command);
+ _CoreExport void tclThread(TclInterpreterBundle* bundle, std::string command);
}
#endif /* _TclThreadManager_H__ */
More information about the Orxonox-commit
mailing list