utils/actions.cpp
Go to the documentation of this file.
00001 /*************************************************************************** 00002 file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/trunk/src/utils/actions.cpp $ 00003 version : $LastChangedRevision: 1489 $ $LastChangedBy: jdetaeye $ 00004 date : $LastChangedDate: 2011-07-19 16:57:33 +0200 (Tue, 19 Jul 2011) $ 00005 ***************************************************************************/ 00006 00007 /*************************************************************************** 00008 * * 00009 * Copyright (C) 2007-2011 by Johan De Taeye, frePPLe bvba * 00010 * * 00011 * This library is free software; you can redistribute it and/or modify it * 00012 * under the terms of the GNU Lesser General Public License as published * 00013 * by the Free Software Foundation; either version 2.1 of the License, or * 00014 * (at your option) any later version. * 00015 * * 00016 * This library is distributed in the hope that it will be useful, * 00017 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 00018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * 00019 * General Public License for more details. * 00020 * * 00021 * You should have received a copy of the GNU Lesser General Public * 00022 * License along with this library; if not, write to the Free Software * 00023 * Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * 00024 * USA * 00025 * * 00026 ***************************************************************************/ 00027 00028 #define FREPPLE_CORE 00029 #include "frepple/utils.h" 00030 00031 00032 namespace frepple 00033 { 00034 namespace utils 00035 { 00036 00037 00038 // 00039 // COMMAND LIST 00040 // 00041 00042 00043 DECLARE_EXPORT void CommandList::add(Command* c) 00044 { 00045 // Validity check 00046 if (!c) throw LogicException("Adding NULL command to a command list"); 00047 00048 // Set the owner of the command 00049 c->owner = this; 00050 00051 // Maintenance of the linked list of child commands 00052 c->prev = lastCommand; 00053 if (lastCommand) 00054 // Let the last command in the chain point to this new extra command 00055 lastCommand->next = c; 00056 else 00057 // This is the first command in this command list 00058 firstCommand = c; 00059 lastCommand = c; 00060 } 00061 00062 00063 DECLARE_EXPORT void CommandList::rollback() 00064 { 00065 // Undo all commands and delete them. 00066 // Note that undoing an operation that hasn't been executed yet or has been 00067 // undone already is expected to be harmless, so we don't need to worry 00068 // about that... 00069 for (Command *i = lastCommand; i; ) 00070 { 00071 Command *t = i; // Temporarily store the pointer to be deleted 00072 i = i->prev; 00073 delete t; // The delete is expected to also revert the change! 00074 } 00075 00076 // Reset the list 00077 firstCommand = NULL; 00078 lastCommand = NULL; 00079 } 00080 00081 00082 DECLARE_EXPORT void CommandList::undo() 00083 { 00084 // Undo all commands and delete them. 00085 // Note that undoing an operation that hasn't been executed yet or has been 00086 // undone already is expected to be harmless, so we don't need to worry 00087 // about that... 00088 for (Command *i = lastCommand; i; i = i->prev) 00089 i->undo(); 00090 } 00091 00092 00093 DECLARE_EXPORT void CommandList::commit() 00094 { 00095 // Commit the commands 00096 for (Command *i = firstCommand; i;) 00097 { 00098 Command *t = i; // Temporarily store the pointer to be deleted 00099 i->commit(); 00100 i = i->next; 00101 delete t; 00102 } 00103 00104 // Reset the list 00105 firstCommand = NULL; 00106 lastCommand = NULL; 00107 } 00108 00109 00110 DECLARE_EXPORT void CommandList::redo() 00111 { 00112 // Redo the commands 00113 for (Command* c = firstCommand; c; c = c->next) 00114 c->redo(); 00115 } 00116 00117 00118 DECLARE_EXPORT CommandList::~CommandList() 00119 { 00120 if (firstCommand) 00121 { 00122 logger << "Warning: Deleting a command list with commands that have" 00123 << " not been committed or rolled back" << endl; 00124 rollback(); 00125 } 00126 } 00127 00128 00129 // 00130 // COMMAND MANAGER 00131 // 00132 00133 00134 DECLARE_EXPORT CommandManager::Bookmark* CommandManager::setBookmark() 00135 { 00136 Bookmark* n = new Bookmark(currentBookmark); 00137 lastBookmark->nextBookmark = n; 00138 n->prevBookmark = lastBookmark; 00139 lastBookmark = n; 00140 currentBookmark = n; 00141 return n; 00142 } 00143 00144 00145 DECLARE_EXPORT void CommandManager::undoBookmark(CommandManager::Bookmark* b) 00146 { 00147 if (!b) throw LogicException("Can't undo NULL bookmark"); 00148 00149 Bookmark* i = lastBookmark; 00150 for (; i && i != b; i = i->prevBookmark) 00151 { 00152 if (i->isChildOf(b) && i->active) 00153 { 00154 i->undo(); 00155 i->active = false; 00156 } 00157 } 00158 if (!i) throw LogicException("Can't find bookmark to undo"); 00159 currentBookmark = b->parent; 00160 } 00161 00162 00163 DECLARE_EXPORT void CommandManager::redoBookmark(CommandManager::Bookmark* b) 00164 { 00165 if (!b) throw LogicException("Can't redo NULL bookmark"); 00166 00167 for (Bookmark* i = b; i; i = i->nextBookmark) 00168 { 00169 if (i->isChildOf(b) && !i->active) 00170 { 00171 i->redo(); 00172 i->active = true; 00173 } 00174 } 00175 currentBookmark = b; 00176 } 00177 00178 00179 DECLARE_EXPORT void CommandManager::rollback(CommandManager::Bookmark* b) 00180 { 00181 if (!b) 00182 throw LogicException("Can't rollback NULL bookmark"); 00183 if (b == &firstBookmark) 00184 throw LogicException("Can't rollback default bookmark"); 00185 00186 // Remove all later child bookmarks 00187 Bookmark* i = lastBookmark; 00188 while (i && i != b) 00189 { 00190 if (i->isChildOf(b)) 00191 { 00192 // Remove from bookmark list 00193 if (i->prevBookmark) 00194 i->prevBookmark->nextBookmark = i->nextBookmark; 00195 if (i->nextBookmark) 00196 i->nextBookmark->prevBookmark = i->prevBookmark; 00197 else 00198 lastBookmark = i->prevBookmark; 00199 i->rollback(); 00200 if (currentBookmark == i) 00201 currentBookmark = b; 00202 Bookmark* tmp = i; 00203 i = i->prevBookmark; 00204 delete tmp; 00205 } 00206 else 00207 // Bookmark has a different parent 00208 i = i->prevBookmark; 00209 } 00210 if (!i) throw LogicException("Can't find bookmark to rollback"); 00211 b->rollback(); 00212 } 00213 00214 00215 DECLARE_EXPORT void CommandManager::commit() 00216 { 00217 if (firstBookmark.active) firstBookmark.commit(); 00218 for (Bookmark* i = firstBookmark.nextBookmark; i; ) 00219 { 00220 if (i->active) i->commit(); 00221 Bookmark *tmp = i; 00222 i = i->nextBookmark; 00223 delete tmp; 00224 } 00225 firstBookmark.nextBookmark = NULL; 00226 currentBookmark = &firstBookmark; 00227 lastBookmark = &firstBookmark; 00228 } 00229 00230 00231 DECLARE_EXPORT void CommandManager::rollback() 00232 { 00233 for (Bookmark* i = lastBookmark; i != &firstBookmark;) 00234 { 00235 i->rollback(); 00236 Bookmark *tmp = i; 00237 i = i->prevBookmark; 00238 delete tmp; 00239 } 00240 firstBookmark.rollback(); 00241 firstBookmark.nextBookmark = NULL; 00242 currentBookmark = &firstBookmark; 00243 lastBookmark = &firstBookmark; 00244 } 00245 00246 00247 // 00248 // THREAD GROUP 00249 // 00250 00251 00252 DECLARE_EXPORT void ThreadGroup::execute() 00253 { 00254 #ifndef MT 00255 // CASE 1: Sequential execution when compiled without multithreading 00256 wrapper(this); 00257 #else 00258 // CASE 2: No need to create worker threads when either a) only a single 00259 // worker is allowed or b) only a single function needs to be called. 00260 if (maxParallel<=1 || countCallables<=1) 00261 { 00262 wrapper(this); 00263 return; 00264 } 00265 00266 // CASE 3: Parallel execution in worker threads 00267 int numthreads = countCallables; 00268 // Limit the number of threads to the maximum allowed 00269 if (numthreads > maxParallel) numthreads = maxParallel; 00270 int worker = 0; 00271 #ifdef HAVE_PTHREAD_H 00272 // Create a thread for every command list. The main thread will then 00273 // wait for all of them to finish. 00274 pthread_t threads[numthreads]; // holds thread info 00275 int errcode; // holds pthread error code 00276 00277 // Create the threads 00278 for (; worker<numthreads; ++worker) 00279 { 00280 if ((errcode=pthread_create(&threads[worker], // thread struct 00281 NULL, // default thread attributes 00282 wrapper, // start routine 00283 this))) // arg to routine 00284 { 00285 if (!worker) 00286 { 00287 ostringstream ch; 00288 ch << "Can't create any threads, error " << errcode; 00289 throw RuntimeException(ch.str()); 00290 } 00291 // Some threads could be created. 00292 // Let these threads run and do all the work. 00293 logger << "Warning: Could create only " << worker 00294 << " threads, error " << errcode << endl; 00295 } 00296 } 00297 00298 // Wait for the threads as they exit 00299 for (--worker; worker>=0; --worker) 00300 // Wait for thread to terminate. 00301 // The second arg is NULL, since we don't care about the return status 00302 // of the finished threads. 00303 if ((errcode=pthread_join(threads[worker],NULL))) 00304 { 00305 ostringstream ch; 00306 ch << "Can't join with thread " << worker << ", error " << errcode; 00307 throw RuntimeException(ch.str()); 00308 } 00309 #else 00310 // Create a thread for every command list. The main thread will then 00311 // wait for all of them to finish. 00312 HANDLE* threads = new HANDLE[numthreads]; 00313 unsigned int * m_id = new unsigned int[numthreads]; 00314 00315 // Create the threads 00316 for (; worker<numthreads; ++worker) 00317 { 00318 threads[worker] = reinterpret_cast<HANDLE>( 00319 _beginthreadex(0, // Security atrtributes 00320 0, // Stack size 00321 &wrapper, // Thread function 00322 this, // Argument list 00323 0, // Initial state is 0, "running" 00324 &m_id[worker])); // Address to receive the thread identifier 00325 if (!threads[worker]) 00326 { 00327 if (!worker) 00328 { 00329 // No threads could be created at all. 00330 delete threads; 00331 delete m_id; 00332 throw RuntimeException("Can't create any threads, error " + errno); 00333 } 00334 // Some threads could be created. 00335 // Let these threads run and do all the work. 00336 logger << "Warning: Could create only " << worker 00337 << " threads, error " << errno << endl; 00338 break; // Step out of the thread creation loop 00339 } 00340 } 00341 00342 // Wait for the threads as they exit 00343 int res = WaitForMultipleObjects(worker, threads, true, INFINITE); 00344 if (res == WAIT_FAILED) 00345 { 00346 char error[256]; 00347 FormatMessage( 00348 FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, 00349 NULL, 00350 GetLastError(), 00351 0, 00352 error, 00353 256, 00354 NULL ); 00355 delete threads; 00356 delete m_id; 00357 throw RuntimeException(string("Can't join threads: ") + error); 00358 } 00359 00360 // Cleanup 00361 for (--worker; worker>=0; --worker) 00362 CloseHandle(threads[worker]); 00363 delete threads; 00364 delete m_id; 00365 #endif // End of #ifdef ifHAVE_PTHREAD_H 00366 #endif // End of #ifndef MT 00367 } 00368 00369 00370 DECLARE_EXPORT ThreadGroup::callableWithArgument ThreadGroup::selectNextCallable() 00371 { 00372 ScopeMutexLock l(lock ); 00373 if (callables.empty()) 00374 { 00375 // No more functions 00376 assert( countCallables == 0 ); 00377 return callableWithArgument(static_cast<callable>(NULL),static_cast<void*>(NULL)); 00378 } 00379 callableWithArgument c = callables.top(); 00380 callables.pop(); 00381 --countCallables; 00382 return c; 00383 } 00384 00385 00386 #if defined(HAVE_PTHREAD_H) || !defined(MT) 00387 void* ThreadGroup::wrapper(void *arg) 00388 #else 00389 unsigned __stdcall ThreadGroup::wrapper(void *arg) 00390 #endif 00391 { 00392 // Each OS-level thread needs to initialize a Python thread state. 00393 ThreadGroup *l = static_cast<ThreadGroup*>(arg); 00394 bool threaded = l->maxParallel > 1 && l->countCallables > 1; 00395 if (threaded) PythonInterpreter::addThread(); 00396 00397 for (callableWithArgument nextfunc = l->selectNextCallable(); 00398 nextfunc.first; 00399 nextfunc = l->selectNextCallable()) 00400 { 00401 #if defined(HAVE_PTHREAD_H) && defined(MT) 00402 // Verify whether there has been a cancellation request in the meantime 00403 pthread_testcancel(); 00404 #endif 00405 try {nextfunc.first(nextfunc.second);} 00406 catch (...) 00407 { 00408 // Error message 00409 logger << "Error: Caught an exception while executing command:" << endl; 00410 try {throw;} 00411 catch (exception& e) {logger << " " << e.what() << endl;} 00412 catch (...) {logger << " Unknown type" << endl;} 00413 } 00414 }; 00415 00416 // Finalize the Python thread state 00417 if (threaded) PythonInterpreter::deleteThread(); 00418 return 0; 00419 } 00420 00421 00422 // 00423 // LOADMODULE FUNCTION 00424 // 00425 00426 00427 DECLARE_EXPORT PyObject* loadModule 00428 (PyObject* self, PyObject* args, PyObject* kwds) 00429 { 00430 00431 // Create the command 00432 char *data = NULL; 00433 int ok = PyArg_ParseTuple(args, "s:loadmodule", &data); 00434 if (!ok) return NULL; 00435 00436 // Load parameters for the module 00437 Environment::ParameterList params; 00438 if (kwds) 00439 { 00440 PyObject *key, *value; 00441 Py_ssize_t pos = 0; 00442 while (PyDict_Next(kwds, &pos, &key, &value)) 00443 params[PythonObject(key).getString()] = PythonObject(value).getString(); 00444 } 00445 00446 // Free Python interpreter for other threads. 00447 // This is important since the module may also need access to Python 00448 // during its initialization... 00449 Py_BEGIN_ALLOW_THREADS 00450 try { 00451 // Load the library 00452 Environment::loadModule(data, params); 00453 } 00454 catch(...) 00455 { 00456 Py_BLOCK_THREADS; 00457 PythonType::evalException(); 00458 return NULL; 00459 } 00460 Py_END_ALLOW_THREADS // Reclaim Python interpreter 00461 return Py_BuildValue(""); 00462 } 00463 00464 00465 DECLARE_EXPORT void Environment::printModules() 00466 { 00467 logger << "Loaded modules:" << endl; 00468 for (set<string>::const_iterator i=moduleRegistry.begin(); i!=moduleRegistry.end(); ++i) 00469 logger << " " << *i << endl; 00470 logger << endl; 00471 } 00472 00473 00474 00475 00476 } // end namespace 00477 } // end namespace