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