Commit a8e3547b for guacamole.apache.org
commit a8e3547b779fdd9ea718b75134e8de92ef61144d
Author: Michael Jumper <mjumper@apache.org>
Date: Fri Sep 25 18:08:51 2026 -0700
GUACAMOLE-2118: Monitor all operations which must not block with watchdog.
diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am
index af9e7fed..cb575f43 100644
--- a/src/libguac/Makefile.am
+++ b/src/libguac/Makefile.am
@@ -132,6 +132,7 @@ libguac_la_SOURCES = \
argv.c \
audio.c \
client.c \
+ client-watchdog.c \
display.c \
display-builtin-cursors.c \
display-cursor.c \
diff --git a/src/libguac/client-watchdog.c b/src/libguac/client-watchdog.c
new file mode 100644
index 00000000..13849379
--- /dev/null
+++ b/src/libguac/client-watchdog.c
@@ -0,0 +1,374 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "guacamole/client.h"
+#include "guacamole/error.h"
+#include "guacamole/flag.h"
+#include "guacamole/mem.h"
+#include "guacamole/proctitle.h"
+#include "guacamole/timestamp.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/**
+ * Flag set within the state of a watchdog once teardown of the connection has
+ * begun. This flag is never cleared. Once set, the connection being monitored
+ * must finish cleaning up within the cleanup timeout given to
+ * guac_client_watchdog_start(). If the connection does not finish cleaning up
+ * within this time, it will be forcibly killed.
+ */
+#define GUAC_CLIENT_WATCHDOG_TEARDOWN_STARTED 1
+
+/**
+ * Flag set within the state of a watchdog once teardown of the connection has
+ * completed. This flag is never cleared.
+ */
+#define GUAC_CLIENT_WATCHDOG_TEARDOWN_ENDED 2
+
+/**
+ * Flag set within the state of a watchdog once all users have left the
+ * connection. Once set, this flag is never cleared, and no further users are
+ * permitted to join.
+ */
+#define GUAC_CLIENT_WATCHDOG_USERS_GONE 4
+
+/**
+ * The polling interval of the watchdog's state checks, in milliseconds.
+ */
+#define GUAC_CLIENT_WATCHDOG_POLLING_INTERVAL 1000
+
+struct guac_client_watchdog {
+
+ /**
+ * The state of the watchdog. This guac_flag also functions as the lock for
+ * other state-related members of this structure.
+ *
+ * @see GUAC_CLIENT_WATCHDOG_TEARDOWN_STARTED
+ * @see GUAC_CLIENT_WATCHDOG_TEARDOWN_ENDED
+ * @see GUAC_CLIENT_WATCHDOG_USERS_GONE
+ */
+ guac_flag state;
+
+ /**
+ * The client whose connection this watchdog observes.
+ *
+ * @important
+ * This pointer MUST NOT be dereferenced once
+ * GUAC_CLIENT_WATCHDOG_TEARDOWN_STARTED has been set.
+ *
+ * @important
+ * This value must only be read or modified while holding the state
+ * lock.
+ */
+ guac_client* client;
+
+ /**
+ * The number of milliseconds allowed for a timed operation before the
+ * connection process is considered unresponsive. Within the scope of client
+ * plugins, and with the sole exception of the free_handler covered by
+ * cleanup_timeout, the handlers of guac_client and guac_user are considered
+ * timed operations. This value may safely be read without acquiring the
+ * state lock.
+ */
+ unsigned const int op_timeout;
+
+ /**
+ * The maximum number of milliseconds the process may take to clean up
+ * before it is forcibly killed. This value may safely be read without
+ * acquiring the state lock.
+ */
+ unsigned const int cleanup_timeout;
+
+ /**
+ * Whether the connection being monitored has been requested to stop. This
+ * value may safely be modified or checked without acquiring the state lock.
+ *
+ * @see guac_client_watchdog_stop_connection().
+ */
+ volatile sig_atomic_t stop_requested;
+
+ /**
+ * Whether users are currently present on the connection.
+ *
+ * @note
+ * This value is maintained as a consistent rough count. It increases
+ * as users join, decreases as users leave, is non-zero when at least
+ * one user is present, and is exactly zero once all users have left.
+ * An individual user may be counted multiple times so long as that
+ * same number is decremented from this value when that user leaves.
+ *
+ * @important
+ * This value must only be read or modified while holding the state
+ * lock.
+ */
+ unsigned int users;
+
+ /**
+ * The number of timed operations currently in progress across all users of
+ * the connection.
+ *
+ * @important
+ * This value must only be read or modified while holding the state
+ * lock.
+ */
+ unsigned int active_operations;
+
+ /**
+ * The number of times any timed operation has started or ended, across all
+ * users of the connection.
+ *
+ * @important
+ * This value must only be read or modified while holding the state
+ * lock.
+ */
+ unsigned long total_operation_events;
+
+};
+
+int guac_client_watchdog_notify_user_joined(guac_client* client) {
+
+ guac_client_watchdog* watchdog = client->__watchdog;
+ if (watchdog == NULL)
+ return 0;
+
+ guac_flag_lock(&watchdog->state);
+
+ /* No further users are accepted after the last user has left */
+ if (watchdog->state.value & GUAC_CLIENT_WATCHDOG_USERS_GONE) {
+ guac_flag_unlock(&watchdog->state);
+ return 1;
+ }
+
+ watchdog->users++;
+ guac_flag_unlock(&watchdog->state);
+
+ return 0;
+
+}
+
+void guac_client_watchdog_notify_user_left(guac_client* client) {
+
+ guac_client_watchdog* watchdog = client->__watchdog;
+ if (watchdog == NULL)
+ return;
+
+ guac_flag_lock(&watchdog->state);
+
+ if (watchdog->users && --watchdog->users == 0)
+ guac_flag_set(&watchdog->state, GUAC_CLIENT_WATCHDOG_USERS_GONE);
+
+ guac_flag_unlock(&watchdog->state);
+
+}
+
+void guac_client_watchdog_notify_operation_start(guac_client* client) {
+
+ guac_client_watchdog* watchdog = client->__watchdog;
+ if (watchdog == NULL)
+ return;
+
+ guac_flag_lock(&watchdog->state);
+ watchdog->active_operations++;
+ watchdog->total_operation_events++;
+ guac_flag_unlock(&watchdog->state);
+
+}
+
+void guac_client_watchdog_notify_operation_end(guac_client* client) {
+
+ guac_client_watchdog* watchdog = client->__watchdog;
+ if (watchdog == NULL)
+ return;
+
+ guac_flag_lock(&watchdog->state);
+
+ if (watchdog->active_operations)
+ watchdog->active_operations--;
+
+ watchdog->total_operation_events++;
+ guac_flag_unlock(&watchdog->state);
+
+}
+
+void guac_client_watchdog_stop_connection(guac_client_watchdog* watchdog) {
+ watchdog->stop_requested = 1;
+}
+
+void guac_client_watchdog_notify_teardown_start(guac_client_watchdog* watchdog) {
+
+ guac_flag_set_and_lock(&watchdog->state, GUAC_CLIENT_WATCHDOG_TEARDOWN_STARTED);
+
+ if (watchdog->users == 0)
+ guac_flag_set(&watchdog->state, GUAC_CLIENT_WATCHDOG_USERS_GONE);
+
+ guac_flag_unlock(&watchdog->state);
+
+}
+
+void guac_client_watchdog_await_teardown(guac_client_watchdog* watchdog) {
+ guac_flag_wait_and_lock(&watchdog->state, GUAC_CLIENT_WATCHDOG_TEARDOWN_STARTED);
+ guac_flag_unlock(&watchdog->state);
+}
+
+void guac_client_watchdog_notify_teardown_end(guac_client_watchdog* watchdog) {
+
+ guac_flag_lock(&watchdog->state);
+
+ if (watchdog->state.value & GUAC_CLIENT_WATCHDOG_TEARDOWN_STARTED)
+ guac_flag_set(&watchdog->state, GUAC_CLIENT_WATCHDOG_TEARDOWN_ENDED);
+
+ guac_flag_unlock(&watchdog->state);
+
+}
+
+void guac_client_watchdog_await_all_users(guac_client_watchdog* watchdog) {
+ guac_flag_wait_and_lock(&watchdog->state, GUAC_CLIENT_WATCHDOG_USERS_GONE);
+ guac_flag_unlock(&watchdog->state);
+}
+
+/**
+ * Thread which guarantees the process serving a client does not continue
+ * past that client's connection.
+ *
+ * @param data
+ * The guac_client_watchdog describing the watchdog.
+ *
+ * @return
+ * Always NULL.
+ */
+static void* guac_client_watchdog_thread(void* data) {
+
+ guac_thread_name_set("watchdog");
+
+ guac_client_watchdog* watchdog = (guac_client_watchdog*) data;
+
+ /* Do not allow the signal handlers of any client plugin (or its libraries)
+ * to run on this thread. A signal handler that preempts this thread and
+ * blocks could prevent the cleanup deadline from being enforced. */
+ sigset_t all_signals;
+ sigfillset(&all_signals);
+ pthread_sigmask(SIG_BLOCK, &all_signals, NULL);
+
+ unsigned long last_operation_events = 0;
+ guac_timestamp last_progress = guac_timestamp_current();
+
+ /* Watch connection state until teardown begins */
+ for (;;) {
+
+ if (!guac_flag_timedwait_and_lock(&watchdog->state,
+ GUAC_CLIENT_WATCHDOG_TEARDOWN_STARTED | GUAC_CLIENT_WATCHDOG_USERS_GONE,
+ GUAC_CLIENT_WATCHDOG_POLLING_INTERVAL)) {
+ guac_flag_lock(&watchdog->state);
+ }
+
+ /* Teardown may have begun while the lock was being acquired. If it has,
+ * it's no longer safe to dereference the client, and we must stop
+ * monitoring here */
+ if (watchdog->state.value & GUAC_CLIENT_WATCHDOG_TEARDOWN_STARTED) {
+ guac_flag_unlock(&watchdog->state);
+ break;
+ }
+
+ guac_timestamp current = guac_timestamp_current();
+
+ /* Track general operation progress, stopping the client if at least one
+ * operation must have exceeded the timeout */
+ if (watchdog->active_operations == 0
+ || watchdog->total_operation_events != last_operation_events) {
+ last_operation_events = watchdog->total_operation_events;
+ last_progress = current;
+ }
+ else if (current - last_progress >= watchdog->op_timeout) {
+ guac_client_watchdog_stop_connection(watchdog);
+ }
+
+ /* Switch from watching connection behavior to watching termination
+ * progress if any termination condition is met (stop forced/requested
+ * or all users have left) */
+ if (watchdog->stop_requested
+ || watchdog->client->state != GUAC_CLIENT_RUNNING
+ || (watchdog->state.value & GUAC_CLIENT_WATCHDOG_USERS_GONE)) {
+ guac_client_watchdog_notify_teardown_start(watchdog);
+ guac_flag_unlock(&watchdog->state);
+ break;
+ }
+
+ guac_flag_unlock(&watchdog->state);
+
+ }
+
+ /* Teardown must complete within the cleanup timeout */
+ if (guac_flag_timedwait_and_lock(&watchdog->state,
+ GUAC_CLIENT_WATCHDOG_TEARDOWN_ENDED,
+ watchdog->cleanup_timeout)) {
+ guac_flag_unlock(&watchdog->state);
+ return NULL;
+ }
+
+ /* If we reach here, teardown did not complete in time. NOTE: We do not log
+ * here for the reason noted above - logging would mean invoking
+ * guac_client_log() and there's no guarantee that the client has not yet
+ * been freed. */
+ kill(0, SIGKILL);
+
+ /* We should never reach here so long as SIGKILL delivery is possible */
+ _exit(EXIT_FAILURE);
+
+}
+
+guac_client_watchdog* guac_client_watchdog_start(guac_client* client,
+ unsigned int op_timeout, unsigned int cleanup_timeout) {
+
+ /* NOTE: Once started successfully, the watchdog allocated here is never
+ * freed. Its struct continues to be available and its thread continues to
+ * run until the process exits. */
+
+ guac_client_watchdog* watchdog = guac_mem_zalloc(sizeof(guac_client_watchdog));
+ if (watchdog == NULL) {
+ guac_error = GUAC_STATUS_NO_MEMORY;
+ guac_error_message = "Insufficient memory to allocate client watchdog";
+ return NULL;
+ }
+
+ guac_flag_init(&watchdog->state);
+ watchdog->client = client;
+ *((unsigned int*) &watchdog->op_timeout) = op_timeout;
+ *((unsigned int*) &watchdog->cleanup_timeout) = cleanup_timeout;
+
+ pthread_t watchdog_thread;
+ int result = pthread_create(&watchdog_thread, NULL, guac_client_watchdog_thread, watchdog);
+ if (result) {
+ guac_flag_destroy(&watchdog->state);
+ guac_mem_free(watchdog);
+ guac_error = GUAC_STATUS_SEE_ERRNO;
+ guac_error_message = "Unable to start client watchdog thread";
+ errno = result;
+ return NULL;
+ }
+
+ pthread_detach(watchdog_thread);
+
+ client->__watchdog = watchdog;
+ return watchdog;
+
+}
diff --git a/src/libguac/client.c b/src/libguac/client.c
index b2919c44..416fc7a9 100644
--- a/src/libguac/client.c
+++ b/src/libguac/client.c
@@ -173,16 +173,19 @@ static void guac_client_promote_pending_users(guac_client* client) {
/* Run the pending join handler, if one is defined */
if (client->join_pending_handler) {
- /* If an error occurs in the pending handler */
- if(client->join_pending_handler(client)) {
+ guac_client_watchdog_notify_operation_start(client);
+ int join_pending_failed = client->join_pending_handler(client);
+ guac_client_watchdog_notify_operation_end(client);
- /* Log a warning and abort the promotion of the pending users */
+ /* Log a warning and abort the promotion of the pending users if an error
+ * occurs in the pending handler */
+ if (join_pending_failed) {
guac_client_log(client, GUAC_LOG_WARNING,
"join_pending_handler did not successfully complete;"
" any pending users have not been promoted.\n");
-
goto promotion_complete;
}
+
}
/* The first pending user in the list, if any */
@@ -470,8 +473,11 @@ int guac_client_add_user(guac_client* client, guac_user* user, int argc, char**
int retval = 0;
/* Call handler, if defined */
- if (client->join_handler)
+ if (client->join_handler) {
+ guac_client_watchdog_notify_operation_start(client);
retval = client->join_handler(user, argc, argv);
+ guac_client_watchdog_notify_operation_end(client);
+ }
if (retval == 0) {
@@ -526,10 +532,16 @@ void guac_client_remove_user(guac_client* client, guac_user* user) {
guac_client_owner_notify_leave(client, user);
/* Call handler, if defined */
- if (user->leave_handler)
+ if (user->leave_handler) {
+ guac_client_watchdog_notify_operation_start(client);
user->leave_handler(user);
- else if (client->leave_handler)
+ guac_client_watchdog_notify_operation_end(client);
+ }
+ else if (client->leave_handler) {
+ guac_client_watchdog_notify_operation_start(client);
client->leave_handler(user);
+ guac_client_watchdog_notify_operation_end(client);
+ }
}
@@ -641,6 +653,9 @@ int guac_client_end_multiple_frames(guac_client* client, int frames) {
int guac_client_load_plugin(guac_client* client, const char* protocol) {
+ int retval = -1;
+ guac_client_watchdog_notify_operation_start(client);
+
/* Reference to dlopen()'d plugin */
void* client_plugin_handle;
@@ -661,7 +676,7 @@ int guac_client_load_plugin(guac_client* client, const char* protocol) {
sizeof(protocol_lib)) >= sizeof(protocol_lib)) {
guac_error = GUAC_STATUS_NO_MEMORY;
guac_error_message = "Protocol name is too long";
- return -1;
+ goto finished;
}
/* Load client plugin */
@@ -669,7 +684,7 @@ int guac_client_load_plugin(guac_client* client, const char* protocol) {
if (!client_plugin_handle) {
guac_error = GUAC_STATUS_NOT_FOUND;
guac_error_message = dlerror();
- return -1;
+ goto finished;
}
dlerror(); /* Clear errors */
@@ -682,13 +697,17 @@ int guac_client_load_plugin(guac_client* client, const char* protocol) {
guac_error = GUAC_STATUS_INTERNAL_ERROR;
guac_error_message = dlerror();
dlclose(client_plugin_handle);
- return -1;
+ goto finished;
}
/* Init client */
client->__plugin_handle = client_plugin_handle;
- return alias.client_init(client);
+ retval = alias.client_init(client);
+
+finished:
+ guac_client_watchdog_notify_operation_end(client);
+ return retval;
}
diff --git a/src/libguac/guacamole/client-types.h b/src/libguac/guacamole/client-types.h
index 1caa6bcb..c6d642bf 100644
--- a/src/libguac/guacamole/client-types.h
+++ b/src/libguac/guacamole/client-types.h
@@ -34,6 +34,14 @@
*/
typedef struct guac_client guac_client;
+/**
+ * Watchdog guarding resource consumption and termination behavior of
+ * guac_client. The watchdog is intended to guarantee proper cleanup of the
+ * per-connection process and any child processes, as well as to recognize cases
+ * where a connection process has stopped responding and must be terminated.
+ */
+typedef struct guac_client_watchdog guac_client_watchdog;
+
/**
* Possible current states of the Guacamole client. Currently, the only
* two states are GUAC_CLIENT_RUNNING and GUAC_CLIENT_STOPPING.
diff --git a/src/libguac/guacamole/client.h b/src/libguac/guacamole/client.h
index 01ecbc84..27587b64 100644
--- a/src/libguac/guacamole/client.h
+++ b/src/libguac/guacamole/client.h
@@ -311,6 +311,12 @@ struct guac_client {
*/
void* __plugin_handle;
+ /**
+ * The watchdog for this client, or NULL if guac_client_watchdog_start() has
+ * not yet been called.
+ */
+ guac_client_watchdog* __watchdog;
+
};
/**
@@ -393,6 +399,153 @@ void guac_client_abort(guac_client* client, guac_protocol_status status,
void vguac_client_abort(guac_client* client, guac_protocol_status status,
const char* format, va_list ap);
+/**
+ * Allocates and starts the watchdog for the given client. If the watchdog could
+ * not be allocated or started, NULL is returned and guac_error is set
+ * appropriately.
+ *
+ * The watchdog observes the behavior of the connection and guarantees the
+ * connection process cleans itself up in a timely manner upon disconnect. If
+ * the connection process fails to clean up in a timely manner, or stops
+ * responding, it is forcibly killed.
+ *
+ * @important
+ * The client monitored by the watchdog MUST NOT be freed until after
+ * guac_client_watchdog_notify_teardown_start() has been called and
+ * no further users remain connected.
+ *
+ * @see guac_client_watchdog_notify_teardown_start()
+ * @see guac_client_watchdog_await_all_users()
+ *
+ * @param client
+ * The client that the watchdog should observe.
+ *
+ * @param op_timeout
+ * The number of milliseconds allowed for a timed operation before the
+ * connection process is considered unresponsive. Within the scope of client
+ * plugins, and with the sole exception of the free_handler covered by
+ * cleanup_timeout, the handlers of guac_client and guac_user are considered
+ * timed operations.
+ *
+ * @param cleanup_timeout
+ * The maximum number of milliseconds the process may take to clean up
+ * before it is forcibly killed.
+ *
+ * @return
+ * The newly started watchdog, or NULL if the watchdog could not be started.
+ */
+guac_client_watchdog* guac_client_watchdog_start(guac_client* client,
+ unsigned int op_timeout, unsigned int cleanup_timeout);
+
+/**
+ * Requests that the given watchdog terminate the connection it is monitoring.
+ *
+ * @note
+ * This function is intentionally async-signal-safe and may be called at
+ * any time from any thread, even after the relevant guac_client has been
+ * freed.
+ *
+ * @param watchdog
+ * The watchdog of the client whose connection should terminate.
+ */
+void guac_client_watchdog_stop_connection(guac_client_watchdog* watchdog);
+
+/**
+ * Notifies the given watchdog that teardown has begun for the client that the
+ * watchdog is monitoring. Any thread waiting on
+ * guac_client_watchdog_await_teardown() is unblocked. If teardown has already
+ * been notified, this function has no effect.
+ *
+ * @param watchdog
+ * The watchdog of the client whose connection is being torn down.
+ */
+void guac_client_watchdog_notify_teardown_start(guac_client_watchdog* watchdog);
+
+/**
+ * Notifies the given watchdog that teardown has completed for the client that
+ * the watchdog is monitoring. This function may only be called after
+ * guac_client_watchdog_notify_teardown_start().
+ *
+ * @param watchdog
+ * The watchdog of the client whose connection has been torn down.
+ */
+void guac_client_watchdog_notify_teardown_end(guac_client_watchdog* watchdog);
+
+/**
+ * Notifies the watchdog of the given client that a new user is joining the
+ * connection. Once all users of a connection have left, any further attempts to
+ * join the connection are refused. If the client does not have an associated
+ * watchdog, this function has no effect.
+ *
+ * @param client
+ * The client the user has joined.
+ *
+ * @return
+ * Zero if the user may be accepted, non-zero if all users have left and
+ * this user's attempt to join should be refused.
+ */
+int guac_client_watchdog_notify_user_joined(guac_client* client);
+
+/**
+ * Notifies the watchdog of the given client that an existing user is leaving
+ * the connection. This function may be called multiple times per user, but
+ * MUST be called exactly once for each call to
+ * guac_client_watchdog_notify_user_joined(). If the client does not have an
+ * associated watchdog, this function has no effect.
+ *
+ * @param client
+ * The client the user has left.
+ */
+void guac_client_watchdog_notify_user_left(guac_client* client);
+
+/**
+ * Notifies the watchdog of the given client that an operation is beginning that
+ * must complete within a bounded amount of time. The general amount of time
+ * that should be permitted for timed operations is provided when the watchdog
+ * was originally started. Every call must be balanced by a call to
+ * guac_client_watchdog_notify_operation_end(), even if the operation fails.
+ * Calls may safely overlap and nest. If the client does not have an associated
+ * watchdog, this function has no effect.
+ *
+ * @see guac_client_watchdog_start()
+ * @see guac_client_watchdog_notify_operation_end()
+ *
+ * @param client
+ * The client on whose behalf the operation is about to be performed.
+ */
+void guac_client_watchdog_notify_operation_start(guac_client* client);
+
+/**
+ * Notifies the watchdog of the given client that an operation associated with a
+ * call to guac_client_watchdog_notify_operation_start() has completed. If the
+ * client does not have an associated watchdog, this function has no effect.
+ *
+ * @param client
+ * The client on whose behalf the operation was performed.
+ */
+void guac_client_watchdog_notify_operation_end(guac_client* client);
+
+/**
+ * Blocks until teardown of the connection monitored by the given watchdog has
+ * begun, whether because the watchdog observed that the connection terminated
+ * or because guac_client_watchdog_notify_teardown_start() was called.
+ *
+ * @see guac_client_watchdog_notify_teardown_start()
+ *
+ * @param watchdog
+ * The watchdog to wait on.
+ */
+void guac_client_watchdog_await_teardown(guac_client_watchdog* watchdog);
+
+/**
+ * Waits until no users of the connection monitored by the given watchdog remain
+ * connected.
+ *
+ * @param watchdog
+ * The watchdog to wait on.
+ */
+void guac_client_watchdog_await_all_users(guac_client_watchdog* watchdog);
+
/**
* Allocates a new buffer (invisible layer). An arbitrary index is
* automatically assigned if no existing buffer is available for use.
diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c
index 4b79ccae..e3492c44 100644
--- a/src/libguac/user-handshake.c
+++ b/src/libguac/user-handshake.c
@@ -166,8 +166,12 @@ static void* guac_user_input_thread(void* data) {
guac_error_message = NULL;
/* Call handler, stop on error */
- if (__guac_user_call_opcode_handler(__guac_instruction_handler_map,
- user, parser->opcode, parser->argc, parser->argv)) {
+ guac_client_watchdog_notify_operation_start(client);
+ int handler_failed = __guac_user_call_opcode_handler(__guac_instruction_handler_map,
+ user, parser->opcode, parser->argc, parser->argv);
+ guac_client_watchdog_notify_operation_end(client);
+
+ if (handler_failed) {
/* Log error */
guac_user_log_guac_error(user, GUAC_LOG_WARNING,
@@ -269,9 +273,13 @@ static int __guac_user_handshake(guac_user* user, guac_parser* parser,
guac_user_log(user, GUAC_LOG_DEBUG, "Processing instruction: %s",
parser->opcode);
- /* Run instruction handler for opcode with arguments. */
- if (__guac_user_call_opcode_handler(__guac_handshake_handler_map, user,
- parser->opcode, parser->argc, parser->argv)) {
+ /* Run instruction handler for opcode with arguments */
+ guac_client_watchdog_notify_operation_start(user->client);
+ int handler_failed = __guac_user_call_opcode_handler(__guac_handshake_handler_map,
+ user, parser->opcode, parser->argc, parser->argv);
+ guac_client_watchdog_notify_operation_end(user->client);
+
+ if (handler_failed) {
guac_user_log_handshake_failure(user);
guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
@@ -279,7 +287,6 @@ static int __guac_user_handshake(guac_user* user, guac_parser* parser,
guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s",
parser->opcode);
- guac_parser_free(parser);
return 1;
}
@@ -294,9 +301,17 @@ static int __guac_user_handshake(guac_user* user, guac_parser* parser,
int guac_user_handle_connection(guac_user* user, int usec_timeout) {
+ int retval = 1;
+
guac_socket* socket = user->socket;
guac_client* client = user->client;
-
+ guac_parser* parser = NULL;
+
+ /* Immediately report change in user count, denying the attempt if all users
+ * have left */
+ if (guac_client_watchdog_notify_user_joined(client))
+ return 1;
+
user->info.audio_mimetypes = NULL;
user->info.image_mimetypes = NULL;
user->info.video_mimetypes = NULL;
@@ -316,16 +331,14 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
"Error sending \"args\" to new user");
- return 1;
+ goto finished;
}
- guac_parser* parser = guac_parser_alloc();
+ parser = guac_parser_alloc();
/* Perform the handshake with the client. */
- if (__guac_user_handshake(user, parser, usec_timeout)) {
- guac_parser_free(parser);
- return 1;
- }
+ if (__guac_user_handshake(user, parser, usec_timeout))
+ goto finished;
/* Acknowledge connection availability */
guac_protocol_send_ready(socket, client->connection_id);
@@ -335,7 +348,7 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
if (parser->argc != (num_args + 1)) {
guac_client_log(client, GUAC_LOG_ERROR, "Client did not return the "
"expected number of arguments.");
- return 1;
+ goto finished;
}
/* Attempt to join user to connection. */
@@ -378,11 +391,17 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
/* Free name and timezone info. */
guac_mem_free_const(user->info.name);
guac_mem_free_const(user->info.timezone);
-
- guac_parser_free(parser);
/* Successful disconnect */
- return 0;
+ retval = 0;
+
+finished:
+
+ if (parser != NULL)
+ guac_parser_free(parser);
+
+ guac_client_watchdog_notify_user_left(client);
+ return retval;
}