Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Interruptible Blocking Statements #1113

Open
moefear85 opened this issue Nov 10, 2023 · 4 comments
Open

[Feature Request] Interruptible Blocking Statements #1113

moefear85 opened this issue Nov 10, 2023 · 4 comments
Labels
enhancement New feature or request

Comments

@moefear85
Copy link

Is your feature request related to a problem? Please describe.
Often I need a thread to block indefinitely on some operation, such as reading from a Queue, but still need to interrupt it when some specific event/scenario occurs, such as closing of resources. Currently such operations can only be unblocked by the queue itself or by a timeout, but not by other tasks bzw external events. This means one is forced to choose an arbitrary timeout period and check for a flag, and such a period is often either too small or too big or together and inefficient.

Describe the solution you'd like
An extra function such as xQueueUnblock(QueueHandle_t) that unblocks any thread waiting to recieve or send from/to the queue, but with a failure return value (or convert the return type into an enum on which the user can place a switch statement to determine which case, whether success, failure, interrupted, etc). It can be extended to all timeout based blocking operations in FreeRTOS.

Describe alternatives you've considered
The only way to effectively achieve the same, would be to add a dummy Queue (of size 1) used solely for the sake of interrupting, and then having the thread that wants to send/receive, to block on the set of Queues, one being the dummy, the other being the real Queue, such that an interrupting thread would place a dummy value (or read a dummy value) to unblock the target thread and use a separate flag variable to signal what happened. This is unnecessarily complicated and clutters up the code.

How many devices will this feature impact?
For me it affects only a few personal projects, but I'm sure it would benefit alot of other people, hobbyists and professionals alike, low and high volume productions.

What are your project timelines?
No timeline.

Additional context
None available

If you have the same (or similar) feature request, please upvote this issue with thumbs up 👍
and use the comments section to provide answers to the questions above.

@moefear85 moefear85 added the enhancement New feature or request label Nov 10, 2023
@cookpate
Copy link
Member

Ignoring the complexity of unblocking all tasks waiting on a Queue, There is a problem with atomicity of this operation. What post-conditions are expected when xQueueUnblock returns a success code to the calling task? If the expectation is that no task indefinitely blocks on the queue, then there is a race condition:

  • If a task is preempted right before calling xQueueSend, then calling xQueueUnblock from the
    task/interrupt fails to prevent the preempted task from blocking.
  • If a task is preempted right after calling xQueueUnblock, then calling xQueueSend from the preempting task indefinitely blocks.

The listed alternative describes a FreeRTOS binary semaphore, a wrapper around a size 1 Queue.
If the task's TCB is known, task notification is a lightweight alternative. Having a variable to determine why a task is signaled is perfectly normal.

If signals are not preferred, then the timing requirements of the system can be defined. For example, if a reader must take at most 1ms to receive from the queue and process the item, and there are 3 writers, then any write to the queue must not block more than 3ms to send onto a If the send times out, then the resource must be unavailable. A writer must have some non-volatile storage so that the message can be enqueued at a later time.

I would recommend opening a thread on the FreeRTOS Forum asking why such a function function is not provided.

@moefear85
Copy link
Author

moefear85 commented Nov 13, 2023

I understand the difficulties some non-professionals experience trying to grapple with concurrent execution. But AFAIK, only one thread at a time can be waiting on a queue anyways. I've already provided an example on how to implement it, and have already created my own nice wrapper to abstract away the details, and called it interruptQueue(...). But the codebase is not mine, so I don't own the copyrights.

If someone is working on a toy project and prefers to shift the complexity off to the user, that's just constantly reinventing the wheel.

I advise you to study the the C++ standard library. Any serious project would attempt to stick to that interface for maximum portability and extend that to new platforms, not reinvent the wheel with a different api, and without the tire profiles (due to its complexity, which is more a matter of skill rather than complexity. Going about it the primitive way of carving the profile out by hand, would of course be very complex, but that's not how modern tyres are fabricated. Don't get stuck on it though, it's just an analogy).

@cookpate
Copy link
Member

cookpate commented Nov 14, 2023

FreeRTOS queues can have multiple readers or multiple writers waiting on them.
https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/ad13a1f8dfeb444dd8a8af52c00814954900dfca/queue.c#L115-L116

    List_t xTasksWaitingToSend;             /**< List of tasks that are blocked waiting to post onto this queue.  Stored in priority order. */
    List_t xTasksWaitingToReceive;          /**< List of tasks that are blocked waiting to read from this queue.  Stored in priority order. */

The C++ concurrency support library provides no faculties for canceling another thread from waiting on a synchronization primitive. The only thing a thread can do to prevent itself from being blocked forever on waiting is to use timeouts (wait_until, wait_for `try_lock

The concurrency support library includes std::condition_variable which allows for the alternative described in the original issue.
A thread can wait for a signal (cv.wait(lock)) and then determine based on an atomic variable whether to continue or cancel.
Another thread can set an atomic variable and/or signal (cv.notify_one() or cv.notify_all())
https://en.cppreference.com/w/cpp/thread/condition_variable

std::atomic<bool> stop{false};
std::mutex mutex;
std::condition_variable signal;

// consumer thread(s)
while( true )
{
  std::unique_lock lock{ mutex };
  signal.wait( lock );
  if( stop.load() ) {
      break;
  }
  std::cout << "Signaled\n";
}

// producer thread(s)
using namespace std::chrono_literals;
while( !stop.load() )
{
    signal.notify_one();
    std::this_thread::wait_for( 100ms );
}

// main thread
using namespace std::chrono_literals;
std::this_thread::wait_for( 5s );
stop.store( true );
signal.notify_all();

@glemco
Copy link
Contributor

glemco commented Dec 13, 2023

Just wondering, as already stated queues can have multiple readers or writers, but stream/messagebuffers cannot. I believe what the OP is requesting could be achieved with a streambuffer by just notifying the waiting thread, this would wake it up as if the timeout triggered in the xTaskNotifyWait used inside the streambuffer.
Is that an expected use-case? Should it be made somehow explicit (e.g. creating a xMessageBufferUnblock doing exactly that)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants