Help with a task that's blocking itself (when it's not supposed to)

I have a bit of a quandary and was wondering if anyone has some suggestions on the best way to solve it. It’s not a bug, per say, but more of a “best practices” question.

I have two tasks running; one handles the serial link and receives characters to be sent over the link in a queue of character bytes. Things that need to send messages out over the link load a string into the queue, and the serial handler reads the characters off the queue and feeds them to the io port; it also handles incoming commands from the user. The other task does stuff and occasionally generates messages to send. Works nicely so far.

In order to avoid message loss, there’s a mutex that controls access to the queue; a task grabs the mutex, writes the message, and then releases the mutex. (I’m using this instead of simply blocking on the queue in order to avoid two tasks interleaving characters in their messages).

The problem occurs because occasionally the serial handler itself needs to send out a string; it does this by getting the mutex and then loading a string onto the queue, just like every other task. IF the queue is already full with another message, the task blocks waiting on the mutex. However, if it blocks, it can’t service the queue and clear it!

Any thoughts on a good solution would be appreciated.

Why does the serial handler need to get mutex and put message in the queue? Since it is the only task accessing the serial, can it not just send it to the port directly? The purpose of mutex and queue, as you mentioned, is to avoid interleaving which cannot happen as long as the serial handler task ensures to send its message completely before picking the next message from the queue.

Thanks.

Mostly because I wanted to use a common interface throughout. The user-interface task calls the same routine to load a string into the queue as the other task; the mutex acquisition and release is done in that routine.

I believe that this is the flaw in your control flow. What each task that need to post a message should do is instead of claiming the mutex, post a message to a mailbox. Your serial task should simply query the mail box with a 0 timeout; if there has been a mail, process it.

Your serial service task is then free to post to the mailbox itself.

Make the mutex recursive. A recursive mutex can be freely taken by the task that has it, and is actually y given back when as many gives as takes have been done.

I think your serial handler task needs to poll for the mutex not block. That is, when it wants to send something it tries to get the mutex with zero wait time - if it doesn’t get the mutex then it should read from the queue with a small wait time (not forever) so that when the sending task releases the mutex your serial handler task will timeout on the queue and be able to get the mutex. I think having a single task write and read the same queue is a bit clumsy but I understand why you are doing it and I think it is workable.

1 Like

Reading you problem more closely, maboytim is right, the task that is draining a queue should not block to get access to add to the queue to send. (This is one reason timer callback Ned to be careful when adjusting timers, they can create a similar deadlock)

One big point, why can’t the serial ISR just take from the queue. I hope you ARE using an ISR for this I/O and not just spin waiting on the serial port.