/* This source file is a provided to reproduce a possible problem in stackless python. The original version can be found here: http://svn.python.org/view/stackless/sandbox/examples/embedding/watchdog-cpp/main.cpp?view=log Much thanks to Juho Makinen for his sample frameowork. */ #include #include #include #include "sync.h" #include "sleeper.h" using namespace std; namespace py = boost::python; // Stackless ticker. This takes care of re-scheduling // tasklets which have called beNice (every tasklet should call beNice) // and running the watchdog. void stacklessTick() { using namespace boost::python; // Normally call wakeupSleepers here, but as we are only demonstrating // how the sleepers system works, we call it manually from stacklessMain // during the sleeper test case. // Run the reScheduler, which will load the taskler running queue try { int balance = extract(Sync::niceChannel.attr("balance")); PyChannelObject *channel = reinterpret_cast(Sync::niceChannel.ptr()); for (int i = balance; i < 0; i++) { PyChannel_Send(channel, Py_None); } } catch (boost::python::error_already_set&) { std::cerr << "reShedule error" << std::endl; PyErr_Print(); PyErr_Clear(); } // Run all pending tasklets until they call themselfs nice handle<> watchdogHandle; do { try { watchdogHandle = handle<>(allow_null(PyStackless_RunWatchdog(1000))); py::object obj(watchdogHandle); if (obj.ptr() != Py_None) { // handle stacklet which has been running too long cerr << "Watchdog interrupted a tasklet which was running too long without calling beNice" << endl; int retval = PyTasklet_Kill(reinterpret_cast(obj.ptr())); cerr << "Killing this tasklet, retval: " << retval << " (expecting 0)" << endl; } } catch (error_already_set&) { std::cerr << "Got error from a tasklet process" << std::endl; PyErr_Print(); PyErr_Clear(); } } while (!watchdogHandle); } // This method is used to wake up the sleepers. // Parameters: // currentTime: This is a variable which holds number of "time units" // passed from the beginning of the program. // // sleepers: This is a SleepersSet which contains the sleepers // which are sleeping. If a sleepers getResumeTime() // is greater than currentTime, then the sleeper must // be awaken. // // The program might have multiple SleepersSets, one for real time // sleepers and another for "game time" sleepers and this function // can be used for both of them by supplying different arguments. // // Normally you would call this from stacklessTick method, // but here we call this from stacklessMain when we test and demostrate // how this is used. int wakeUpSleepers(int currentTime, Sync::SleepersSet& sleepers) { Sync::SleepersSet::iterator begin = sleepers.begin(); Sync::SleepersSet::iterator end = sleepers.end(); if (begin == end) return 0; if ((*begin)->getResumeTime() > currentTime) // There are no sleepers to be resumed return 0; Sync::SleepersSet::iterator pos = begin; Sync::SleepersSet::iterator next; int count = 0; while (1) { SleeperPtr sleeper = *pos; next = pos; next++; sleepers.erase(pos); pos = next; py::object none; PyChannel_Send(sleeper->getBorrowedChannel(), none.ptr()); count++; if (next == end) break; if ((*next)->getResumeTime() <= currentTime) break; } return count; } PyObject* global_dict = NULL; void stacklessMain() { // TEST 2 // // Test the beNice implementation. // Notice that this function is actually the main tasklet, // so we first define a function, then we start this function as // another tasklet, which calls beNice once a while. string code = "def func2():\n" " c = 0\n" " for i in xrange(4):\n" " c += 1\n" " print \"from stacklessMain: c is: \" + str(c)\n" " Sync.beNice()\n" " Sync.pushTestResult(c)\n" "stackless.tasklet(func2)()\n"; PyRun_SimpleString(code.c_str()); for (int i = 0; i < 5; i++) { // Execute the stackless ticker. This is what you would call // once on every frame of a game, for example. stacklessTick(); } // correct value for c is 4 int retval = py::extract(Sync::popTestResult()); cout << "Checking that c is actually four: c == " << retval << endl; assert(retval == 4); // TEST 3 // // Test the watchdog. code = "def func3():\n" " c = 0\n" " while 1:\n" " c += 1\n" " pass\n" "stackless.tasklet(func3)()\n"; PyRun_SimpleString(code.c_str()); // Execute the stackless ticker. As our func3() behaves badly, // and doesn't call Sync.beNice(), the Watchdog should interrupt // the execution. stacklessTick(); // TEST 4 // // Test the sleep implementation // Notice that this implementation doesn't use real clock times, // but we only simulate this by adding 250 "time units" // on each ticker loop. code = "def func4(time):\n" " Sync.sleepReal(time)\n" // sleep for time amounts of "time units", supplied as argument " Sync.pushTestResult(time)\n" // Push the sleeped time to the test result stack " print \"I'm awake, I slept for \" + str(time) + \" time units\"\n" "stackless.tasklet(func4)(500)\n" // Create tasklet which sleeps for 500 "time units" "stackless.tasklet(func4)(2000)\n" "stackless.tasklet(func4)(1000)\n" "stackless.tasklet(func4)(1000)\n" "stackless.tasklet(func4)(250)\n"; PyRun_SimpleString(code.c_str()); // We simulate the time by having "current time" variable, which is incremented on every loop iteration int currentTime = 0; for (int i = 0; i < 10; i++) { wakeUpSleepers(currentTime, Sync::realtimeSleepersSet); stacklessTick(); currentTime += 250; } // Verify that the sleepers were awaken in the right order. assert(static_cast(py::extract(Sync::popTestResult())) == 250); assert(static_cast(py::extract(Sync::popTestResult())) == 500); assert(static_cast(py::extract(Sync::popTestResult())) == 1000); assert(static_cast(py::extract(Sync::popTestResult())) == 1000); assert(static_cast(py::extract(Sync::popTestResult())) == 2000); /// REPRODUCING CRASH TEST // Simple code to reproduce the problem. // Create a tasklet, giving it a channel, make the new tasklet wait 2 seconds then send an exception // The exception will cause a NULL value to be returned to the caller and thus a C++ exception is thrown // in the CrashTest C++ function defined below code = "import time\n" \ "def TestException( c ):\n" \ " t = time.time()\n" \ " target = t + 2\n" \ " while time.time() < target:\n" \ " Sync.beNice()\n" \ " c.send_exception( 'test' )\n" \ "def TestTasklet():\n" \ " t = stackless.tasklet()\n" \ " c = stackless.channel()\n" \ " t.bind( TestException )\n" \ " t( c )\n" \ " c.receive();\n"; // Load the test code PyRun_String( code.c_str(), Py_file_input, global_dict, global_dict ); // Create two instances of the tasklets code = "t = stackless.tasklet()\nt.bind( TestTasklet )\nt()\n"; for ( int i = 0; i < 2; i++ ) PyRun_String( code.c_str(), Py_file_input, global_dict, global_dict ); // Run the stackless scheduler forever while ( true ) { stacklessTick(); } } void CrashTest() { // Repeatedly call the test method while ( true ) { PyObject* arg = PyTuple_New(0); PyObject* method = PyDict_GetItemString( global_dict, "TestTasklet" ); Py_INCREF( method ); PyObject* result = PyObject_CallObject( method, arg ); Py_DECREF( method ); Py_DECREF( arg ); // If we get a result, decref, otherwise throw the standard boost error already set if ( result ) { Py_DECREF( result ); } else { throw py::error_already_set(); } } } int main (int argc, char** argv) { // Initialize Python Py_SetProgramName(argv[0]); Py_InitializeEx(0); if (!Py_IsInitialized()) { cerr << "Python initialization failed" << endl; return -1; } PySys_SetArgv(argc, argv); // setup the niceChannel object Sync::niceChannel = py::object(py::handle<>(reinterpret_cast(PyChannel_New(NULL)))); PyChannel_SetPreference(reinterpret_cast(Sync::niceChannel.ptr()), 0); PyRun_SimpleString("import stackless\n" "stackless.getcurrent().block_trap = True"); py::handle<> mainHandle(py::borrowed(PyImport_AddModule("__main__"))); py::object mainModule(mainHandle); py::scope mainScope(mainModule); py::object o = mainModule.attr("__dict__"); global_dict = o.ptr(); Py_INCREF( global_dict ); py::object class_Sync = py::class_("Sync") .def("beNice", &Sync::beNice) .staticmethod("beNice") .def("sleepReal", &Sync::sleepReal) .staticmethod("sleepReal") .def("pushTestResult", &Sync::pushTestResult) .staticmethod("pushTestResult") ; py::def("stacklessMain", &stacklessMain); // Add TestCase py::def( "Test", &CrashTest ); // // TEST 1: Verify that python is initialized and it's working // // Run simple test to verify that everything is running fine PyRun_SimpleString("Sync.pushTestResult(42)"); int retval = py::extract(Sync::popTestResult()); cout << "Checking that we could execute a python code. retval == " << retval << " (expected 42)" << endl; assert(retval == 42); // Start the stacklessMain method inside stackless context // This function call will block until the stacklessMain method is done PyStackless_CallMethod_Main(mainModule.ptr(), "stacklessMain", 0); //Py_Finalize(); }