From: Vinicius Costa Gomes <vinicius.gomes@openbossa.org>
To: Ido Yariv <ido@wizery.com>
Cc: linux-bluetooth@vger.kernel.org
Subject: Re: [PATCH] gattrib: Fix a request/response command deadlock
Date: Mon, 28 May 2012 16:45:06 -0300 [thread overview]
Message-ID: <20120528194505.GA6742@samus> (raw)
In-Reply-To: <1338230071-27599-1-git-send-email-ido@wizery.com>
Hi Ido,
On 21:34 Mon 28 May, Ido Yariv wrote:
> New requests and responses are never sent if a request was sent and the
> response for it hasn't been received yet. As a result, if both end
> points send requests at the same time, a deadlock could occur. This
> could happen, for instance, if the client sends a read request and the
> server sends an indication before responding to the read request.
>
> Fix this by introducing an additional queue for responses. Responses may
> be sent while there's still a pending request/indication.
> ---
> attrib/gattrib.c | 86 +++++++++++++++++++++++++++++++++++++++++++----------
> 1 files changed, 69 insertions(+), 17 deletions(-)
>
> diff --git a/attrib/gattrib.c b/attrib/gattrib.c
> index 769be36..769d746 100644
> --- a/attrib/gattrib.c
> +++ b/attrib/gattrib.c
> @@ -47,6 +47,7 @@ struct _GAttrib {
> guint write_watch;
> guint timeout_watch;
> GQueue *queue;
> + GQueue *response_queue;
I would change the name of the other queue as well, to make it clear
that the two queues have different objectives.
> GSList *events;
> guint next_cmd_id;
> guint next_evt_id;
> @@ -175,9 +176,13 @@ static void attrib_destroy(GAttrib *attrib)
>
> while ((c = g_queue_pop_head(attrib->queue)))
> command_destroy(c);
I would add a empty line here.
> + while ((c = g_queue_pop_head(attrib->response_queue)))
> + command_destroy(c);
>
> g_queue_free(attrib->queue);
> attrib->queue = NULL;
And here.
> + g_queue_free(attrib->response_queue);
> + attrib->response_queue = NULL;
>
> for (l = attrib->events; l; l = l->next)
> event_destroy(l->data);
> @@ -259,21 +264,34 @@ static gboolean can_write_data(GIOChannel *io, GIOCondition cond,
> GError *gerr = NULL;
> gsize len;
> GIOStatus iostat;
> + GQueue *queue;
>
> if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL))
> return FALSE;
>
> - cmd = g_queue_peek_head(attrib->queue);
> + queue = attrib->response_queue;
> + cmd = g_queue_peek_head(queue);
> + if (cmd == NULL) {
> + queue = attrib->queue;
> + cmd = g_queue_peek_head(queue);
> + }
> if (cmd == NULL)
> return FALSE;
>
> + /*
> + * Verify that we didn't already send this command. This can only
> + * happen with elementes from attrib->queue.
> + */
> + if (cmd->sent)
> + return FALSE;
> +
> iostat = g_io_channel_write_chars(io, (gchar *) cmd->pdu, cmd->len,
> &len, &gerr);
> if (iostat != G_IO_STATUS_NORMAL)
> return FALSE;
>
> if (cmd->expected == 0) {
> - g_queue_pop_head(attrib->queue);
> + g_queue_pop_head(queue);
> command_destroy(cmd);
>
> return TRUE;
> @@ -315,7 +333,7 @@ static gboolean received_data(GIOChannel *io, GIOCondition cond, gpointer data)
> uint8_t buf[512], status;
> gsize len;
> GIOStatus iostat;
> - gboolean qempty;
> + gboolean qempty, respqempty;
>
> if (attrib->timeout_watch > 0) {
> g_source_remove(attrib->timeout_watch);
> @@ -369,6 +387,8 @@ static gboolean received_data(GIOChannel *io, GIOCondition cond, gpointer data)
>
> done:
> qempty = attrib->queue == NULL || g_queue_is_empty(attrib->queue);
> + respqempty = attrib->response_queue == NULL ||
> + g_queue_is_empty(attrib->response_queue);
>
> if (cmd) {
> if (cmd->func)
> @@ -377,7 +397,7 @@ done:
> command_destroy(cmd);
> }
>
> - if (!qempty)
> + if (!qempty || !respqempty)
> wake_up_sender(attrib);
>
> return TRUE;
> @@ -397,6 +417,7 @@ GAttrib *g_attrib_new(GIOChannel *io)
>
> attrib->io = g_io_channel_ref(io);
> attrib->queue = g_queue_new();
> + attrib->response_queue = g_queue_new();
>
> attrib->read_watch = g_io_add_watch(attrib->io,
> G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
> @@ -421,6 +442,7 @@ guint g_attrib_send(GAttrib *attrib, guint id, guint8 opcode,
> gpointer user_data, GDestroyNotify notify)
> {
> struct command *c;
> + GQueue *queue;
>
> c = g_try_new0(struct command, 1);
> if (c == NULL)
> @@ -435,15 +457,25 @@ guint g_attrib_send(GAttrib *attrib, guint id, guint8 opcode,
> c->user_data = user_data;
> c->notify = notify;
>
> + if (is_response(opcode))
> + queue = attrib->response_queue;
> + else
> + queue = attrib->queue;
> +
> if (id) {
> c->id = id;
> - g_queue_push_head(attrib->queue, c);
> + g_queue_push_head(queue, c);
I don't know if this is the right thing to do for responses. For
requests there's is no problem if I rearrange them, but if I rearrange
responses I guess that it would confuse the remote side.
> } else {
> c->id = ++attrib->next_cmd_id;
> - g_queue_push_tail(attrib->queue, c);
> + g_queue_push_tail(queue, c);
> }
>
> - if (g_queue_get_length(attrib->queue) == 1)
> + /*
> + * If a command was added to the queue and it was empty before, wake up
> + * the sender. If the sender was already woken up by the second queue,
> + * wake_up_sender will just return.
> + */
> + if (g_queue_get_length(queue) == 1)
> wake_up_sender(attrib);
>
> return c->id;
> @@ -459,38 +491,49 @@ static gint command_cmp_by_id(gconstpointer a, gconstpointer b)
>
> gboolean g_attrib_cancel(GAttrib *attrib, guint id)
> {
> - GList *l;
> + GList *l = NULL;
> struct command *cmd;
> + GQueue *queue;
>
> - if (attrib == NULL || attrib->queue == NULL)
> + if (attrib == NULL)
> return FALSE;
>
> - l = g_queue_find_custom(attrib->queue, GUINT_TO_POINTER(id),
> - command_cmp_by_id);
> + queue = attrib->queue;
> + if (queue)
> + l = g_queue_find_custom(queue, GUINT_TO_POINTER(id),
> + command_cmp_by_id);
> + if (l == NULL) {
> + queue = attrib->response_queue;
> + if (!queue)
> + return FALSE;
> + l = g_queue_find_custom(queue, GUINT_TO_POINTER(id),
> + command_cmp_by_id);
> + }
> +
> if (l == NULL)
> return FALSE;
>
> cmd = l->data;
>
> - if (cmd == g_queue_peek_head(attrib->queue) && cmd->sent)
> + if (cmd == g_queue_peek_head(queue) && cmd->sent)
> cmd->func = NULL;
> else {
> - g_queue_remove(attrib->queue, cmd);
> + g_queue_remove(queue, cmd);
> command_destroy(cmd);
> }
>
> return TRUE;
> }
>
> -gboolean g_attrib_cancel_all(GAttrib *attrib)
> +static gboolean g_attrib_cancel_all_per_queue(GQueue *queue)
I don't think that the g_attrib_ prefix here adds much, if that function
is not exported.
> {
> struct command *c, *head = NULL;
> gboolean first = TRUE;
>
> - if (attrib == NULL || attrib->queue == NULL)
> + if (queue == NULL)
> return FALSE;
>
> - while ((c = g_queue_pop_head(attrib->queue))) {
> + while ((c = g_queue_pop_head(queue))) {
> if (first && c->sent) {
> /* If the command was sent ignore its callback ... */
> c->func = NULL;
> @@ -504,12 +547,21 @@ gboolean g_attrib_cancel_all(GAttrib *attrib)
>
> if (head) {
> /* ... and put it back in the queue */
> - g_queue_push_head(attrib->queue, head);
> + g_queue_push_head(queue, head);
> }
>
> return TRUE;
> }
>
> +gboolean g_attrib_cancel_all(GAttrib *attrib)
> +{
> + if (attrib == NULL)
> + return FALSE;
> +
> + return g_attrib_cancel_all_per_queue(attrib->queue) &&
> + g_attrib_cancel_all_per_queue(attrib->response_queue);
> +}
> +
> gboolean g_attrib_set_debug(GAttrib *attrib,
> GAttribDebugFunc func, gpointer user_data)
> {
Other than that, patch looks good.
Just for information, some time ago I did something similar, but I never
felt sure enough about it, but I guess that I was not that far off, in case
you want to take a look:
http://git.infradead.org/users/vcgomes/bluez.git/commitdiff/refs/heads/two-queues
> --
> 1.7.7.6
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
Cheers,
--
Vinicius
next prev parent reply other threads:[~2012-05-28 19:45 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-05-28 18:34 [PATCH] gattrib: Fix a request/response command deadlock Ido Yariv
2012-05-28 19:45 ` Vinicius Costa Gomes [this message]
2012-05-29 1:16 ` Ido Yariv
2012-05-29 1:20 ` [PATCH v2] " Ido Yariv
2012-05-29 2:40 ` Vinicius Costa Gomes
2012-05-29 8:39 ` [PATCH v3] " Ido Yariv
2012-05-29 13:59 ` Vinicius Costa Gomes
2012-05-30 7:42 ` Johan Hedberg
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20120528194505.GA6742@samus \
--to=vinicius.gomes@openbossa.org \
--cc=ido@wizery.com \
--cc=linux-bluetooth@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.