utils/actions.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2012 by Johan De Taeye, frePPLe bvba *
4  * *
5  * This library is free software; you can redistribute it and/or modify it *
6  * under the terms of the GNU Affero General Public License as published *
7  * by the Free Software Foundation; either version 3 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This library is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU Affero General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU Affero General Public *
16  * License along with this program. *
17  * If not, see <http://www.gnu.org/licenses/>. *
18  * *
19  ***************************************************************************/
20 
21 #define FREPPLE_CORE
22 #include "frepple/utils.h"
23 
24 
25 namespace frepple
26 {
27 namespace utils
28 {
29 
30 
31 //
32 // COMMAND LIST
33 //
34 
35 
37 {
38  // Validity check
39  if (!c) throw LogicException("Adding NULL command to a command list");
40 
41  // Set the owner of the command
42  c->owner = this;
43 
44  // Maintenance of the linked list of child commands
45  c->prev = lastCommand;
46  if (lastCommand)
47  // Let the last command in the chain point to this new extra command
48  lastCommand->next = c;
49  else
50  // This is the first command in this command list
51  firstCommand = c;
52  lastCommand = c;
53 }
54 
55 
57 {
58  // Undo all commands and delete them.
59  // Note that undoing an operation that hasn't been executed yet or has been
60  // undone already is expected to be harmless, so we don't need to worry
61  // about that...
62  for (Command *i = lastCommand; i; )
63  {
64  Command *t = i; // Temporarily store the pointer to be deleted
65  i = i->prev;
66  delete t; // The delete is expected to also revert the change!
67  }
68 
69  // Reset the list
70  firstCommand = NULL;
71  lastCommand = NULL;
72 }
73 
74 
76 {
77  // Undo all commands and delete them.
78  // Note that undoing an operation that hasn't been executed yet or has been
79  // undone already is expected to be harmless, so we don't need to worry
80  // about that...
81  for (Command *i = lastCommand; i; i = i->prev)
82  i->undo();
83 }
84 
85 
87 {
88  // Commit the commands
89  for (Command *i = firstCommand; i;)
90  {
91  Command *t = i; // Temporarily store the pointer to be deleted
92  i->commit();
93  i = i->next;
94  delete t;
95  }
96 
97  // Reset the list
98  firstCommand = NULL;
99  lastCommand = NULL;
100 }
101 
102 
104 {
105  // Redo the commands
106  for (Command* c = firstCommand; c; c = c->next)
107  c->redo();
108 }
109 
110 
112 {
113  if (firstCommand)
114  {
115  logger << "Warning: Deleting a command list with commands that have"
116  << " not been committed or rolled back" << endl;
117  rollback();
118  }
119 }
120 
121 
122 //
123 // COMMAND MANAGER
124 //
125 
126 
128 {
129  Bookmark* n = new Bookmark(currentBookmark);
130  lastBookmark->nextBookmark = n;
131  n->prevBookmark = lastBookmark;
132  lastBookmark = n;
133  currentBookmark = n;
134  return n;
135 }
136 
137 
139 {
140  if (!b) throw LogicException("Can't undo NULL bookmark");
141 
142  Bookmark* i = lastBookmark;
143  for (; i && i != b; i = i->prevBookmark)
144  {
145  if (i->isChildOf(b) && i->active)
146  {
147  i->undo();
148  i->active = false;
149  }
150  }
151  if (!i) throw LogicException("Can't find bookmark to undo");
152  currentBookmark = b->parent;
153 }
154 
155 
157 {
158  if (!b) throw LogicException("Can't redo NULL bookmark");
159 
160  for (Bookmark* i = b; i; i = i->nextBookmark)
161  {
162  if (i->isChildOf(b) && !i->active)
163  {
164  i->redo();
165  i->active = true;
166  }
167  }
168  currentBookmark = b;
169 }
170 
171 
173 {
174  if (!b)
175  throw LogicException("Can't rollback NULL bookmark");
176  if (b == &firstBookmark)
177  throw LogicException("Can't rollback default bookmark");
178 
179  // Remove all later child bookmarks
180  Bookmark* i = lastBookmark;
181  while (i && i != b)
182  {
183  if (i->isChildOf(b))
184  {
185  // Remove from bookmark list
186  if (i->prevBookmark)
187  i->prevBookmark->nextBookmark = i->nextBookmark;
188  if (i->nextBookmark)
189  i->nextBookmark->prevBookmark = i->prevBookmark;
190  else
191  lastBookmark = i->prevBookmark;
192  i->rollback();
193  if (currentBookmark == i)
194  currentBookmark = b;
195  Bookmark* tmp = i;
196  i = i->prevBookmark;
197  delete tmp;
198  }
199  else
200  // Bookmark has a different parent
201  i = i->prevBookmark;
202  }
203  if (!i) throw LogicException("Can't find bookmark to rollback");
204  b->rollback();
205 }
206 
207 
209 {
210  if (firstBookmark.active) firstBookmark.commit();
211  for (Bookmark* i = firstBookmark.nextBookmark; i; )
212  {
213  if (i->active) i->commit();
214  Bookmark *tmp = i;
215  i = i->nextBookmark;
216  delete tmp;
217  }
218  firstBookmark.nextBookmark = NULL;
219  currentBookmark = &firstBookmark;
220  lastBookmark = &firstBookmark;
221 }
222 
223 
225 {
226  for (Bookmark* i = lastBookmark; i != &firstBookmark;)
227  {
228  i->rollback();
229  Bookmark *tmp = i;
230  i = i->prevBookmark;
231  delete tmp;
232  }
233  firstBookmark.rollback();
234  firstBookmark.nextBookmark = NULL;
235  currentBookmark = &firstBookmark;
236  lastBookmark = &firstBookmark;
237 }
238 
239 
240 //
241 // THREAD GROUP
242 //
243 
244 
246 {
247 #ifndef MT
248  // CASE 1: Sequential execution when compiled without multithreading
249  wrapper(this);
250 #else
251  // CASE 2: No need to create worker threads when either a) only a single
252  // worker is allowed or b) only a single function needs to be called.
253  if (maxParallel<=1 || countCallables<=1)
254  {
255  wrapper(this);
256  return;
257  }
258 
259  // CASE 3: Parallel execution in worker threads
260  int numthreads = countCallables;
261  // Limit the number of threads to the maximum allowed
262  if (numthreads > maxParallel) numthreads = maxParallel;
263  int worker = 0;
264 #ifdef HAVE_PTHREAD_H
265  // Create a thread for every command list. The main thread will then
266  // wait for all of them to finish.
267  pthread_t threads[numthreads]; // holds thread info
268  int errcode; // holds pthread error code
269 
270  // Create the threads
271  for (; worker<numthreads; ++worker)
272  {
273  if ((errcode=pthread_create(&threads[worker], // thread struct
274  NULL, // default thread attributes
275  wrapper, // start routine
276  this))) // arg to routine
277  {
278  if (!worker)
279  {
280  ostringstream ch;
281  ch << "Can't create any threads, error " << errcode;
282  throw RuntimeException(ch.str());
283  }
284  // Some threads could be created.
285  // Let these threads run and do all the work.
286  logger << "Warning: Could create only " << worker
287  << " threads, error " << errcode << endl;
288  }
289  }
290 
291  // Wait for the threads as they exit
292  for (--worker; worker>=0; --worker)
293  // Wait for thread to terminate.
294  // The second arg is NULL, since we don't care about the return status
295  // of the finished threads.
296  if ((errcode=pthread_join(threads[worker],NULL)))
297  {
298  ostringstream ch;
299  ch << "Can't join with thread " << worker << ", error " << errcode;
300  throw RuntimeException(ch.str());
301  }
302 #else
303  // Create a thread for every command list. The main thread will then
304  // wait for all of them to finish.
305  HANDLE* threads = new HANDLE[numthreads];
306  unsigned int * m_id = new unsigned int[numthreads];
307 
308  // Create the threads
309  for (; worker<numthreads; ++worker)
310  {
311  threads[worker] = reinterpret_cast<HANDLE>(
312  _beginthreadex(0, // Security atrtributes
313  0, // Stack size
314  &wrapper, // Thread function
315  this, // Argument list
316  0, // Initial state is 0, "running"
317  &m_id[worker])); // Address to receive the thread identifier
318  if (!threads[worker])
319  {
320  if (!worker)
321  {
322  // No threads could be created at all.
323  delete threads;
324  delete m_id;
325  throw RuntimeException("Can't create any threads, error " + errno);
326  }
327  // Some threads could be created.
328  // Let these threads run and do all the work.
329  logger << "Warning: Could create only " << worker
330  << " threads, error " << errno << endl;
331  break; // Step out of the thread creation loop
332  }
333  }
334 
335  // Wait for the threads as they exit
336  int res = WaitForMultipleObjects(worker, threads, true, INFINITE);
337  if (res == WAIT_FAILED)
338  {
339  char error[256];
340  FormatMessage(
341  FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
342  NULL,
343  GetLastError(),
344  0,
345  error,
346  256,
347  NULL );
348  delete threads;
349  delete m_id;
350  throw RuntimeException(string("Can't join threads: ") + error);
351  }
352 
353  // Cleanup
354  for (--worker; worker>=0; --worker)
355  CloseHandle(threads[worker]);
356  delete threads;
357  delete m_id;
358 #endif // End of #ifdef ifHAVE_PTHREAD_H
359 #endif // End of #ifndef MT
360 }
361 
362 
363 DECLARE_EXPORT ThreadGroup::callableWithArgument ThreadGroup::selectNextCallable()
364 {
365  ScopeMutexLock l(lock );
366  if (callables.empty())
367  {
368  // No more functions
369  assert( countCallables == 0 );
370  return callableWithArgument(static_cast<callable>(NULL),static_cast<void*>(NULL));
371  }
372  callableWithArgument c = callables.top();
373  callables.pop();
374  --countCallables;
375  return c;
376 }
377 
378 
379 #if defined(HAVE_PTHREAD_H) || !defined(MT)
380 void* ThreadGroup::wrapper(void *arg)
381 #else
382 unsigned __stdcall ThreadGroup::wrapper(void *arg)
383 #endif
384 {
385  // Each OS-level thread needs to initialize a Python thread state.
386  ThreadGroup *l = static_cast<ThreadGroup*>(arg);
387  bool threaded = l->maxParallel > 1 && l->countCallables > 1;
388  if (threaded) PythonInterpreter::addThread();
389 
390  for (callableWithArgument nextfunc = l->selectNextCallable();
391  nextfunc.first;
392  nextfunc = l->selectNextCallable())
393  {
394 #if defined(HAVE_PTHREAD_H) && defined(MT)
395  // Verify whether there has been a cancellation request in the meantime
396  pthread_testcancel();
397 #endif
398  try {nextfunc.first(nextfunc.second);}
399  catch (...)
400  {
401  // Error message
402  logger << "Error: Caught an exception while executing command:" << endl;
403  try {throw;}
404  catch (const exception& e) {logger << " " << e.what() << endl;}
405  catch (...) {logger << " Unknown type" << endl;}
406  }
407  };
408 
409  // Finalize the Python thread state
410  if (threaded) PythonInterpreter::deleteThread();
411  return 0;
412 }
413 
414 
415 //
416 // LOADMODULE FUNCTION
417 //
418 
419 
420 DECLARE_EXPORT PyObject* loadModule
421 (PyObject* self, PyObject* args, PyObject* kwds)
422 {
423 
424  // Create the command
425  char *data = NULL;
426  int ok = PyArg_ParseTuple(args, "s:loadmodule", &data);
427  if (!ok) return NULL;
428 
429  // Load parameters for the module
431  if (kwds)
432  {
433  PyObject *key, *value;
434  Py_ssize_t pos = 0;
435  while (PyDict_Next(kwds, &pos, &key, &value))
436  params[PythonObject(key).getString()] = PythonObject(value).getString();
437  }
438 
439  // Free Python interpreter for other threads.
440  // This is important since the module may also need access to Python
441  // during its initialization...
442  Py_BEGIN_ALLOW_THREADS
443  try
444  {
445  // Load the library
446  Environment::loadModule(data, params);
447  }
448  catch(...)
449  {
450  Py_BLOCK_THREADS;
452  return NULL;
453  }
454  Py_END_ALLOW_THREADS // Reclaim Python interpreter
455  return Py_BuildValue("");
456 }
457 
458 
460 {
461  logger << "Loaded modules:" << endl;
462  for (set<string>::const_iterator i=moduleRegistry.begin(); i!=moduleRegistry.end(); ++i)
463  logger << " " << *i << endl;
464  logger << endl;
465 }
466 
467 
468 
469 
470 } // end namespace
471 } // end namespace