jackdbus: implement session command queue
This commit is contained in:
parent
b35647d838
commit
0f439c74dd
|
@ -26,6 +26,7 @@
|
|||
#include <string.h>
|
||||
#include <dbus/dbus.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "controller_internal.h"
|
||||
|
@ -287,6 +288,15 @@ jack_controller_stop_server(
|
|||
{
|
||||
int ret;
|
||||
|
||||
pthread_mutex_lock(&controller_ptr->lock);
|
||||
if (!list_empty(&controller_ptr->session_pending_commands))
|
||||
{
|
||||
pthread_mutex_unlock(&controller_ptr->lock);
|
||||
jack_dbus_error(dbus_call_context_ptr, JACK_DBUS_ERROR_GENERIC, "Refusing to stop JACK server because of pending session commands");
|
||||
return false;
|
||||
}
|
||||
pthread_mutex_unlock(&controller_ptr->lock);
|
||||
|
||||
jack_info("Stopping jack server...");
|
||||
|
||||
assert(controller_ptr->started); /* should be ensured by caller */
|
||||
|
@ -507,6 +517,7 @@ void *
|
|||
jack_controller_create(
|
||||
DBusConnection *connection)
|
||||
{
|
||||
int error;
|
||||
struct jack_controller *controller_ptr;
|
||||
const char * address[PARAM_ADDRESS_SIZE];
|
||||
DBusObjectPathVTable vtable =
|
||||
|
@ -523,11 +534,20 @@ jack_controller_create(
|
|||
goto fail;
|
||||
}
|
||||
|
||||
error = pthread_mutex_init(&controller_ptr->lock, NULL);
|
||||
if (error != 0)
|
||||
{
|
||||
jack_error("Failed to initialize mutex. error %d", error);
|
||||
goto fail_free;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&controller_ptr->session_pending_commands);
|
||||
|
||||
controller_ptr->server = jackctl_server_create(on_device_acquire, on_device_release);
|
||||
if (controller_ptr->server == NULL)
|
||||
{
|
||||
jack_error("Failed to create server object");
|
||||
goto fail_free;
|
||||
goto fail_uninit_mutex;
|
||||
}
|
||||
|
||||
controller_ptr->params = jack_params_create(controller_ptr->server);
|
||||
|
@ -585,6 +605,9 @@ fail_destroy_params:
|
|||
fail_destroy_server:
|
||||
jackctl_server_destroy(controller_ptr->server);
|
||||
|
||||
fail_uninit_mutex:
|
||||
pthread_mutex_destroy(&controller_ptr->lock);
|
||||
|
||||
fail_free:
|
||||
free(controller_ptr);
|
||||
|
||||
|
@ -739,13 +762,17 @@ jack_controller_destroy(
|
|||
{
|
||||
if (controller_ptr->started)
|
||||
{
|
||||
jack_controller_stop_server(controller_ptr, NULL);
|
||||
while (!jack_controller_stop_server(controller_ptr, NULL))
|
||||
{
|
||||
jack_info("jack server failed to stop, retrying in 3 seconds...");
|
||||
usleep(3000000);
|
||||
}
|
||||
}
|
||||
|
||||
jack_controller_remove_slave_drivers(controller_ptr);
|
||||
jack_params_destroy(controller_ptr->params);
|
||||
jackctl_server_destroy(controller_ptr->server);
|
||||
|
||||
pthread_mutex_destroy(&controller_ptr->lock);
|
||||
free(controller_ptr);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,69 +30,96 @@
|
|||
#include "jackdbus.h"
|
||||
#include "controller_internal.h"
|
||||
#include "jack/session.h"
|
||||
#include "common/JackError.h"
|
||||
|
||||
#define controller_ptr ((struct jack_controller *)call->context)
|
||||
#define JACK_DBUS_IFACE_NAME "org.jackaudio.SessionManager"
|
||||
|
||||
static
|
||||
void
|
||||
jack_controller_dbus_session_notify(
|
||||
struct jack_dbus_method_call * call)
|
||||
jack_controller_control_send_signal_session_state_changed(
|
||||
jack_session_event_type_t type,
|
||||
const char * target)
|
||||
{
|
||||
const char * target;
|
||||
dbus_uint32_t u32;
|
||||
const char * path;
|
||||
jack_session_event_type_t type;
|
||||
jack_session_command_t * commands;
|
||||
|
||||
u32 = type;
|
||||
if (target == NULL)
|
||||
{
|
||||
target = "";
|
||||
}
|
||||
|
||||
jack_dbus_send_signal(
|
||||
JACK_CONTROLLER_OBJECT_PATH,
|
||||
JACK_DBUS_IFACE_NAME,
|
||||
"StateChanged",
|
||||
DBUS_TYPE_UINT32,
|
||||
&u32,
|
||||
DBUS_TYPE_STRING,
|
||||
&target,
|
||||
DBUS_TYPE_INVALID);
|
||||
}
|
||||
|
||||
static bool start_detached_thread(void * (* start_routine)(void *), void * arg)
|
||||
{
|
||||
int ret;
|
||||
static pthread_attr_t attr;
|
||||
pthread_t tid;
|
||||
|
||||
ret = pthread_attr_init(&attr);
|
||||
if (ret != 0)
|
||||
{
|
||||
jack_error("pthread_attr_init() failed with %d", ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
if (ret != 0)
|
||||
{
|
||||
jack_error("pthread_attr_setdetachstate() failed with %d", ret);
|
||||
goto destroy_attr;
|
||||
}
|
||||
|
||||
ret = pthread_create(&tid, &attr, start_routine, arg);
|
||||
if (ret != 0)
|
||||
{
|
||||
jack_error("pthread_create() failed with %d", ret);
|
||||
goto destroy_attr;
|
||||
}
|
||||
|
||||
jack_log("Detached thread %d created", (int)tid);
|
||||
|
||||
destroy_attr:
|
||||
pthread_attr_destroy(&attr);
|
||||
exit:
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
static void send_session_notify_reply(struct jack_session_pending_command * pending_cmd_ptr, jack_session_command_t * commands)
|
||||
{
|
||||
struct jack_dbus_method_call call;
|
||||
const jack_session_command_t * cmd_ptr;
|
||||
DBusMessageIter top_iter, array_iter, struct_iter;
|
||||
dbus_uint32_t u32;
|
||||
|
||||
if (!jack_dbus_get_method_args(
|
||||
call,
|
||||
DBUS_TYPE_STRING,
|
||||
&target,
|
||||
DBUS_TYPE_UINT32,
|
||||
&u32,
|
||||
DBUS_TYPE_STRING,
|
||||
&path,
|
||||
DBUS_TYPE_INVALID))
|
||||
{
|
||||
/* The method call had invalid arguments meaning that jack_dbus_get_method_args() has constructed an error for us. */
|
||||
goto exit;
|
||||
}
|
||||
/* jack_dbus_error() wants call struct */
|
||||
call.message = pending_cmd_ptr->message;
|
||||
call.connection = pending_cmd_ptr->connection;
|
||||
|
||||
jack_info("Session notify initiated. target='%s', type=%"PRIu32", path='%s'", target, u32, path);
|
||||
|
||||
if (*target == 0)
|
||||
{
|
||||
target = NULL;
|
||||
}
|
||||
|
||||
type = (jack_session_event_type_t)u32;
|
||||
|
||||
if (type != JackSessionSave &&
|
||||
type != JackSessionSaveAndQuit &&
|
||||
type != JackSessionSaveTemplate)
|
||||
{
|
||||
jack_dbus_error(call, JACK_DBUS_ERROR_INVALID_ARGS, "Invalid session event type %" PRIu32, u32);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
commands = jack_session_notify(controller_ptr->client, target, type, path);
|
||||
if (commands == NULL)
|
||||
{
|
||||
jack_dbus_error(call, JACK_DBUS_ERROR_GENERIC, "jack_session_notify() failed");
|
||||
goto exit;
|
||||
jack_dbus_error(&call, JACK_DBUS_ERROR_GENERIC, "jack_session_notify() failed");
|
||||
goto send_reply;
|
||||
}
|
||||
|
||||
jack_info("Session notify complete, commands follow:");
|
||||
|
||||
call->reply = dbus_message_new_method_return(call->message);
|
||||
if (call->reply == NULL)
|
||||
call.reply = dbus_message_new_method_return(pending_cmd_ptr->message);
|
||||
if (call.reply == NULL)
|
||||
{
|
||||
goto oom;
|
||||
}
|
||||
|
||||
dbus_message_iter_init_append(call->reply, &top_iter);
|
||||
dbus_message_iter_init_append(call.reply, &top_iter);
|
||||
|
||||
if (!dbus_message_iter_open_container(&top_iter, DBUS_TYPE_ARRAY, "(sssu)", &array_iter))
|
||||
{
|
||||
|
@ -142,21 +169,180 @@ jack_controller_dbus_session_notify(
|
|||
goto unref;
|
||||
}
|
||||
|
||||
goto free;
|
||||
goto send_reply;
|
||||
|
||||
close_struct:
|
||||
dbus_message_iter_close_container(&array_iter, &struct_iter);
|
||||
close_array:
|
||||
dbus_message_iter_close_container(&top_iter, &array_iter);
|
||||
unref:
|
||||
dbus_message_unref(call->reply);
|
||||
call->reply = NULL;
|
||||
dbus_message_unref(call.reply);
|
||||
goto oom;
|
||||
|
||||
send_reply:
|
||||
if (call.reply != NULL)
|
||||
{
|
||||
if (!dbus_connection_send(pending_cmd_ptr->connection, call.reply, NULL))
|
||||
{
|
||||
jack_error("Ran out of memory trying to queue method return");
|
||||
}
|
||||
|
||||
dbus_connection_flush(pending_cmd_ptr->connection);
|
||||
dbus_message_unref(call.reply);
|
||||
}
|
||||
else
|
||||
{
|
||||
oom:
|
||||
jack_error("Ran out of memory trying to construct method return");
|
||||
free:
|
||||
jack_session_commands_free(commands);
|
||||
exit:
|
||||
return;
|
||||
jack_error("Ran out of memory trying to construct method return");
|
||||
}
|
||||
}
|
||||
|
||||
#define controller_ptr ((struct jack_controller *)context)
|
||||
void * jack_controller_process_session_command_thread(void * context)
|
||||
{
|
||||
struct jack_session_pending_command * pending_cmd_ptr;
|
||||
jack_session_command_t * commands;
|
||||
|
||||
jack_log("jack_controller_process_session_command_thread enter");
|
||||
|
||||
pthread_mutex_lock(&controller_ptr->lock);
|
||||
loop:
|
||||
/* get next command */
|
||||
assert(!list_empty(&controller_ptr->session_pending_commands));
|
||||
pending_cmd_ptr = list_entry(controller_ptr->session_pending_commands.next, struct jack_session_pending_command, siblings);
|
||||
pthread_mutex_unlock(&controller_ptr->lock);
|
||||
|
||||
jack_info("Session notify initiated. target='%s', type=%d, path='%s'", pending_cmd_ptr->target, (int)pending_cmd_ptr->type, pending_cmd_ptr->path);
|
||||
|
||||
jack_controller_control_send_signal_session_state_changed(pending_cmd_ptr->type, pending_cmd_ptr->target);
|
||||
|
||||
commands = jack_session_notify(controller_ptr->client, pending_cmd_ptr->target, pending_cmd_ptr->type, pending_cmd_ptr->path);
|
||||
usleep(5000000);
|
||||
send_session_notify_reply(pending_cmd_ptr, commands);
|
||||
if (commands != NULL)
|
||||
{
|
||||
jack_session_commands_free(commands);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&controller_ptr->lock);
|
||||
|
||||
/* keep state consistent by sending signal after to lock */
|
||||
/* otherwise the main thread may receive not-to-be-queued request and fail */
|
||||
jack_controller_control_send_signal_session_state_changed(0, NULL);
|
||||
|
||||
/* remove the head of the list (queue) */
|
||||
assert(!list_empty(&controller_ptr->session_pending_commands));
|
||||
assert(pending_cmd_ptr == list_entry(controller_ptr->session_pending_commands.next, struct jack_session_pending_command, siblings));
|
||||
list_del(&pending_cmd_ptr->siblings);
|
||||
|
||||
/* command cleanup */
|
||||
dbus_message_unref(pending_cmd_ptr->message);
|
||||
dbus_connection_ref(pending_cmd_ptr->connection);
|
||||
free(pending_cmd_ptr);
|
||||
|
||||
/* If there are more commands, process them. Otherwise - exit the thread */
|
||||
if (!list_empty(&controller_ptr->session_pending_commands))
|
||||
{
|
||||
goto loop;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&controller_ptr->lock);
|
||||
|
||||
jack_log("jack_controller_process_session_command_thread exit");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#undef controller_ptr
|
||||
#define controller_ptr ((struct jack_controller *)call->context)
|
||||
|
||||
static
|
||||
void
|
||||
jack_controller_dbus_session_notify(
|
||||
struct jack_dbus_method_call * call)
|
||||
{
|
||||
dbus_bool_t queue;
|
||||
const char * target;
|
||||
dbus_uint32_t u32;
|
||||
const char * path;
|
||||
jack_session_event_type_t type;
|
||||
struct jack_session_pending_command * cmd_ptr;
|
||||
|
||||
if (!controller_ptr->started)
|
||||
{
|
||||
jack_dbus_only_error(call, JACK_DBUS_ERROR_SERVER_NOT_RUNNING, "Can't execute method '%s' with stopped JACK server", call->method_name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!jack_dbus_get_method_args(
|
||||
call,
|
||||
DBUS_TYPE_BOOLEAN,
|
||||
&queue,
|
||||
DBUS_TYPE_STRING,
|
||||
&target,
|
||||
DBUS_TYPE_UINT32,
|
||||
&u32,
|
||||
DBUS_TYPE_STRING,
|
||||
&path,
|
||||
DBUS_TYPE_INVALID))
|
||||
{
|
||||
/* The method call had invalid arguments meaning that jack_dbus_get_method_args() has constructed an error for us. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (*target == 0)
|
||||
{
|
||||
target = NULL;
|
||||
}
|
||||
|
||||
type = (jack_session_event_type_t)u32;
|
||||
|
||||
if (type != JackSessionSave &&
|
||||
type != JackSessionSaveAndQuit &&
|
||||
type != JackSessionSaveTemplate)
|
||||
{
|
||||
jack_dbus_error(call, JACK_DBUS_ERROR_INVALID_ARGS, "Invalid session event type %" PRIu32, u32);
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&controller_ptr->lock);
|
||||
if (list_empty(&controller_ptr->session_pending_commands))
|
||||
{
|
||||
if (!start_detached_thread(jack_controller_process_session_command_thread, controller_ptr))
|
||||
{
|
||||
jack_dbus_error(call, JACK_DBUS_ERROR_GENERIC, "Cannot start thread to process the command");
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
jack_log("Session notify thread started");
|
||||
}
|
||||
else if (!queue)
|
||||
{
|
||||
jack_dbus_error(call, JACK_DBUS_ERROR_GENERIC, "Busy");
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
cmd_ptr = malloc(sizeof(struct jack_session_pending_command));
|
||||
if (cmd_ptr == NULL)
|
||||
{
|
||||
jack_dbus_error(call, JACK_DBUS_ERROR_GENERIC, "malloc() failed for jack_session_pending_command struct");
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
cmd_ptr->message = dbus_message_ref(call->message);
|
||||
call->message = NULL; /* mark that reply will be sent asynchronously */
|
||||
cmd_ptr->connection = dbus_connection_ref(call->connection);
|
||||
|
||||
/* it is safe to use the retrived pointers because we already made an additional message reference */
|
||||
cmd_ptr->type = type;
|
||||
cmd_ptr->target = target;
|
||||
cmd_ptr->path = path;
|
||||
|
||||
list_add_tail(&cmd_ptr->siblings, &controller_ptr->session_pending_commands);
|
||||
|
||||
jack_log("Session notify scheduled. target='%s', type=%"PRIu32", path='%s'", target, u32, path);
|
||||
|
||||
unlock:
|
||||
pthread_mutex_unlock(&controller_ptr->lock);
|
||||
}
|
||||
|
||||
static
|
||||
|
@ -278,9 +464,60 @@ jack_controller_dbus_has_session_callback(
|
|||
jack_dbus_construct_method_return_single(call, DBUS_TYPE_BOOLEAN, retval);
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
jack_controller_dbus_get_session_state(
|
||||
struct jack_dbus_method_call * call)
|
||||
{
|
||||
DBusMessageIter iter;
|
||||
struct jack_session_pending_command * cmd_ptr;
|
||||
const char * target;
|
||||
dbus_uint32_t type;
|
||||
bool append_failed;
|
||||
|
||||
call->reply = dbus_message_new_method_return(call->message);
|
||||
if (call->reply == NULL)
|
||||
{
|
||||
goto oom;
|
||||
}
|
||||
|
||||
dbus_message_iter_init_append(call->reply, &iter);
|
||||
|
||||
pthread_mutex_lock(&controller_ptr->lock);
|
||||
|
||||
if (list_empty(&controller_ptr->session_pending_commands))
|
||||
{
|
||||
type = 0;
|
||||
target = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd_ptr = list_entry(controller_ptr->session_pending_commands.next, struct jack_session_pending_command, siblings);
|
||||
type = (dbus_uint32_t)cmd_ptr->type;
|
||||
target = cmd_ptr->target;
|
||||
}
|
||||
|
||||
append_failed =
|
||||
!dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &type) ||
|
||||
!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &target);
|
||||
|
||||
pthread_mutex_unlock(&controller_ptr->lock);
|
||||
|
||||
if (!append_failed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbus_message_unref(call->reply);
|
||||
call->reply = NULL;
|
||||
oom:
|
||||
jack_error("Ran out of memory trying to construct method return");
|
||||
}
|
||||
|
||||
#undef controller_ptr
|
||||
|
||||
JACK_DBUS_METHOD_ARGUMENTS_BEGIN(Notify)
|
||||
JACK_DBUS_METHOD_ARGUMENT("queue", DBUS_TYPE_BOOLEAN_AS_STRING, false)
|
||||
JACK_DBUS_METHOD_ARGUMENT("target", DBUS_TYPE_STRING_AS_STRING, false)
|
||||
JACK_DBUS_METHOD_ARGUMENT("type", DBUS_TYPE_UINT32_AS_STRING, false)
|
||||
JACK_DBUS_METHOD_ARGUMENT("path", DBUS_TYPE_STRING_AS_STRING, false)
|
||||
|
@ -307,14 +544,30 @@ JACK_DBUS_METHOD_ARGUMENTS_BEGIN(HasSessionCallback)
|
|||
JACK_DBUS_METHOD_ARGUMENT("has_session_callback", DBUS_TYPE_BOOLEAN_AS_STRING, true)
|
||||
JACK_DBUS_METHOD_ARGUMENTS_END
|
||||
|
||||
JACK_DBUS_METHOD_ARGUMENTS_BEGIN(GetState)
|
||||
JACK_DBUS_METHOD_ARGUMENT("type", DBUS_TYPE_UINT32_AS_STRING, true)
|
||||
JACK_DBUS_METHOD_ARGUMENT("target", DBUS_TYPE_STRING_AS_STRING, true)
|
||||
JACK_DBUS_METHOD_ARGUMENTS_END
|
||||
|
||||
JACK_DBUS_SIGNAL_ARGUMENTS_BEGIN(StateChanged)
|
||||
JACK_DBUS_SIGNAL_ARGUMENT("type", DBUS_TYPE_UINT32_AS_STRING)
|
||||
JACK_DBUS_SIGNAL_ARGUMENT("target", DBUS_TYPE_STRING_AS_STRING)
|
||||
JACK_DBUS_SIGNAL_ARGUMENTS_END
|
||||
|
||||
JACK_DBUS_METHODS_BEGIN
|
||||
JACK_DBUS_METHOD_DESCRIBE(Notify, jack_controller_dbus_session_notify)
|
||||
JACK_DBUS_METHOD_DESCRIBE(GetUuidForClientName, jack_controller_dbus_get_uuid_for_client_name)
|
||||
JACK_DBUS_METHOD_DESCRIBE(GetClientNameByUuid, jack_controller_dbus_get_client_name_by_uuid)
|
||||
JACK_DBUS_METHOD_DESCRIBE(ReserveClientName, jack_controller_dbus_reserve_client_name)
|
||||
JACK_DBUS_METHOD_DESCRIBE(HasSessionCallback, jack_controller_dbus_has_session_callback)
|
||||
JACK_DBUS_METHOD_DESCRIBE(GetState, jack_controller_dbus_get_session_state)
|
||||
JACK_DBUS_METHODS_END
|
||||
|
||||
JACK_DBUS_IFACE_BEGIN(g_jack_controller_iface_session_manager, "org.jackaudio.SessionManager")
|
||||
JACK_DBUS_SIGNALS_BEGIN
|
||||
JACK_DBUS_SIGNAL_DESCRIBE(StateChanged)
|
||||
JACK_DBUS_SIGNALS_END
|
||||
|
||||
JACK_DBUS_IFACE_BEGIN(g_jack_controller_iface_session_manager, JACK_DBUS_IFACE_NAME)
|
||||
JACK_DBUS_IFACE_EXPOSE_METHODS
|
||||
JACK_DBUS_IFACE_EXPOSE_SIGNALS
|
||||
JACK_DBUS_IFACE_END
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "jslist.h"
|
||||
#include "jack/control.h"
|
||||
#include "jack/jack.h"
|
||||
#include "jack/session.h"
|
||||
#include "jackdbus.h"
|
||||
#include "list.h"
|
||||
#include "params.h"
|
||||
|
@ -37,6 +38,16 @@ struct jack_controller_slave_driver
|
|||
bool loaded;
|
||||
};
|
||||
|
||||
struct jack_session_pending_command
|
||||
{
|
||||
struct list_head siblings;
|
||||
DBusConnection * connection;
|
||||
DBusMessage * message;
|
||||
jack_session_event_type_t type;
|
||||
const char * target;
|
||||
const char * path;
|
||||
};
|
||||
|
||||
struct jack_controller
|
||||
{
|
||||
jackctl_server_t *server;
|
||||
|
@ -54,6 +65,9 @@ struct jack_controller
|
|||
union jackctl_parameter_value slave_drivers_vparam_value;
|
||||
|
||||
struct jack_dbus_object_descriptor dbus_descriptor;
|
||||
|
||||
pthread_mutex_t lock;
|
||||
struct list_head session_pending_commands;
|
||||
};
|
||||
|
||||
#define DEFAULT_DRIVER "dummy"
|
||||
|
|
|
@ -105,6 +105,12 @@ void
|
|||
jack_dbus_send_method_return(
|
||||
struct jack_dbus_method_call * call)
|
||||
{
|
||||
if (call->message == NULL)
|
||||
{
|
||||
/* async call */
|
||||
return;
|
||||
}
|
||||
|
||||
if (call->reply)
|
||||
{
|
||||
retry_send:
|
||||
|
|
Loading…
Reference in New Issue