Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/resource/src/core/TclThreadManager.cc @ 3350

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

small changes relating to tcl

  • Property svn:eol-style set to native
File size: 25.9 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_ = TclBind::createTclInterpreter();
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            newbundle->interpreter_->eval("proc running    {}        { return [orxonox::running " + id_string + "] }");
262
263            // Define a variable containing the thread id
264            newbundle->interpreter_->eval("set id " + id_string);
265
266            // Use our own exit function to avoid shutting down the whole program instead of just the interpreter
267            newbundle->interpreter_->eval("rename exit tcl::exit");
268            newbundle->interpreter_->eval("proc exit {} { execute TclThreadManager destroy " + id_string + " }");
269
270            // Redefine some native functions
271            newbundle->interpreter_->eval("rename while tcl::while");
272            newbundle->interpreter_->eval("rename orxonox::while while");
273            newbundle->interpreter_->eval("rename for tcl::for");
274            newbundle->interpreter_->eval("rename orxonox::for for");
275        }
276        catch (const Tcl::tcl_error& e)
277        {   newbundle->interpreter_ = 0; COUT(1) << "Tcl error while creating Tcl-interpreter (" << id << "): " << e.what() << std::endl;   }
278        catch (const std::exception& e)
279        {   newbundle->interpreter_ = 0; COUT(1) << "Error while creating Tcl-interpreter (" << id << "): " << e.what() << std::endl;   }
280        catch (...)
281        {   newbundle->interpreter_ = 0; COUT(1) << "An error occurred while creating a new Tcl-interpreter (" << id << ")" << std::endl;   }
282
283        {
284            // Add the new bundle to the map
285            boost::unique_lock<boost::shared_mutex> lock(*TclThreadManager::getInstance().interpreterBundlesMutex_);
286            TclThreadManager::getInstance().interpreterBundles_[id] = newbundle;
287        }
288
289        return newbundle->interpreter_;
290    }
291
292    /**
293        @brief Stops and destroys a given Tcl-interpreter
294    */
295    void TclThreadManager::destroy(unsigned int id)
296    {
297        // TODO
298        // Not yet implemented
299        TclInterpreterBundle* bundle = TclThreadManager::getInstance().getInterpreterBundle(id);
300        if (bundle)
301        {
302            bundle->bRunning_ = false;
303        }
304    }
305
306    /**
307        @brief Sends a command to the queue of a given Tcl-interpreter
308        @param id The id of the target interpreter
309        @param command The command to be sent
310    */
311    void TclThreadManager::execute(unsigned int target_id, const std::string& command)
312    {
313        TclThreadManager::getInstance()._execute(target_id, command);
314    }
315
316    /**
317        @brief This function can be called from Tcl to execute a console command.
318
319        Commands which shall be executed are put into a queue and processed as soon as the
320        main thread feels ready to do so. The queue may also be full which results in a temporary
321        suspension of the calling thread until the queue gets ready again.
322    */
323    void TclThreadManager::tcl_execute(const Tcl::object& args)
324    {
325        TclThreadManager::getInstance()._execute(0, stripEnclosingBraces(args.get()));
326    }
327
328    /**
329        @brief This function can be called from Tcl to send a command to the queue of any interpreter.
330        @param target_id The id of the target thread
331    */
332    void TclThreadManager::tcl_crossexecute(int target_id, const Tcl::object& args)
333    {
334        TclThreadManager::getInstance()._execute(static_cast<unsigned int>(target_id), stripEnclosingBraces(args.get()));
335    }
336
337    /**
338        @brief Sends a command to the queue of a given Tcl-interpreter
339        @param id The id of the target interpreter
340        @param command The command to be sent
341    */
342    void TclThreadManager::_execute(unsigned int target_id, const std::string& command)
343    {
344        TclInterpreterBundle* bundle = this->getInterpreterBundle(target_id);
345        if (bundle)
346            bundle->queue_.push_back(command);
347    }
348
349
350    /**
351        @brief Sends a query to a given Tcl-interpreter and waits for the result
352        @param id The id of the target interpreter
353        @param command The command to be sent
354        @return The result of the command
355    */
356    std::string TclThreadManager::query(unsigned int target_id, const std::string& command)
357    {
358        return TclThreadManager::getInstance()._query(0, target_id, command);
359    }
360
361    /**
362        @brief This function can be called from Tcl to send a query to the main thread.
363        @param source_id The id of the calling thread
364
365        A query waits for the result of the command. This means, the calling thread will be blocked until
366        the main thread answers the query. In return, the main thread sends the result of the console
367        command back to Tcl.
368    */
369    std::string TclThreadManager::tcl_query(int source_id, const Tcl::object& args)
370    {
371        return TclThreadManager::getInstance()._query(static_cast<unsigned int>(source_id), 0, stripEnclosingBraces(args.get()), true);
372    }
373
374    /**
375        @brief This function can be called from Tcl to send a query to another thread.
376        @param source_id The id of the calling thread
377        @param target_id The id of the target thread
378    */
379    std::string TclThreadManager::tcl_crossquery(int source_id, int target_id, const Tcl::object& args)
380    {
381        return TclThreadManager::getInstance()._query(static_cast<unsigned int>(source_id), static_cast<unsigned int>(target_id), stripEnclosingBraces(args.get()));
382    }
383
384    /**
385        @brief This function performs a query to any Tcl interpreter
386        @param source_id The id of the calling thread
387        @param target_id The id of the target thread
388        @param command The command to send as a query
389        @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.
390    */
391    std::string TclThreadManager::_query(unsigned int source_id, unsigned int target_id, const std::string& command, bool bUseCommandExecutor)
392    {
393        TclInterpreterBundle* source_bundle = this->getInterpreterBundle(source_id);
394        TclInterpreterBundle* target_bundle = this->getInterpreterBundle(target_id);
395        std::string output;
396
397        if (source_bundle && target_bundle)
398        {
399            // 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)
400            // 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)
401
402            if ((source_bundle->id_ == target_bundle->id_) || source_bundle->queriers_.is_in(target_bundle->id_))
403            {
404                // This query would lead to a deadlock - return with an error
405                this->error("Error: Circular query (" + this->dumpList(source_bundle->queriers_.getList()) + " " + getConvertedValue<unsigned int, std::string>(source_bundle->id_) \
406                            + " -> " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) \
407                            + "), couldn't query Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) \
408                            + " from other interpreter with ID " + getConvertedValue<unsigned int, std::string>(source_bundle->id_) + ".");
409            }
410            else
411            {
412                boost::unique_lock<boost::mutex> lock(target_bundle->mutex_, boost::try_to_lock);
413                boost::unique_lock<boost::mutex> mainlock(*this->mainInterpreterMutex_, boost::defer_lock);
414
415                if (!lock.owns_lock() && source_bundle->id_ != 0)
416                {
417                    // 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)
418                    if (target_bundle->id_ == 0)
419                    {
420                        // We're querying the main interpreter - use the main interpreter mutex to synchronize
421                        mainlock.lock();
422                        lock.lock();
423                    }
424                    else
425                    {
426                        // We're querying a threaded interpreter - no synchronization needed
427                        lock.lock();
428                    }
429                }
430
431                if (lock.owns_lock())
432                {
433                    // Now the mutex of target_bundle is also locked an we can update the querier list
434                    target_bundle->queriers_.insert(target_bundle->queriers_.getList().begin(), source_bundle->queriers_.getList().begin(), source_bundle->queriers_.getList().end());
435                    target_bundle->queriers_.push_back(source_bundle->id_);
436
437                    // Perform the query (note: this happens in the main thread because we need the returnvalue)
438                    if (target_bundle->id_ == 0 && bUseCommandExecutor)
439                    {
440                        // It's a query to the CommandExecutor
441                        this->debug("TclThread_query -> CE: " + command);
442                        if (!CommandExecutor::execute(command, false))
443                            this->error("Error: Can't execute command \"" + command + "\"!");
444
445                        if (CommandExecutor::getLastEvaluation().hasReturnvalue())
446                            output = CommandExecutor::getLastEvaluation().getReturnvalue().getString();
447                    }
448                    else
449                    {
450                        // It's a query to a Tcl interpreter
451                        this->debug("TclThread_query: " + command);
452
453                        output = this->eval(target_bundle, command);
454                    }
455
456                    // Clear the queriers list of the target
457                    target_bundle->queriers_.clear();
458
459                    // Unlock the mutex of the target_bundle
460                    lock.unlock();
461
462                    // Finally unlock the main interpreter lock if necessary
463                    if (mainlock.owns_lock())
464                        mainlock.unlock();
465                }
466                else
467                {
468                    // This happens if the main thread tries to query a busy interpreter
469                    // To avoid a lock of the main thread, we simply don't proceed with the query in this case
470                    this->error("Error: Couldn't query Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) + ", interpreter is busy right now.");
471                }
472            }
473
474        }
475
476        return output;
477    }
478
479    /**
480        @brief This function can be called from Tcl to ask if the thread is still suposed to be running.
481        @param id The id of the thread in question
482    */
483    bool TclThreadManager::tcl_running(int id)
484    {
485        TclInterpreterBundle* bundle = TclThreadManager::getInstance().getInterpreterBundle(static_cast<unsigned int>(id));
486        if (bundle)
487            return bundle->bRunning_;
488        else
489            return false;
490    }
491
492    /**
493        @brief Returns the interpreter bundle with the given id.
494        @param id The id of the interpreter
495        @return The interpreter or 0 if the id doesn't exist
496    */
497    TclInterpreterBundle* TclThreadManager::getInterpreterBundle(unsigned int id)
498    {
499        boost::shared_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_);
500
501        std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = this->interpreterBundles_.find(id);
502        if (it != this->interpreterBundles_.end())
503        {
504            return it->second;
505        }
506        else
507        {
508            this->error("Error: No Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(id) + " existing.");
509            return 0;
510        }
511    }
512
513    /**
514        @brief Returns a string containing all elements of a unsigned-integer-list separated by spaces.
515    */
516    std::string TclThreadManager::dumpList(const std::list<unsigned int>& list)
517    {
518        std::string output = "";
519        for (std::list<unsigned int>::const_iterator it = list.begin(); it != list.end(); ++it)
520        {
521            if (it != list.begin())
522                output += " ";
523
524            output += getConvertedValue<unsigned int, std::string>(*it);
525        }
526        return output;
527    }
528
529    /**
530        @brief Returns a list with the numbers of all existing Tcl-interpreters.
531
532        This function is used by the auto completion function.
533    */
534    std::list<unsigned int> TclThreadManager::getThreadList() const
535    {
536        boost::shared_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_);
537
538        std::list<unsigned int> threads;
539        for (std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = this->interpreterBundles_.begin(); it != this->interpreterBundles_.end(); ++it)
540            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)
541                threads.push_back(it->first);
542        return threads;
543    }
544
545    /**
546        @brief A helper function to print errors in a thread safe manner.
547    */
548    void TclThreadManager::error(const std::string& error)
549    {
550        this->messageQueue_->push_back("error " + error);
551    }
552
553    /**
554        @brief A helper function to print debug information in a thread safe manner.
555    */
556    void TclThreadManager::debug(const std::string& error)
557    {
558        this->messageQueue_->push_back("debug " + error);
559    }
560
561    /**
562        @brief Evaluates a Tcl command without throwing exceptions (which may rise problems on certain machines).
563        @return The Tcl return value
564
565        Errors are reported through the @ref error function.
566    */
567    std::string TclThreadManager::eval(TclInterpreterBundle* bundle, const std::string& command)
568    {
569        Tcl_Interp* interpreter = bundle->interpreter_->get();
570        int cc = Tcl_Eval(interpreter, command.c_str());
571
572        Tcl::details::result result(interpreter);
573
574        if (cc != TCL_OK)
575        {
576            this->error("Tcl error (execute, ID " + getConvertedValue<unsigned int, std::string>(bundle->id_) + "): " + static_cast<std::string>(result));
577            return "";
578        }
579        else
580        {
581            return result;
582        }
583    }
584
585    ////////////////
586    // The Thread //
587    ////////////////
588
589    /**
590        @brief The main function of the thread. Executes a Tcl command.
591        @param bundle The interpreter bundle containing all necessary variables
592        @param command the Command to execute
593    */
594    void tclThread(TclInterpreterBundle* bundle, std::string command)
595    {
596        TclThreadManager::getInstance().debug("TclThread_execute: " + command);
597
598        TclThreadManager::getInstance().eval(bundle, command);
599
600        bundle->lock_->unlock();
601    }
602}
Note: See TracBrowser for help on using the repository browser.