Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/core/TclThreadManager.cc @ 3365

Last change on this file since 3365 was 3326, checked in by landauf, 15 years ago

small fix against the thread-exit assertion failure

  • Property svn:eol-style set to native
File size: 25.8 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Fabian 'x3n' Landau
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29#include "TclThreadManager.h"
30
31#include <boost/bind.hpp>
32#include <boost/thread/thread.hpp>
33#include <boost/thread/locks.hpp>
34#include <boost/thread/shared_mutex.hpp>
35#include <OgreTimer.h>
36#include <cpptcl/cpptcl.h>
37
38#include "util/Convert.h"
39#include "Clock.h"
40#include "CommandExecutor.h"
41#include "ConsoleCommand.h"
42#include "CoreIncludes.h"
43#include "TclBind.h"
44#include "TclThreadList.h"
45
46namespace orxonox
47{
48    const float TCLTHREADMANAGER_MAX_CPU_USAGE = 0.50f;
49
50    SetConsoleCommandShortcutAlias(TclThreadManager, execute, "tclexecute").argumentCompleter(0, autocompletion::tclthreads());
51    SetConsoleCommandShortcutAlias(TclThreadManager, query,   "tclquery"  ).argumentCompleter(0, autocompletion::tclthreads());
52    SetConsoleCommand(TclThreadManager, create,  false);
53    SetConsoleCommand(TclThreadManager, destroy, false).argumentCompleter(0, autocompletion::tclthreads());
54    SetConsoleCommand(TclThreadManager, execute, false).argumentCompleter(0, autocompletion::tclthreads());
55    SetConsoleCommand(TclThreadManager, query,   false).argumentCompleter(0, autocompletion::tclthreads());
56
57    /**
58        @brief A struct containing all informations about a Tcl-interpreter
59    */
60    struct TclInterpreterBundle
61    {
62        TclInterpreterBundle()
63        {
64            this->lock_ = new boost::unique_lock<boost::mutex>(this->mutex_, boost::defer_lock);
65            this->bRunning_ = true;
66        }
67
68        ~TclInterpreterBundle()
69        {
70            delete this->lock_;
71        }
72
73        unsigned int                      id_;             ///< The id of the interpreter
74        Tcl::interpreter*                 interpreter_;    ///< The Tcl-interpreter
75        boost::mutex                      mutex_;          ///< A mutex to lock the interpreter while it's being used
76        boost::unique_lock<boost::mutex>* lock_;           ///< The corresponding lock for the mutex
77        TclThreadList<std::string>        queue_;          ///< The command queue for commands passed by execute(command)
78        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)
79        bool                              bRunning_;       ///< This variable stays true until destroy() gets called
80    };
81
82    TclThreadManager* TclThreadManager::singletonPtr_s = 0;
83
84    /**
85        @brief Constructor
86        @param interpreter A pointer to the standard Tcl-interpreter (see @ref TclBind)
87    */
88    TclThreadManager::TclThreadManager(Tcl::interpreter* interpreter)
89    {
90        RegisterRootObject(TclThreadManager);
91
92        assert(TclThreadManager::singletonPtr_s == 0);
93        TclThreadManager::singletonPtr_s = this;
94
95        this->numInterpreterBundles_ = 0;
96
97        this->interpreterBundlesMutex_ = new boost::shared_mutex();
98        this->mainInterpreterMutex_ = new boost::mutex();
99        this->messageQueue_ = new TclThreadList<std::string>();
100
101        TclInterpreterBundle* newbundle = new TclInterpreterBundle();
102        newbundle->id_ = 0;
103        newbundle->interpreter_ = interpreter;
104        newbundle->lock_->lock();
105
106        {
107            boost::unique_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_);
108            this->interpreterBundles_[0] = newbundle;
109        }
110    }
111
112    /**
113        @brief Destructor
114    */
115    TclThreadManager::~TclThreadManager()
116    {
117        TclThreadManager::singletonPtr_s = 0;
118
119        delete this->interpreterBundlesMutex_;
120//        delete this->mainInterpreterMutex_; // <-- temporary disabled to avoid crash if a thread is still actively queriyng
121        delete this->messageQueue_;
122    }
123
124    /**
125        @brief The "main loop" of the TclThreadManager. Creates new threads if needed and handles queries and queued commands for the main interpreter.
126    */
127    void TclThreadManager::update(const Clock& time)
128    {
129        // Get the bundle of the main interpreter (0)
130        TclInterpreterBundle* bundle = this->getInterpreterBundle(0);
131        if (bundle)
132        {
133            // Unlock the mutex to allow other threads accessing the main interpreter
134            bundle->lock_->unlock();
135
136            // Lock the main interpreter mutex once to synchronize with threads that want to query the main interpreter
137            {
138                boost::unique_lock<boost::mutex> lock(*this->mainInterpreterMutex_);
139            }
140
141            // Lock the mutex again to gain exclusive access to the interpreter for the rest of the mainloop
142            bundle->lock_->lock();
143
144            // Execute commands in the queues of the threaded interpreters
145            {
146                boost::shared_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_);
147                for (std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = this->interpreterBundles_.begin(); it != this->interpreterBundles_.end(); ++it)
148                {
149                    if (it->first == 0)
150                        continue; // We'll handle the default interpreter later (and without threads of course)
151
152                    TclInterpreterBundle* bundle = it->second;
153                    if (!bundle->queue_.empty())
154                    {
155                        // There are commands in the queue
156                        try
157                        {
158                            if (!bundle->lock_->owns_lock() && bundle->lock_->try_lock())
159                            {
160                                // We sucessfully obtained a lock for the interpreter
161                                std::string command;
162                                if (bundle->queue_.try_pop_front(&command))
163                                {
164                                    // Start a thread to execute the command
165                                    boost::thread(boost::bind(&tclThread, bundle, command));
166                                }
167                                else
168                                {
169                                    // Somehow the queue become empty (maybe multiple consumers) - unlock the mutex
170                                    bundle->lock_->unlock();
171                                }
172                            }
173                        }
174                        catch (...)
175                        {
176                            // A lock error occurred - this is possible if the lock gets locked between !bundle->lock_->owns_lock() and bundle->lock_->try_lock()
177                            // This isn't too bad, just continue
178                        }
179                    }
180                }
181            }
182
183            // Execute commands in the message queue
184            if (!this->messageQueue_->empty())
185            {
186                std::string command;
187                while (true)
188                {
189                    // Pop the front value from the list (break the loop if there are no elements in the list)
190                    if (!this->messageQueue_->try_pop_front(&command))
191                        break;
192
193                    // Execute the command
194                    CommandExecutor::execute(command, false);
195                }
196            }
197
198            // Execute commands in the queue of the main interpreter
199            if (!bundle->queue_.empty())
200            {
201                // Calculate the time we have until we reach the maximal cpu usage
202                unsigned long maxtime = (unsigned long)(time.getDeltaTime() * 1000000 * TCLTHREADMANAGER_MAX_CPU_USAGE);
203
204                Ogre::Timer timer;
205                std::string command;
206
207                while (timer.getMicroseconds() < maxtime)
208                {
209                    // Pop the front value from the list (break the loop if there are no elements in the list)
210                    if (!bundle->queue_.try_pop_front(&command))
211                        break;
212
213                    // Execute the command
214                    CommandExecutor::execute(command, false);
215                }
216            }
217        }
218    }
219
220    /**
221        @brief Creates a new Tcl-interpreter.
222    */
223    unsigned int TclThreadManager::create()
224    {
225        TclThreadManager::getInstance().numInterpreterBundles_++;
226        TclThreadManager::createWithId(TclThreadManager::getInstance().numInterpreterBundles_);
227        COUT(0) << "Created new Tcl-interpreter with ID " << TclThreadManager::getInstance().numInterpreterBundles_ << std::endl;
228        return TclThreadManager::getInstance().numInterpreterBundles_;
229    }
230
231    /**
232        @brief Creates a new Tcl-interpreter with a given id.
233
234        Use with caution - if the id collides with an already existing interpreter, this call will fail.
235        This will also be a problem, if the auto-numbered interpreters (by using create()) reach an id
236        which was previously used in this function. Use high numbers to be safe.
237    */
238    Tcl::interpreter* TclThreadManager::createWithId(unsigned int id)
239    {
240        TclInterpreterBundle* newbundle = new TclInterpreterBundle();
241        newbundle->id_ = id;
242        newbundle->interpreter_ = new Tcl::interpreter(TclBind::getInstance().getTclLibPath());
243
244        // Initialize the new interpreter
245        try
246        {
247            std::string id_string = getConvertedValue<unsigned int, std::string>(id);
248
249            // Define the functions which are implemented in C++
250            newbundle->interpreter_->def("orxonox::execute",      TclThreadManager::tcl_execute,      Tcl::variadic());
251            newbundle->interpreter_->def("orxonox::crossexecute", TclThreadManager::tcl_crossexecute, Tcl::variadic());
252            newbundle->interpreter_->def("orxonox::query",        TclThreadManager::tcl_query,        Tcl::variadic());
253            newbundle->interpreter_->def("orxonox::crossquery",   TclThreadManager::tcl_crossquery,   Tcl::variadic());
254            newbundle->interpreter_->def("orxonox::running",      TclThreadManager::tcl_running);
255
256            // Create threadspecific shortcuts for the functions above
257            newbundle->interpreter_->def("execute",      TclThreadManager::tcl_execute,      Tcl::variadic());
258            newbundle->interpreter_->def("crossexecute", TclThreadManager::tcl_crossexecute, Tcl::variadic());
259            newbundle->interpreter_->eval("proc query       args     { orxonox::query " + id_string + " $args }");
260            newbundle->interpreter_->eval("proc crossquery {id args} { orxonox::crossquery " + id_string + " $id $args }");
261
262            // Define a variable containing the thread id
263            newbundle->interpreter_->eval("set id " + id_string);
264
265            // Use our own exit function to avoid shutting down the whole program instead of just the interpreter
266            newbundle->interpreter_->eval("rename exit tcl::exit");
267            newbundle->interpreter_->eval("proc exit {} { execute TclThreadManager destroy " + id_string + " }");
268
269            // Redefine some native functions
270            newbundle->interpreter_->eval("redef_puts");
271
272//            newbundle->interpreter_->eval("rename while tcl::while");
273//            newbundle->interpreter_->eval("proc while {test command} { tcl::while {[uplevel 1 expr $test]} {uplevel 1 $command} }"); // (\"$test\" && [orxonox::running " + id + "]])
274//            newbundle->interpreter_->eval("rename for tcl::for");
275//            newbundle->interpreter_->eval("proc for {start test next command} { uplevel tcl::for \"$start\" \"$test\" \"$next\" \"$command\" }");
276        }
277        catch (const Tcl::tcl_error& e)
278        {   newbundle->interpreter_ = 0; COUT(1) << "Tcl error while creating Tcl-interpreter (" << id << "): " << e.what() << std::endl;   }
279        catch (const std::exception& e)
280        {   newbundle->interpreter_ = 0; COUT(1) << "Error while creating Tcl-interpreter (" << id << "): " << e.what() << std::endl;   }
281        catch (...)
282        {   newbundle->interpreter_ = 0; COUT(1) << "An error occurred while creating a new Tcl-interpreter (" << id << ")" << std::endl;   }
283
284        {
285            // Add the new bundle to the map
286            boost::unique_lock<boost::shared_mutex> lock(*TclThreadManager::getInstance().interpreterBundlesMutex_);
287            TclThreadManager::getInstance().interpreterBundles_[id] = newbundle;
288        }
289
290        return newbundle->interpreter_;
291    }
292
293    /**
294        @brief Stops and destroys a given Tcl-interpreter
295    */
296    void TclThreadManager::destroy(unsigned int id)
297    {
298        // TODO
299        // Not yet implemented
300    }
301
302    /**
303        @brief Sends a command to the queue of a given Tcl-interpreter
304        @param id The id of the target interpreter
305        @param command The command to be sent
306    */
307    void TclThreadManager::execute(unsigned int target_id, const std::string& command)
308    {
309        TclThreadManager::getInstance()._execute(target_id, command);
310    }
311
312    /**
313        @brief This function can be called from Tcl to execute a console command.
314
315        Commands which shall be executed are put into a queue and processed as soon as the
316        main thread feels ready to do so. The queue may also be full which results in a temporary
317        suspension of the calling thread until the queue gets ready again.
318    */
319    void TclThreadManager::tcl_execute(const Tcl::object& args)
320    {
321        TclThreadManager::getInstance()._execute(0, stripEnclosingBraces(args.get()));
322    }
323
324    /**
325        @brief This function can be called from Tcl to send a command to the queue of any interpreter.
326        @param target_id The id of the target thread
327    */
328    void TclThreadManager::tcl_crossexecute(int target_id, const Tcl::object& args)
329    {
330        TclThreadManager::getInstance()._execute(static_cast<unsigned int>(target_id), stripEnclosingBraces(args.get()));
331    }
332
333    /**
334        @brief Sends a command to the queue of a given Tcl-interpreter
335        @param id The id of the target interpreter
336        @param command The command to be sent
337    */
338    void TclThreadManager::_execute(unsigned int target_id, const std::string& command)
339    {
340        TclInterpreterBundle* bundle = this->getInterpreterBundle(target_id);
341        if (bundle)
342            bundle->queue_.push_back(command);
343    }
344
345
346    /**
347        @brief Sends a query to a given Tcl-interpreter and waits for the result
348        @param id The id of the target interpreter
349        @param command The command to be sent
350        @return The result of the command
351    */
352    std::string TclThreadManager::query(unsigned int target_id, const std::string& command)
353    {
354        return TclThreadManager::getInstance()._query(0, target_id, command);
355    }
356
357    /**
358        @brief This function can be called from Tcl to send a query to the main thread.
359        @param source_id The id of the calling thread
360
361        A query waits for the result of the command. This means, the calling thread will be blocked until
362        the main thread answers the query. In return, the main thread sends the result of the console
363        command back to Tcl.
364    */
365    std::string TclThreadManager::tcl_query(int source_id, const Tcl::object& args)
366    {
367        return TclThreadManager::getInstance()._query(static_cast<unsigned int>(source_id), 0, stripEnclosingBraces(args.get()), true);
368    }
369
370    /**
371        @brief This function can be called from Tcl to send a query to another thread.
372        @param source_id The id of the calling thread
373        @param target_id The id of the target thread
374    */
375    std::string TclThreadManager::tcl_crossquery(int source_id, int target_id, const Tcl::object& args)
376    {
377        return TclThreadManager::getInstance()._query(static_cast<unsigned int>(source_id), static_cast<unsigned int>(target_id), stripEnclosingBraces(args.get()));
378    }
379
380    /**
381        @brief This function performs a query to any Tcl interpreter
382        @param source_id The id of the calling thread
383        @param target_id The id of the target thread
384        @param command The command to send as a query
385        @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.
386    */
387    std::string TclThreadManager::_query(unsigned int source_id, unsigned int target_id, const std::string& command, bool bUseCommandExecutor)
388    {
389        TclInterpreterBundle* source_bundle = this->getInterpreterBundle(source_id);
390        TclInterpreterBundle* target_bundle = this->getInterpreterBundle(target_id);
391        std::string output;
392
393        if (source_bundle && target_bundle)
394        {
395            // 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)
396            // 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)
397
398            if ((source_bundle->id_ == target_bundle->id_) || source_bundle->queriers_.is_in(target_bundle->id_))
399            {
400                // This query would lead to a deadlock - return with an error
401                this->error("Error: Circular query (" + this->dumpList(source_bundle->queriers_.getList()) + " " + getConvertedValue<unsigned int, std::string>(source_bundle->id_) \
402                            + " -> " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) \
403                            + "), couldn't query Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) \
404                            + " from other interpreter with ID " + getConvertedValue<unsigned int, std::string>(source_bundle->id_) + ".");
405            }
406            else
407            {
408                boost::unique_lock<boost::mutex> lock(target_bundle->mutex_, boost::try_to_lock);
409                boost::unique_lock<boost::mutex> mainlock(*this->mainInterpreterMutex_, boost::defer_lock);
410
411                if (!lock.owns_lock() && source_bundle->id_ != 0)
412                {
413                    // 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)
414                    if (target_bundle->id_ == 0)
415                    {
416                        // We're querying the main interpreter - use the main interpreter mutex to synchronize
417                        mainlock.lock();
418                        lock.lock();
419                    }
420                    else
421                    {
422                        // We're querying a threaded interpreter - no synchronization needed
423                        lock.lock();
424                    }
425                }
426
427                if (lock.owns_lock())
428                {
429                    // Now the mutex of target_bundle is also locked an we can update the querier list
430                    target_bundle->queriers_.insert(target_bundle->queriers_.getList().begin(), source_bundle->queriers_.getList().begin(), source_bundle->queriers_.getList().end());
431                    target_bundle->queriers_.push_back(source_bundle->id_);
432
433                    // Perform the query (note: this happens in the main thread because we need the returnvalue)
434                    if (target_bundle->id_ == 0 && bUseCommandExecutor)
435                    {
436                        // It's a query to the CommandExecutor
437                        this->debug("TclThread_query -> CE: " + command);
438                        if (!CommandExecutor::execute(command, false))
439                            this->error("Error: Can't execute command \"" + command + "\"!");
440
441                        if (CommandExecutor::getLastEvaluation().hasReturnvalue())
442                            output = CommandExecutor::getLastEvaluation().getReturnvalue().getString();
443                    }
444                    else
445                    {
446                        // It's a query to a Tcl interpreter
447                        this->debug("TclThread_query: " + command);
448
449                        output = this->eval(target_bundle, command);
450                    }
451
452                    // Clear the queriers list of the target
453                    target_bundle->queriers_.clear();
454
455                    // Unlock the mutex of the target_bundle
456                    lock.unlock();
457
458                    // Finally unlock the main interpreter lock if necessary
459                    if (mainlock.owns_lock())
460                        mainlock.unlock();
461                }
462                else
463                {
464                    // This happens if the main thread tries to query a busy interpreter
465                    // To avoid a lock of the main thread, we simply don't proceed with the query in this case
466                    this->error("Error: Couldn't query Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) + ", interpreter is busy right now.");
467                }
468            }
469
470        }
471
472        return output;
473    }
474
475    /**
476        @brief This function can be called from Tcl to ask if the thread is still suposed to be running.
477        @param id The id of the thread in question
478    */
479    bool TclThreadManager::tcl_running(int id)
480    {
481        TclInterpreterBundle* bundle = TclThreadManager::getInstance().getInterpreterBundle(static_cast<unsigned int>(id));
482        if (bundle)
483            return bundle->bRunning_;
484        else
485            return false;
486    }
487
488    /**
489        @brief Returns the interpreter bundle with the given id.
490        @param id The id of the interpreter
491        @return The interpreter or 0 if the id doesn't exist
492    */
493    TclInterpreterBundle* TclThreadManager::getInterpreterBundle(unsigned int id)
494    {
495        boost::shared_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_);
496
497        std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = this->interpreterBundles_.find(id);
498        if (it != this->interpreterBundles_.end())
499        {
500            return it->second;
501        }
502        else
503        {
504            this->error("Error: No Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(id) + " existing.");
505            return 0;
506        }
507    }
508
509    /**
510        @brief Returns a string containing all elements of a unsigned-integer-list separated by spaces.
511    */
512    std::string TclThreadManager::dumpList(const std::list<unsigned int>& list)
513    {
514        std::string output = "";
515        for (std::list<unsigned int>::const_iterator it = list.begin(); it != list.end(); ++it)
516        {
517            if (it != list.begin())
518                output += " ";
519
520            output += getConvertedValue<unsigned int, std::string>(*it);
521        }
522        return output;
523    }
524
525    /**
526        @brief Returns a list with the numbers of all existing Tcl-interpreters.
527
528        This function is used by the auto completion function.
529    */
530    std::list<unsigned int> TclThreadManager::getThreadList() const
531    {
532        boost::shared_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_);
533
534        std::list<unsigned int> threads;
535        for (std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = this->interpreterBundles_.begin(); it != this->interpreterBundles_.end(); ++it)
536            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)
537                threads.push_back(it->first);
538        return threads;
539    }
540
541    /**
542        @brief A helper function to print errors in a thread safe manner.
543    */
544    void TclThreadManager::error(const std::string& error)
545    {
546        this->messageQueue_->push_back("error " + error);
547    }
548
549    /**
550        @brief A helper function to print debug information in a thread safe manner.
551    */
552    void TclThreadManager::debug(const std::string& error)
553    {
554        this->messageQueue_->push_back("debug " + error);
555    }
556
557    /**
558        @brief Evaluates a Tcl command without throwing exceptions (which may rise problems on certain machines).
559        @return The Tcl return value
560
561        Errors are reported through the @ref error function.
562    */
563    std::string TclThreadManager::eval(TclInterpreterBundle* bundle, const std::string& command)
564    {
565        Tcl_Interp* interpreter = bundle->interpreter_->get();
566        int cc = Tcl_Eval(interpreter, command.c_str());
567
568        Tcl::details::result result(interpreter);
569
570        if (cc != TCL_OK)
571        {
572            this->error("Tcl error (execute, ID " + getConvertedValue<unsigned int, std::string>(bundle->id_) + "): " + static_cast<std::string>(result));
573            return "";
574        }
575        else
576        {
577            return result;
578        }
579    }
580
581    ////////////////
582    // The Thread //
583    ////////////////
584
585    /**
586        @brief The main function of the thread. Executes a Tcl command.
587        @param bundle The interpreter bundle containing all necessary variables
588        @param command the Command to execute
589    */
590    void tclThread(TclInterpreterBundle* bundle, std::string command)
591    {
592        TclThreadManager::getInstance().debug("TclThread_execute: " + command);
593
594        TclThreadManager::getInstance().eval(bundle, command);
595
596        bundle->lock_->unlock();
597    }
598}
Note: See TracBrowser for help on using the repository browser.