/* * Copyright (c) 2002-2006 Samit Basu * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include "Common.hpp" #include "MainApp.hpp" #include "KeyManager.hpp" #include "File.hpp" #include "Module.hpp" #include "Class.hpp" #include "LoadCore.hpp" #include "LoadFN.hpp" #include "HandleCommands.hpp" #include "Core.hpp" #include "HandleList.hpp" #include "Interpreter.hpp" #include "HandleWindow.hpp" #include "PathSearch.hpp" HandleList m_threadHandles; #ifdef Q_WS_X11 #include "FuncTerminal.hpp" #include "DumbTerminal.hpp" #include "Terminal.hpp" #include #include #include #include #include sig_t signal_suspend_default; sig_t signal_resume_default; Terminal* gterm; void signal_suspend(int a) { Terminal *tptr = dynamic_cast(gterm); if (tptr) tptr->RestoreOriginalMode(); printf("Suspending FreeMat...\n"); fflush(stdout); signal(SIGTSTP,signal_suspend_default); raise(SIGTSTP); } void signal_resume(int a) { fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK); printf("Resuming FreeMat...\n"); Terminal *tptr = dynamic_cast(gterm); if (tptr) { tptr->SetRawMode(); } } void signal_resize(int a) { Terminal *tptr = dynamic_cast(gterm); if (tptr) { tptr->ResizeEvent(); } } #endif MainApp::MainApp() { guimode = true; GUIHack = false; skipGreeting = false; m_keys = new KeyManager; m_global = new Scope("global",false); // The global scope is special m_global->mutexSetup(); } MainApp::~MainApp() { } void MainApp::HelpWin() { ArrayVector dummy; HelpWinFunction(0,dummy,m_eval); } void MainApp::SetupGUICase() { if (inBundleMode()) { QDir dir(QApplication::applicationDirPath()); dir.cdUp(); dir.cd("Plugins"); QString dummy(dir.absolutePath()); QApplication::setLibraryPaths(QStringList() << dir.absolutePath()); } m_win = new ApplicationWindow; QTTerm *gui = new QTTerm; m_keys->RegisterTerm(gui); m_win->SetGUITerminal(gui); m_win->SetKeyManager(m_keys); m_win->readSettings(); m_win->show(); gui->setFocus(); QObject::connect(m_win,SIGNAL(startHelp()),this,SLOT(HelpWin())); QObject::connect(m_win,SIGNAL(startEditor()),this,SLOT(Editor())); QObject::connect(m_win,SIGNAL(startPathTool()),this,SLOT(PathTool())); QObject::connect(qApp,SIGNAL(aboutToQuit()),m_win,SLOT(writeSettings())); QObject::connect(qApp,SIGNAL(lastWindowClosed()),qApp,SLOT(quit())); QObject::connect(this,SIGNAL(Shutdown()),m_win,SLOT(close())); QObject::connect(this,SIGNAL(Initialize()),m_win,SLOT(init())); m_term = gui; } void MainApp::SetupInteractiveTerminalCase() { #ifdef Q_WS_X11 GUIHack = true; Terminal *myterm = new Terminal; gterm = myterm; m_keys->RegisterTerm(myterm); fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK); try { myterm->Initialize(); } catch(Exception &e) { fprintf(stderr,"Unable to initialize terminal. Try to start FreeMat with the '-e' option."); exit(1); } QSocketNotifier *notify = new QSocketNotifier(STDIN_FILENO,QSocketNotifier::Read); QObject::connect(notify, SIGNAL(activated(int)), myterm, SLOT(DoRead())); myterm->ResizeEvent(); signal_suspend_default = signal(SIGTSTP,signal_suspend); signal_resume_default = signal(SIGCONT,signal_resume); signal(SIGWINCH, signal_resize); m_term = myterm; QObject::connect(this,SIGNAL(Shutdown()),qApp,SLOT(quit())); #endif } KeyManager* MainApp::GetKeyManager() { return m_keys; } void MainApp::SetupDumbTerminalCase() { #ifdef Q_WS_X11 GUIHack = true; DumbTerminal *myterm = new DumbTerminal; m_keys->RegisterTerm(myterm); fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK); QSocketNotifier *notify = new QSocketNotifier(STDIN_FILENO,QSocketNotifier::Read); QObject::connect(notify, SIGNAL(activated(int)), myterm, SLOT(DoRead())); signal_suspend_default = signal(SIGTSTP,signal_suspend); signal_resume_default = signal(SIGCONT,signal_resume); signal(SIGWINCH, signal_resize); m_term = myterm; QObject::connect(this,SIGNAL(Shutdown()),qApp,SLOT(quit())); #endif } void MainApp::PathTool() { ArrayVector dummy; PathToolFunction(0,dummy,m_eval); } extern MainApp *m_app; //! //@Module EDITOR Open Editor Window //@@Section FREEMAT //@@Usage //Brings up the editor window. The @|editor| function takes no //arguments: //@[ // editor //@] //! ArrayVector EditorFunction(int nargout, const ArrayVector& arg, Interpreter* eval) { static FMEditor *edit = NULL; if (edit == NULL) { edit = new FMEditor(eval); QObject::connect(eval,SIGNAL(RefreshBPLists()),edit,SLOT(RefreshBPLists())); QObject::connect(eval,SIGNAL(ShowActiveLine()),edit,SLOT(ShowActiveLine())); // Because of the threading setup, we need the keymanager to relay commands // from the editor to the interpreter. QObject::connect(edit,SIGNAL(EvaluateText(QString)),m_app->GetKeyManager(),SLOT(QueueMultiString(QString))); } edit->showNormal(); edit->raise(); return ArrayVector(); } //! //@Module EDIT Open Editor Window //@@Section FREEMAT //@@Usage //Brings up the editor window. The arguments of @|edit| function //are names of files for editing: //@[ // edit file1 file2 file3 //@] //! ArrayVector EditFunction(int nargout, const ArrayVector& arg, Interpreter* eval) { static FMEditor *edit= NULL; //Open the editor if (edit == NULL) { edit = new FMEditor(eval); QObject::connect(eval, SIGNAL(RefreshBPLists()), edit, SLOT(RefreshBPLists())); QObject::connect(eval, SIGNAL(ShowActiveLine()), edit, SLOT(ShowActiveLine())); // Because of the threading setup, we need the keymanager to relay commands // from the editor to the interpreter. QObject::connect(edit, SIGNAL(EvaluateText(QString)), m_app->GetKeyManager(), SLOT(QueueMultiString(QString))); } if (edit ) { //Load files listed in the command line for (int i=0; igetContext()->lookupFunction(fname,val); if (isFun && (val->type() == FM_M_FUNCTION)) { //if file is a matlab file get the file name from the interpreter MFunctionDef* mfun = (MFunctionDef*)val; if( mfun ) fname = mfun->fileName; } else { //otherwise try to find it in the path PathSearcher psearch(eval->getTotalPath()); fname = psearch.ResolvePath( fname ); } edit->loadFile(QString::fromStdString(fname)); } else { throw Exception("Illegal file name"); } } } edit->showNormal(); edit->raise(); return ArrayVector(); } void MainApp::Editor() { ArrayVector dummy; EditorFunction(0,dummy,m_eval); } void MainApp::SetGUIMode(bool mode) { guimode = mode; } void MainApp::SetSkipGreeting(bool skip) { skipGreeting = skip; } void MainApp::Crashed() { TerminalReset(); if (guimode) QMessageBox::critical(NULL,"FreeMat Crash","Interpreter thread crashed unexpectedly!\n This is likely a FreeMat bug of some kind. \nPlease file a bug report at http://freemat.sf.net.",QMessageBox::Ok,QMessageBox::NoButton,QMessageBox::NoButton); else cout << "Interpreter thread crashed unexpectedly! This is likely a FreeMat bug of some kind. Please file a bug report at http://freemat.sf.net."; qApp->quit(); } void MainApp::Quit() { TerminalReset(); m_keys->WriteHistory(); qApp->closeAllWindows(); qApp->quit(); } void MainApp::TerminalReset() { #ifdef Q_WS_X11 Terminal *tptr = dynamic_cast(gterm); if (tptr) tptr->RestoreOriginalMode(); #endif } void MainApp::UpdateTermWidth(int w) { m_eval->setTerminalWidth(w); } void MainApp::ExecuteLine(string txt) { m_eval->ExecuteLine(txt); } static bool NonGUIModeHack = false; class NonClosable : public QWidget { public: NonClosable() : QWidget(0,Qt::FramelessWindowHint) {}; void closeEvent(QCloseEvent *ce) {ce->ignore();} }; static NonClosable *wid = NULL; void MainApp::CheckNonClosable() { if (GUIHack && !wid) { wid = new NonClosable; wid->setGeometry(0,0,1,1); wid->setWindowIcon(QIcon(":/images/freemat_small_mod_64.png")); wid->setWindowTitle("FreeMat v" VERSION); wid->show(); } } void MainApp::DoGraphicsCall(Interpreter* interp, FuncPtr f, ArrayVector m, int narg) { CheckNonClosable(); try { ArrayVector n(f->evaluateFunction(interp,m,narg)); interp->RegisterGfxResults(n); } catch (Exception& e) { interp->RegisterGfxError(e.getMessageCopy()); } } //! //@Module THREADID Get Current Thread Handle //@@Section THREAD //@@Usage //The @|threadid| function in FreeMat tells you which thread //is executing the context you are in. Normally, this is thread //1, the main thread. However, if you start a new thread using //@|threadnew|, you will be operating in a new thread, and functions //that call @|threadid| from the new thread will return their //handles. //@@Example //From the main thread, we have //@< //threadid //@> //But from a launched auxilliary thread, we have //@< //t_id = threadnew //id = threadcall(t_id,1000,'threadid') //threadfree(t_id); //@> //! ArrayVector ThreadIDFunction(int nargout, const ArrayVector& arg, Interpreter* eval) { return ArrayVector() << Array::uint32Constructor(eval->getThreadID()); } //! //@Module PAUSE Pause Script Execution //@@Section IO //@@Usage //The @|pause| function can be used to pause execution of FreeMat //scripts. There are several syntaxes for its use. The first form //is //@[ // pause //@] //This form of the @|pause| function pauses FreeMat until you press //any key. The second form of the @|pause| function takes an argument //@[ // pause(p) //@] //where @|p| is the number of seconds to pause FreeMat for. The pause //argument should be accurate to a millisecond on all supported platforms. //Alternately, you can control all @|pause| statements using: //@[ // pause on //@] //which enables pauses and //@[ // pause off //@] //which disables all @|pause| statements, both with and without arguments. //! static bool pause_active = true; ArrayVector PauseFunction(int nargout, const ArrayVector& arg, Interpreter* eval) { if (arg.size() == 1) { // Check for the first argument being a string if (arg[0].isString()) { string parg(arg[0].getContentsAsStringUpper()); if (parg == "ON") pause_active = true; else if (parg == "OFF") pause_active = false; else throw Exception("Unrecognized argument to pause function - must be either 'on' or 'off'"); } if (pause_active) eval->sleepMilliseconds((unsigned long)(ArrayToDouble(arg[0])*1000)); } else { // Do something... if (pause_active) m_app->GetKeyManager()->getKeyPress(); } return ArrayVector(); } //! //@Module SLEEP Sleep For Specified Number of Seconds //@@Section FREEMAT //@@Usage //Suspends execution of FreeMat for the specified number //of seconds. The general syntax for its use is //@[ // sleep(n), //@] //where @|n| is the number of seconds to wait. //! ArrayVector SleepFunction(int nargout, const ArrayVector& arg, Interpreter* eval) { if (arg.size() != 1) throw Exception("sleep function requires 1 argument"); int sleeptime; Array a(arg[0]); sleeptime = a.getContentsAsIntegerScalar(); eval->sleepMilliseconds(1000*sleeptime); return ArrayVector(); } //! //@Module THREADNEW Create a New Thread //@@Section THREAD //@@Usage //The @|threadnew| function creates a new FreeMat thread, and //returns a handle to the resulting thread. The @|threadnew| //function takes no arguments. They general syntax for the //@|threadnew| function is //@[ // handle = threadnew //@] //Once you have a handle to a thread, you can start the thread //on a computation using the @|threadstart| function. The //threads returned by @|threadnew| are in a dormant state (i.e., //not running). Once you are finished with the thread you //must call @|threadfree| to free the resources associated with //that thread. // //Some additional important information. Thread functions operate //in their own context or workspace, which means that data cannot //be shared between threads. The exception is @|global| variables, //which provide a thread-safe way for multiple threads to share data. //Accesses to global variables are serialized so that they can //be used to share data. Threads and FreeMat are a new feature, so //there is room for improvement in the API and behavior. The best //way to improve threads is to experiment with them, and send feedback. //! ArrayVector ThreadNewFunction(int nargout, const ArrayVector& arg, Interpreter* eval) { // Create a new thread int threadID = m_app->StartNewInterpreterThread(); // Translate the path from the starter thread to the new thread Interpreter* thread = m_threadHandles.lookupHandle(threadID); thread->setPath(eval->getPath()); return ArrayVector() << Array::uint32Constructor(threadID); } //! //@Module THREADSTART Start a New Thread Computation //@@Section THREAD //@@Usage //The @|threadstart| function starts a new computation on a //FreeMat thread, and you must provide a function (no scripts //are allowed) to run inside the thread, pass any parameters that //the thread function requires, as well as the number of output //arguments expected. The general syntax for the //@|threadstart| function is //@[ // threadstart(threadid,function,nargout,arg1,arg2,...) //@] //where @|threadid| is a thread handle (returned by @|threadnew|), //where @|function| is a valid function name (it can be a built-in //imported or M-function), @|nargout| is the number of output arguments //expected from the function, and @|arg1| is the first argument that //is passed to the function. Because the function runs in its //own thread, the return values of the function are not available //imediately. Instead, execution of that function will continue //in parallel with the current thread. To retrieve the output //of the thread function, you must wait for the thread to complete //using the @|threadwait| function, and then call @|threadvalue| // to retrieve the result. You can also stop the running thread //prematurely by using the @|threadkill| function. It is important //to call @|threadfree| on the handle you get from @|threadnew| //when you are finished with the thread to ensure that the resoures //are properly freed. // //It is also perfectly reasonable to use a single thread multiple //times, calling @|threadstart| and @|threadreturn| multiple times //on a single thread. The context is preserved between threads. //When calling @|threadstart| on a pre-existing thread, FreeMat //will attempt to wait on the thread. If the wait fails, then //an error will occur. // //Some additional important information. Thread functions operate //in their own context or workspace, which means that data cannot //be shared between threads. The exception is @|global| variables, //which provide a thread-safe way for multiple threads to share data. //Accesses to global variables are serialized so that they can //be used to share data. Threads and FreeMat are a new feature, so //there is room for improvement in the API and behavior. The best //way to improve threads is to experiment with them, and send feedback. // //@@Example //Here we do something very simple. We want to obtain a listing of //all files on the system, but do not want the results to stop our //computation. So we run the @|system| call in a thread. //@< //a = threadnew; % Create the thread //threadstart(a,'system',1,'ls -lrt /'); % Start the thread //b = rand(100)\rand(100,1); % Solve some equations simultaneously //c = threadvalue(a); % Retrieve the file list //size(c) % It is large! //threadfree(a); //@> //The possibilities for threads are significant. For example, //we can solve equations in parallel, or take Fast Fourier Transforms //on multiple threads. On multi-processor machines or multicore CPUs, //these threaded calculations will execute in parallel. Neat. // //The reason for the @|nargout| argument is best illustrated with //an example. Suppose we want to compute the Singular Value //Decomposition @|svd| of a matrix @|A| in a thread. //The documentation for the @|svd| function tells us that //the behavior depends on the number of output arguments we request. //For example, if we want a full decomposition, including the left //and right singular vectors, and a diagonal singular matrix, we //need to use the three-output syntax, instead of the single output //syntax (which returns only the singular values in a column vector): //@< //A = float(rand(4)) //[u,s,v] = svd(A) % Compute the full decomposition //sigmas = svd(A) % Only want the singular values //@> // //Normally, FreeMat uses the left hand side of an assignment to calculate //the number of outputs for the function. When running a function in a //thread, we separate the assignment of the output from the invokation //of the function. Hence, we have to provide the number of arguments at the //time we invoke the function. For example, to compute a full decomposition //in a thread, we specify that we want 3 output arguments: //@< //a = threadnew; % Create the thread //threadstart(a,'svd',3,A); % Start a full decomposition //[u1,s1,v1] = threadvalue(a); % Retrieve the function values //threadfree(a); //@> //If we want to compute just the singular values, we start the thread //function with only one output argument: //@< //a = threadnew; //threadstart(a,'svd',1,A); //sigmas = threadvalue(a); //threadfree(a) //@> // //! ArrayVector ThreadStartFunction(int nargout, const ArrayVector& arg, Interpreter* eval) { if (arg.size() < 3) throw Exception("threadstart requires at least three arguments (the thread id, the function to spawn, and the number of output arguments)"); int32 handle = ArrayToInt32(arg[0]); unsigned long timeout = ULONG_MAX; Interpreter* thread = m_threadHandles.lookupHandle(handle); if (!thread) throw Exception("invalid thread handle"); string fnc = ArrayToString(arg[1]); // Lookup this function in base interpreter to see if it is defined FuncPtr val; if (!eval->lookupFunction(fnc, val)) throw Exception(string("Unable to map ") + fnc + " to a defined function "); val->updateCode(eval); // if (val->scriptFlag) // throw Exception(string("Cannot use a script as the main function in a thread.")); int tnargout = ArrayToInt32(arg[2]); if (!thread->wait(1)) throw Exception("thread was busy"); ArrayVector args(arg); args.pop_front(); args.pop_front(); args.pop_front(); thread->setThreadFunc(val,tnargout,args); thread->start(); return ArrayVector(); } //! //@Module THREADVALUE Retrieve the return values from a thread //@@Section THREAD //@@Usage //The @|threadvalue| function retrieves the values returned //by the function specified in the @|threadnew| call. The //syntax for its use is //@[ // [arg1,arg2,...,argN] = threadvalue(handle) //@] //where @|handle| is the value returned by a @|threadnew| call. //Note that there are issues with @|nargout|. See the examples //section of @|threadnew| for details on how to work around this //limitation. Because the function you have spawned with @|threadnew| //may still be executing, @|threadvalue| must first @|threadwait| //for the function to complete before retrieving the output values. //This wait may take an arbitrarily long time if the thread function //is caught in an infinite loop. Hence, you can also specify //a timeout parameter to @|threadvalue| as //@[ // [arg1,arg2,...,argN] = threadvalue(handle,timeout) //@] //where the @|timeout| is specified in milliseconds. If the //wait times out, an error is raised (that can be caught with a //@|try| and @|catch| block. // //In either case, if the thread function itself caused an error //and ceased execution abruptly, then calling @|threadvalue| will //cause that function to raise an error, allowing you to retrieve //the error that was caused and correct it. See the examples section //for more information. //@@Example //Here we do something very simple. We want to obtain a listing of //all files on the system, but do not want the results to stop our //computation. So we run the @|system| call in a thread. //@< //a = threadnew; % Create the thread //threadstart(a,'system',1,'ls -lrt /'); % Start the thread //b = rand(100)\rand(100,1); % Solve some equations simultaneously //c = threadvalue(a); % Retrieve the file list //size(c) % It is large! //threadfree(a); //@> //In this example, we force the threaded function to cause an //exception (by calling the @|error| function as the thread //function). When we call @|threadvalue|, we get an error, instead //of the return value of the function //@<1 //a = threadnew //threadstart(a,'error',0,'Hello world!'); % Will immediately stop due to error //c = threadvalue(a) % The error comes to us //threadfree(a) //@> //Note that the error has the text @|Thread:| prepended to the message //to help you identify that this was an error in a different thread. //! ArrayVector ThreadValueFunction(int nargout, const ArrayVector& arg) { if (arg.size() < 1) throw Exception("threadvalue requires at least one argument (thread id to retrieve value from)"); int32 handle = ArrayToInt32(arg[0]); Interpreter* thread = m_threadHandles.lookupHandle(handle); if (!thread) throw Exception("invalid thread handle"); unsigned long timeout = ULONG_MAX; if (arg.size() > 1) timeout = (unsigned long) ArrayToInt32(arg[1]); if (!thread->wait()) throw Exception("error waiting for thread to complete"); if (thread->getLastErrorState()) throw Exception("Thread: " + thread->getLastErrorString()); return thread->getThreadFuncReturn(); } //! //@Module THREADWAIT Wait on a thread to complete execution //@@Section THREAD //@@Usage //The @|threadwait| function waits for the given thread to complete //execution, and stops execution of the current thread (the one calling //@|threadwait|) until the given thread completes. The syntax for its //use is //@[ // success = threadwait(handle) //@] //where @|handle| is the value returned by @|threadnew| and @|success| //is a @|logical| vaariable that will be @|1| if the wait was successful //or @|0| if the wait times out. By default, the wait is indefinite. It //is better to use the following form of the function //@[ // success = threadwait(handle,timeout) //@] //where @|timeout| is the amount of time (in milliseconds) for //the @|threadwait| function to wait before a timeout occurs. //If the @|threadwait| function succeeds, then the return //value is a logical @|1|, and if it fails, the return value //is a logical @|0|. Note that you can call @|threadwait| multiple //times on a thread, and if the thread is completed, each one //will succeed. //@@Example //Here we lauch the @|sleep| function in a thread with a time delay of //10 seconds. This means that the thread function will not complete //until 10 seconds have elapsed. When we call @|threadwait| on this //thread with a short timeout, it fails, but not when the timeout //is long enough to capture the end of the function call. //@< //a = threadnew; //threadstart(a,'sleep',0,10); % start a thread that will sleep for 10 //threadwait(a,2000) % 2 second wait is not long enough //threadwait(a,10000) % 10 second wait is long enough //threadfree(a) //@> //! ArrayVector ThreadWaitFunction(int nargout, const ArrayVector& arg) { if (arg.size() < 1) throw Exception("threadwait requires at least one argument (thread id to wait on)"); int32 handle = ArrayToInt32(arg[0]); unsigned long timeout = ULONG_MAX; Interpreter* thread = m_threadHandles.lookupHandle(handle); if (!thread) throw Exception("invalid thread handle"); if (arg.size() > 1) timeout = (unsigned long) ArrayToInt32(arg[1]); bool retval = thread->wait(timeout); if (retval && thread->getLastErrorState()) throw Exception("Thread: " + thread->getLastErrorString()); return ArrayVector() << Array::logicalConstructor(retval); } //! //@Module THREADKILL Halt execution of a thread //@@Section THREAD //@@Usage //The @|threadkill| function stops (or attempts to stop) execution //of the given thread. It works only for functions defined in M-files //(i.e., not for built in or imported functions), and it works by //setting a flag that causes the thread to stop execution at the next //available statement. The syntax for this function is //@[ // threadkill(handle) //@] //where @|handle| is the value returned by a @|threadnew| call. //Note that the @|threadkill| function returns immediately. It //is still your responsibility to call @|threadfree| to free //the thread you have halted. // //You cannot kill the main thread (thread id @|1|). //@@Example //Here is an example of stopping a runaway thread using @|threadkill|. //Note that the thread function in this case is an M-file function. //We start by setting up a free running counter, where we can access //the counter from the global variables. //@{ freecount.m //function freecount // global count // if (~exist('count')) count = 0; end % Initialize the counter // while (1) // count = count + 1; % Update the counter // end //@} //We now launch this function in a thread, and use @|threadkill| to //stop it: //@< //a = threadnew; //global count % register the global variable count //count = 0; //threadstart(a,'freecount',0) % start the thread //count % it is counting //sleep(1) % Wait a bit //count % it is still counting //threadkill(a) % kill the counter //threadwait(a,1000) % wait for it to finish //count % The count will no longer increase //sleep(1) //count //threadfree(a) //@> //! ArrayVector ThreadKillFunction(int nargout, const ArrayVector& arg) { if (arg.size() < 1) throw Exception("threadkill requires at least one argument (thread id to kill)"); int32 handle = ArrayToInt32(arg[0]); if (handle == 1) throw Exception("threadkill cannot be used on the main thread"); Interpreter* thread = m_threadHandles.lookupHandle(handle); thread->setKill(); return ArrayVector(); } //! //@Module THREADFREE Free thread resources //@@Section THREAD //@@Usage //The @|threadfree| is a function to free the resources claimed //by a thread that has finished. The syntax for its use is //@[ // threadfree(handle) //@] //where @|handle| is the handle returned by the call to @|threadnew|. //The @|threadfree| function requires that the thread be completed. //Otherwise it will wait for the thread to complete, potentially //for an arbitrarily long period of time. To fix this, you can //either call @|threadfree| only on threads that are known to have //completed, or you can call it using the syntax //@[ // threadfree(handle,timeout) //@] //where @|timeout| is a time to wait in milliseconds. If the thread //fails to complete before the timeout expires, an error occurs. // //! ArrayVector ThreadFreeFunction(int nargout, const ArrayVector& arg) { if (arg.size() < 1) throw Exception("threadfree requires at least one argument (thread id to wait on) - the optional second argument is the timeout to wait for the thread to finish"); int32 handle = ArrayToInt32(arg[0]); Interpreter* thread = m_threadHandles.lookupHandle(handle); thread->setKill(); unsigned long timeout = ULONG_MAX; if (arg.size() > 1) timeout = (unsigned long) ArrayToInt32(arg[1]); if (!thread->wait(timeout)) throw Exception("Cannot free thread... it is still running"); delete thread; m_threadHandles.deleteHandle(handle); return ArrayVector(); } //! //@Module CLC Clear Dislplay //@@Section FREEMAT //@@Usage //The @|clc| function clears the current display. The syntax for //its use is //@[ // clc //@] //! ArrayVector ClcFunction(int nargout, const ArrayVector& arg) { m_app->GetKeyManager()->ClearDisplayCommand(); return ArrayVector(); } void LoadThreadFunctions(Context *context) { context->addSpecialFunction("threadid",ThreadIDFunction,0,1,NULL); context->addSpecialFunction("threadnew",ThreadNewFunction,0,1,NULL); context->addSpecialFunction("threadstart",ThreadStartFunction,-1,0,NULL); context->addFunction("threadvalue",ThreadValueFunction,2,-1,"handle","timeout",NULL); context->addFunction("threadwait",ThreadWaitFunction,2,1,"handle","timeout",NULL); context->addFunction("threadkill",ThreadKillFunction,1,0,"handle",NULL); context->addFunction("threadfree",ThreadFreeFunction,2,0,"handle","timeout",NULL); context->addGfxSpecialFunction("pause",PauseFunction,1,0,"x",NULL); context->addGfxSpecialFunction("sleep",SleepFunction,1,0,"x",NULL); context->addGfxFunction("clc",ClcFunction,0,0,NULL); context->addGfxSpecialFunction("editor",EditorFunction,0,0,NULL); context->addGfxSpecialFunction("edit",EditFunction,-1,0,NULL); } void MainApp::EnableRepaint() { GfxEnableRepaint(); } void MainApp::DisableRepaint() { GfxDisableRepaint(); } Context *MainApp::NewContext() { Context *context = new Context(m_global); LoadModuleFunctions(context); LoadClassFunction(context); LoadCoreFunctions(context); LoadFNFunctions(context); if (guimode) { LoadGUICoreFunctions(context); LoadHandleGraphicsFunctions(context); } LoadThreadFunctions(context); return context; } void MainApp::UpdatePaths() { static bool paths_set = false; if (!paths_set) { if (inBundleMode()) { QDir dir1(qApp->applicationDirPath() + "/../Resources/toolbox"); if (dir1.exists()) { QString path1(dir1.canonicalPath()); basePath += GetRecursiveDirList(path1); } QDir dir2(qApp->applicationDirPath() + "/../Resources/help/text"); if (dir2.exists()) { QString path2(dir2.canonicalPath()); basePath += GetRecursiveDirList(path2); } } else { QSettings settings("FreeMat","FreeMat"); QDir dir1(QString(settings.value("root", RESOURCEDIR).toString())+"/toolbox"); if (dir1.exists()) { QString path1(dir1.canonicalPath()); basePath += GetRecursiveDirList(path1); } QDir dir2(QString(settings.value("root", RESOURCEDIR).toString())+"/help/text"); if (dir2.exists()) { QString path2(dir2.canonicalPath()); basePath += GetRecursiveDirList(path2); } } QSettings settings("FreeMat","FreeMat"); userPath = settings.value("interpreter/path").toStringList(); paths_set = true; } } int MainApp::StartNewInterpreterThread() { Interpreter *p_eval = new Interpreter(NewContext()); p_eval->setBasePath(basePath); p_eval->setUserPath(userPath); p_eval->rescanPath(); connect(p_eval,SIGNAL(outputRawText(string)),m_term,SLOT(OutputRawString(string))); connect(p_eval,SIGNAL(SetPrompt(string)),m_keys,SLOT(SetPrompt(string))); connect(p_eval,SIGNAL(doGraphicsCall(Interpreter*,FuncPtr,ArrayVector,int)), this,SLOT(DoGraphicsCall(Interpreter*,FuncPtr,ArrayVector,int))); connect(p_eval,SIGNAL(CWDChanged()),m_keys,SIGNAL(UpdateCWD())); connect(p_eval,SIGNAL(QuitSignal()),this,SLOT(Quit())); connect(p_eval,SIGNAL(CrashedSignal()),this,SLOT(Crashed())); connect(p_eval,SIGNAL(EnableRepaint()),this,SLOT(EnableRepaint())); connect(p_eval,SIGNAL(DisableRepaint()),this,SLOT(DisableRepaint())); p_eval->setTerminalWidth(m_keys->getTerminalWidth()); p_eval->setGreetingFlag(skipGreeting); int threadID = m_threadHandles.assignHandle(p_eval); p_eval->setThreadID(threadID); #ifdef __OpenBSD__ /* 64 frames / calls deep */ p_eval->setStackSize(262144); #endif return threadID; } static int m_mainID; void MainApp::RegisterInterrupt() { // Get the main interpreter thread m_eval = m_threadHandles.lookupHandle(m_mainID); if (m_eval) m_eval->setInterrupt(); } int MainApp::Run() { UpdatePaths(); qRegisterMetaType("string"); qRegisterMetaType("FuncPtr"); qRegisterMetaType("ArrayVector"); qRegisterMetaType("Interpreter*"); connect(m_keys,SIGNAL(ExecuteLine(string)),this,SLOT(ExecuteLine(string))); connect(m_keys,SIGNAL(UpdateTermWidth(int)),this,SLOT(UpdateTermWidth(int))); connect(m_keys,SIGNAL(RegisterInterrupt()),this,SLOT(RegisterInterrupt())); // Get a new thread GfxEnableRepaint(); m_mainID = StartNewInterpreterThread(); // Assign this to the main thread m_eval = m_threadHandles.lookupHandle(m_mainID); m_keys->SetCompletionContext(m_eval->getContext()); FuncPtr doCLI; if (!m_eval->lookupFunction("docli",doCLI)) return 0; m_eval->setThreadFunc(doCLI,0,ArrayVector()); m_eval->start(); emit Initialize(); return 0; }