Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

tcl uses it's native library, orxonox adds new features later (defined in media/tcl/init.tcl)

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