Commit 54e91eed authored by Mihai Moldovan's avatar Mihai Moldovan
Browse files

src/x2goclient-network-ssh.c: move check timeout to a separate thread, add...

src/x2goclient-network-ssh.c: move check timeout to a separate thread, add synchronization via a mutex.

We cannot execute a timeout in the main (or, for that matter, callingg) thread, since that would require a running main loop. However, libx2goclient is supposed to be used by other applications and we cannot usefully force them into starting a glib main loop. Additionally, that would make executing any other code almost impossible, so that won't fly either way.

Instead, spawn a different thread for this timer (timeout in glib-parlance) and synchronize access to instance variables (and, by extension, the newly added ones for the thread and the like).

Also, make sure that the last check can run out correctly at object finalization time.
parent c8853faa
......@@ -115,12 +115,21 @@ struct _X2GoClientNetworkSSH {
char *control_path;
gboolean active_master_conn;
guint check_timeout;
GSource *check_timeout_source;
GMutex check_thread_mutex;
GThread *check_thread;
GMainContext *check_thread_context;
};
G_DEFINE_TYPE (X2GoClientNetworkSSH, x2goclient_network_ssh, X2GOCLIENT_TYPE_NETWORK);
struct x2goclient_network_ssh_check_timeout_data {
X2GoClientNetworkSSH *self;
GError **gerr;
};
/* See src/x2goclient-network.c regarding abbreviations. */
enum {
X2GO_NET_SSH_PROP_OPENSSH_VERSION = 1,
......@@ -149,8 +158,12 @@ static gboolean x2goclient_network_ssh_gptrarray_to_string (GPtrArray * const ar
static void x2goclient_network_ssh_gptrarray_print_debug (GPtrArray * const arr, const gchar * const prelude, const gchar * const desc);
static gboolean x2goclient_network_ssh_sshcmd_add_host_port (X2GoClientNetworkSSH * const self, GPtrArray * const ssh_cmd, GError ** const gerr);
static gboolean x2goclient_network_ssh_start_sshcmd (X2GoClientNetworkSSH * const self, const GPtrArray * const ssh_cmd, GError ** const gerr, const gboolean master);
static void x2goclient_network_ssh_check_timeout_data_free (struct x2goclient_network_ssh_check_timeout_data *data);
static gpointer x2goclient_network_ssh_check_timeout_thread_main (const gpointer user_data);
static gboolean x2goclient_network_ssh_start_check_timeout (X2GoClientNetworkSSH * const self, GError ** const gerr);
static void x2goclient_network_ssh_start_check_timeout_invoke (X2GoClientNetworkSSH * const self, GError ** const gerr);
static gboolean x2goclient_network_ssh_start_check_timeout_idle_unwrap (const gpointer user_data);
static gboolean x2goclient_network_ssh_start_check_timeout_real (X2GoClientNetworkSSH * const self, GError ** const gerr);
static gboolean x2goclient_network_ssh_check_timeout (const gpointer self);
......@@ -190,7 +203,10 @@ static void x2goclient_network_ssh_init (X2GoClientNetworkSSH * const self) {
self->control_path = NULL;
self->active_master_conn = FALSE;
self->check_timeout = 0;
self->check_timeout_source = NULL;
g_mutex_init (&(self->check_thread_mutex));
self->check_thread = NULL;
self->check_thread_context = NULL;
}
X2GoClientNetworkSSH* x2goclient_network_ssh_new (const char * const session_path) {
......@@ -235,12 +251,26 @@ static void x2goclient_network_ssh_finalize (GObject * const object) {
* connection, so make sure that the dependencies are set up correctly here.
*/
if (self->check_timeout) {
if (!(g_source_remove (self->check_timeout))) {
g_log (NULL, G_LOG_LEVEL_CRITICAL, "Unable to remove timeout with ID %u.", self->check_timeout);
}
/* Remove timeout source. */
g_mutex_lock (&(self->check_thread_mutex));
if (self->check_timeout_source) {
g_source_destroy (self->check_timeout_source);
g_source_unref (self->check_timeout_source);
self->check_timeout_source = NULL;
}
self->check_timeout = 0;
g_mutex_unlock (&(self->check_thread_mutex));
/* Let last event run out and thread terminate. */
(void) g_thread_join (self->check_thread);
self->check_thread = NULL;
/*
* Context should be gone by now, implicitly through the thread's main
* method, so let's just get rid of the pointer.
*/
self->check_thread_context = NULL;
g_mutex_clear (&(self->check_thread_mutex));
if (!(x2goclient_network_ssh_kill_subprocesses (self))) {
g_log (NULL, G_LOG_LEVEL_CRITICAL, "Some subprocesses were not terminated correctly!");
......@@ -726,7 +756,10 @@ static gboolean x2goclient_network_ssh_kill_subprocesses (X2GoClientNetworkSSH *
g_return_val_if_fail (X2GOCLIENT_IS_NETWORK_SSH (self), ret);
g_mutex_lock (&(self->check_thread_mutex));
if (self->active_master_conn) {
g_mutex_unlock (&(self->check_thread_mutex));
/* Cleanup, if necessary. */
g_log (NULL, G_LOG_LEVEL_DEBUG, "Master connection cleanup required.");
......@@ -735,7 +768,9 @@ static gboolean x2goclient_network_ssh_kill_subprocesses (X2GoClientNetworkSSH *
/* Add control path options. */
g_ptr_array_add (ssh_cmd, g_strdup ("-o"));
g_mutex_lock (&(self->check_thread_mutex));
g_ptr_array_add (ssh_cmd, g_strdup_printf ("ControlPath=\"%s\"", self->control_path));
g_mutex_unlock (&(self->check_thread_mutex));
/* Add master socket command. */
g_ptr_array_add (ssh_cmd, g_strdup ("-O"));
......@@ -752,9 +787,14 @@ static gboolean x2goclient_network_ssh_kill_subprocesses (X2GoClientNetworkSSH *
g_log (NULL, G_LOG_LEVEL_DEBUG, "Launching!");
ret = x2goclient_network_ssh_start_sshcmd (self, ssh_cmd, NULL, FALSE);
g_mutex_lock (&(self->check_thread_mutex));
self->active_master_conn = (!(ret));
g_mutex_unlock (&(self->check_thread_mutex));
}
}
else {
g_mutex_unlock (&(self->check_thread_mutex));
}
return (ret);
}
......@@ -797,7 +837,9 @@ static gboolean x2goclient_network_ssh_parent_connect (X2GoClientNetwork * const
GString *tmp_string = g_string_new (session_path);
g_string_append (tmp_string, "/ssh/control");
g_mutex_lock (&(self->check_thread_mutex));
self->control_path = g_string_free (tmp_string, FALSE);
g_mutex_unlock (&(self->check_thread_mutex));
gchar *dirname = g_path_get_dirname (self->control_path);
errno = 0;
......@@ -837,7 +879,9 @@ static gboolean x2goclient_network_ssh_parent_connect (X2GoClientNetwork * const
g_log (NULL, G_LOG_LEVEL_INFO, "Fetched OpenSSH version: [ Major: %d, Minor: %d, Patch: %d, Additional Data: '%s' ]", self->openssh_version->major, self->openssh_version->minor, self->openssh_version->patch, self->openssh_version->addon);
/* Update/populate bugs structure. */
g_mutex_lock (&(self->check_thread_mutex));
ret = x2goclient_openssh_bugs_update (self->openssh_bugs, self->openssh_version, gerr);
g_mutex_unlock (&(self->check_thread_mutex));
}
if (ret) {
......@@ -903,7 +947,10 @@ static gboolean x2goclient_network_ssh_parent_connect (X2GoClientNetwork * const
x2goclient_network_ssh_gptrarray_print_debug (ssh_cmd, "Would try to connect via:", "OpenSSH client command");
g_log (NULL, G_LOG_LEVEL_DEBUG, "Launching!");
self->active_master_conn = ret = x2goclient_network_ssh_start_sshcmd (self, ssh_cmd, gerr, TRUE);
ret = x2goclient_network_ssh_start_sshcmd (self, ssh_cmd, gerr, TRUE);
g_mutex_lock (&(self->check_thread_mutex));
self->active_master_conn = ret;
g_mutex_unlock (&(self->check_thread_mutex));
}
if (ret) {
......@@ -936,8 +983,10 @@ static gboolean x2goclient_network_ssh_fetch_openssh_version (X2GoClientNetworkS
* it), so we'll have to duplicate a bit of code and can't use the
* GObject property setter directly.
*/
g_mutex_lock (&(self->check_thread_mutex));
x2goclient_openssh_version_free (self->openssh_version);
self->openssh_version = version;
g_mutex_unlock (&(self->check_thread_mutex));
ret = TRUE;
}
......@@ -1385,23 +1434,103 @@ static gboolean x2goclient_network_ssh_start_sshcmd (X2GoClientNetworkSSH * cons
return (ret);
}
static void x2goclient_network_ssh_check_timeout_data_free (struct x2goclient_network_ssh_check_timeout_data *data) {
/* No need to clear any data within the structure. */
g_free (data);
}
static gpointer x2goclient_network_ssh_check_timeout_thread_main (const gpointer user_data) {
gpointer ret = NULL;
GMainContext *main_context = user_data;
GMainLoop *main_loop;
/* Make given main context the default one for this thread. */
g_main_context_push_thread_default (main_context);
/* Add new main loop and execute it. */
main_loop = g_main_loop_new (main_context, FALSE);
g_main_loop_run (main_loop);
/* Clean up everything after g_main_loop_run () returned. */
g_main_loop_unref (main_loop);
g_main_context_pop_thread_default (main_context);
g_main_context_unref (main_context);
return (ret);
}
static gboolean x2goclient_network_ssh_start_check_timeout (X2GoClientNetworkSSH * const self, GError ** const gerr) {
gboolean ret = FALSE;
g_return_val_if_fail (X2GOCLIENT_IS_NETWORK_SSH (self), ret);
g_return_val_if_fail (((NULL == gerr) || (NULL == *gerr)), ret);
guint check_id = g_timeout_add (50, &x2goclient_network_ssh_check_timeout, self);
g_mutex_lock (&(self->check_thread_mutex));
self->check_thread_context = g_main_context_new ();
g_mutex_unlock (&(self->check_thread_mutex));
g_thread_new ("check_thread", &x2goclient_network_ssh_check_timeout_thread_main, g_main_context_ref (self->check_thread_context));
ret = (!(!(check_id)));
x2goclient_network_ssh_start_check_timeout_invoke (self, gerr);
if (ret) {
self->check_timeout = check_id;
ret = TRUE;
return (ret);
}
static void x2goclient_network_ssh_start_check_timeout_invoke (X2GoClientNetworkSSH * const self, GError ** const gerr) {
g_return_if_fail (X2GOCLIENT_IS_NETWORK_SSH (self));
g_return_if_fail (((NULL == gerr) || (NULL == *gerr)));
struct x2goclient_network_ssh_check_timeout_data *data = g_new0 (struct x2goclient_network_ssh_check_timeout_data, 1);
data->self = self;
data->gerr = gerr;
g_main_context_invoke_full (self->check_thread_context, G_PRIORITY_DEFAULT,
&x2goclient_network_ssh_start_check_timeout_idle_unwrap,
data, (GDestroyNotify) x2goclient_network_ssh_check_timeout_data_free);
}
static gboolean x2goclient_network_ssh_start_check_timeout_idle_unwrap (const gpointer user_data) {
gboolean ret = G_SOURCE_CONTINUE;
const struct x2goclient_network_ssh_check_timeout_data *data = user_data;
g_return_val_if_fail (X2GOCLIENT_IS_NETWORK_SSH (data->self), ret);
g_return_val_if_fail (((NULL == data->gerr) || (NULL == *(data->gerr))), ret);
if (x2goclient_network_ssh_start_check_timeout_real (data->self, data->gerr)) {
ret = G_SOURCE_REMOVE;
}
return (ret);
}
static gboolean x2goclient_network_ssh_start_check_timeout_real (X2GoClientNetworkSSH * const self, GError ** const gerr) {
gboolean ret = FALSE;
g_return_val_if_fail (X2GOCLIENT_IS_NETWORK_SSH (self), ret);
g_return_val_if_fail (((NULL == gerr) || (NULL == *gerr)), ret);
g_mutex_lock (&(self->check_thread_mutex));
/* Add source. */
self->check_timeout_source = g_timeout_source_new (50);
/* Bind check function to timeout source. */
g_source_set_callback (self->check_timeout_source, &x2goclient_network_ssh_check_timeout, self, NULL);
/* Bind timeout source to thread context. */
guint check_id = g_source_attach (self->check_timeout_source, self->check_thread_context);
g_mutex_unlock (&(self->check_thread_mutex));
ret = (!(!(check_id)));
return (ret);
}
static gboolean x2goclient_network_ssh_check_timeout (const gpointer data) {
gboolean ret = FALSE;
......@@ -1414,7 +1543,9 @@ static gboolean x2goclient_network_ssh_check_timeout (const gpointer data) {
/* Add control path options. */
g_ptr_array_add (ssh_cmd, g_strdup ("-o"));
g_mutex_lock (&(self->check_thread_mutex));
g_ptr_array_add (ssh_cmd, g_strdup_printf ("ControlPath=\"%s\"", self->control_path));
g_mutex_unlock (&(self->check_thread_mutex));
/* Add master socket command. */
g_ptr_array_add (ssh_cmd, g_strdup ("-O"));
......@@ -1434,11 +1565,17 @@ static gboolean x2goclient_network_ssh_check_timeout (const gpointer data) {
}
if (!(ret)) {
/* We'll return FALSE, so make sure to reset the instance variable. */
self->check_timeout = 0;
/* We'll return FALSE, so make sure to delete the source. */
g_mutex_lock (&(self->check_thread_mutex));
if (self->check_timeout_source) {
g_source_destroy (self->check_timeout_source);
g_source_unref (self->check_timeout_source);
self->check_timeout_source = NULL;
}
/* Also, mark the master connection is terminated/gone. */
/* Also, mark the master connection as terminated/gone. */
self->active_master_conn = FALSE;
g_mutex_unlock (&(self->check_thread_mutex));
}
return (ret);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment