Here is a python code example.
The first block below is my automated and default environment configuration and to catch messages for put on my console.
Then I just execute a few test python commands at the end.
The simple first command “gxsm.sleep(10)” is blocking the g_idle already.
The python code is NOT hanging nor the GUI is blocking or hanging either!
Only a GUI update (done via a g_idle) stalls as long as long as the python thread runs.
I also included below the python code the C++ code fragments I wrote to thread start and the thread:
see “py_gxsm_console::run_command(…)”.
Another note: I do have at this time one (and at times more) more g_idle() running to watch and update the “console” output from the python. That actually runs OK (started 1st along with the python thread).
May be a priority issue between g_idle functions?
Here is a visual demo:
Watch
a) the (noisy test data) scanning image data appearing (top center window) and stalling updates when once the script is started. (a g_idle is managing the image updates)
b) also watch the “very top right” tip position indicator (this is periodically updated wit ha g_timeout)
c) the g_idle image update “catching up” once the script is finished.
It’s so weird, the program GUI is functionally just fine, I can do manual updates/etc… of the data, only the g_idle to do this automatically stalls and but catches up again when the python is done.
However, I also tried for testing (non ideal) to replace the g_idle for image update with a g_timeout, also stalls, no idea here. The simple “sleep” call is not interfering with any of my data.
Python code:
## environment and redirection setup, executed once at initialization
import redirection
import sys
class StdoutCatcher:
def write(self, stuff):
redirection.stdoutredirect(stuff)
def flush(self):
redirection.stdoutredirect('\n')
class StderrCatcher:
def write(self, stuff):
redirection.stdoutredirect(stuff)
def flush(self):
redirection.stdoutredirect('\n')
sys.stdout = StdoutCatcher()
sys.stderr = StderrCatcher()
# import own gxsm interface functions
import gxsm
#### actual test script, executed later by user in the same python env.
print ("sleep 10 test") ## this is doing no more than a "g_usleep() on C level"
gxsm.sleep (10)
print ("start scan test")
gxsm.startscan ()
print ("watching...")
print ('y=', gxsm.y_current())
gxsm.sleep (10)
print ('y=', gxsm.y_current())
gxsm.sleep (10)
print ('y=', gxsm.y_current())
The key functions running the python interpreter in a thread. I tested both a g_thread and also a g_task – both has the same outcome blocking the g_idle.
void py_gxsm_console::PyRun_GTaskThreadFunc (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable){
PyRunThreadData *s = (PyRunThreadData*) task_data;
PI_DEBUG_GM (DBG_L2, "pyremote Plugin :: py_gxsm_console::PyRun_GTaskThreadFunc");
s->ret = PyRun_String(s->cmd,
s->mode,
s->dictionary,
s->dictionary);
g_free (s->cmd);
s->cmd = NULL;
PI_DEBUG_GM (DBG_L2, "pyremote Plugin :: py_gxsm_console::PyRun_GTaskThreadFunc done");
}
gpointer py_gxsm_console::PyRun_GThreadFunc (gpointer data){
PyRunThreadData *s = (PyRunThreadData*) data;
PI_DEBUG_GM (DBG_L2, "pyremote Plugin :: py_gxsm_console::PyRun_GThreadFunc");
s->ret = PyRun_String(s->cmd,
s->mode,
s->dictionary,
s->dictionary);
g_free (s->cmd);
s->cmd = NULL;
PI_DEBUG_GM (DBG_L2, "pyremote Plugin :: py_gxsm_console::PyRun_GThreadFunc PyRun completed");
if (!s->ret) PyErr_Print();
--s->pygc->user_script_running;
s->pygc->push_message_async (s->ret ?
"\n<<< PyRun user script (as thread) finished. <<<\n" :
"\n<<< PyRun user script (as thread) run raised an exeption. <<<\n");
s->pygc->push_message_async (NULL); // terminate IDLE push task
PI_DEBUG_GM (DBG_L2, "pyremote Plugin :: py_gxsm_console::PyRun_GThreadFunc finished.");
return NULL;
}
void py_gxsm_console::PyRun_GAsyncReadyCallback (GObject *source_object,
GAsyncResult *res,
gpointer user_data){
PI_DEBUG_GM (DBG_L2, "pyremote Plugin :: py_gxsm_console::PyRun_GAsyncReadyCallback");
py_gxsm_console *pygc = (py_gxsm_console *)user_data;
if (!pygc->run_data.ret) PyErr_Print();
--pygc->user_script_running;
pygc->push_message_async (pygc->run_data.ret ?
"\n<<< PyRun user script (as thread) finished. <<<\n" :
"\n<<< PyRun user script (as thread) run raised an exeption. <<<\n");
pygc->push_message_async (NULL); // terminate IDLE push task
PI_DEBUG_GM (DBG_L2, "pyremote Plugin :: py_gxsm_console::PyRun_GAsyncReadyCallback done");
}
const gchar* py_gxsm_console::run_command(const gchar *cmd, int mode)
{
if (!cmd) {
g_warning("No command.");
return NULL;
}
PyErr_Clear(); // clear any previous error or interrupts set
g_idle_add (pop_message_list_to_console, this); // keeps running and watching for async console data to display
if (!run_data.cmd){
PI_DEBUG_GM (DBG_L2, "pyremote Plugin :: py_gxsm_console::run_command *** starting console IDLE message pop job.");
run_data.cmd = g_strdup (cmd);
run_data.mode = mode;
run_data.dictionary = dictionary;
run_data.ret = NULL;
run_data.pygc = this;
#if 1
g_thread_new (NULL, PyRun_GThreadFunc, &run_data);
#else
GTask *pyrun_task = g_task_new (NULL,
NULL,
PyRun_GAsyncReadyCallback, this);
g_task_set_task_data (pyrun_task, &run_data, NULL);
g_task_run_in_thread (pyrun_task, PyRun_GTaskThreadFunc);
#endif
PI_DEBUG_GM (DBG_L2, "pyremote Plugin :: py_gxsm_console::run_command thread fired up");
return NULL;
} else {
return "Busy";
}
}
I am now confused – is GSList not thread safe? I though glib itself is thread safe in general? OK, I tried.
Update: I tested it with a added mutex for “message_list” as used below, no change in behavior.
void push_message_async (const gchar *msg){
g_mutex_lock (&g_list_mutex);
if (msg)
message_list = g_slist_prepend (message_list, g_strdup(msg));
else
message_list = g_slist_prepend (message_list, NULL); // push self terminate IDLE task mark
g_mutex_unlock (&g_list_mutex);
}
static gboolean pop_message_list_to_console (gpointer user_data){
py_gxsm_console *pygc = (py_gxsm_console*) user_data;
g_mutex_lock (&g_list_mutex);
if (!pygc->message_list){
g_mutex_unlock (&g_list_mutex);
return true;
}
GSList* last = g_slist_last (pygc->message_list);
if (!last){
g_mutex_unlock (&g_list_mutex);
return true;
}
if (last -> data) {
pygc->append (last -> data);
g_free (last -> data);
pygc->message_list = g_slist_delete_link (pygc->message_list, last);
g_mutex_unlock (&g_list_mutex);
return true;
} else { // NULL data mark found
pygc->message_list = g_slist_delete_link (pygc->message_list, last);
g_mutex_unlock (&g_list_mutex);
pygc->append ("--END IDLE--");
return false; // finish IDLE task
}
}
The C code for the “gxsm.sleep()”:
static PyObject* remote_sleep(PyObject *self, PyObject *args)
{
PI_DEBUG(DBG_L2, "pyremote: Sleep ");
double d;
if (!PyArg_ParseTuple(args, "d", &d))
return Py_BuildValue("i", -1);
if (d>0.){ // d in 1/10s
g_usleep ((useconds_t)round(d*1e5)); // now in a thread and can simply sleep here!
// sleep_ms((int)(round(d*100)));
}
return Py_BuildValue("i", 0);
}