-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Posix: Fix no task switching issue if a task ended its main function #184
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
Posix: Fix no task switching issue if a task ended its main function #184
Conversation
| if ( ev ) | ||
| { | ||
| pthread_mutex_destroy( &ev->mutex ); | ||
| pthread_cond_destroy( &ev->cond ); | ||
| free( ev ); | ||
| *ev_ptr = NULL; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would hide the doube deletion of an event if called twice.
We would like it to fail as soon as possible, as it could make a design issue not apparent by trying to delete a task twice.
This is similar to how free works, if you call free twice on a pointer the second time it would crash instead of checking the validity of that pointer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you are correct. This will hide the double deletion. I added this check to be able to work with the fix in PR #181 when event_delete where called from two places. But now this is not needed after refining the fix of PR #181 by deleting the condition variable in the correct place (When a thread is canceled).
When the main function of a task exits, no task switching happened. This is because all the remaining tasks are waiting on the condition variable. The fix is to trigger a task switch and mark the exiting task as "Dying" to be suspened and exited properly from the scheduler.
f76b9fa to
a4e007e
Compare
portable/ThirdParty/GCC/Posix/port.c
Outdated
| * with any value to trigger a task switch where the task will | ||
| * be suspended and exited. */ | ||
| pxThread->xDying = pdTRUE; | ||
| vTaskDelay( portTASK_ENDED_DELAY ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is better to call taskYIELD() here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will return to the same task when there are no other tasks at a higher or equal priority. So I get stuck again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main issue is the delay
Why was it set to 500 ? would it work with 200? what about 100? etc
Can we make it so it doesn't wait at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The delay can be with any value as I described it in the comment. The task actually will not wait at all. Once the task switching is in place, the task will exit as it is marked as "Dying". The purpose of the delay is to trigger an actual task switching and call prvSwitchThread with different task handles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, I am still confused here. It seems like we are talking about the case where a FreeRTOS task function returns. By design when this happens the stack will be corrupted and the OS will crash, so what are we trying to do here?
I am trying to understand under which circumstances this code is helping us.
In the case above, after 500 ticks the scheduler will schedule your task again, and then you will be in exactly the same place where you would have been had you just called taskYIELD().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A task should call vTaskDelete( NULL ) at its end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RichardBarry The task resources will be freed in the idle task when the task deletes itself. This does not handle the following case:
- Task (x) allocated memory to be used for a new higher priority static task (y).
- Task (x) created a static task (y) and used the allocated memory from the previous step.
- Task (y) scheduled then finished and deleted itself.
- Scheduler returns to Task (x) which was waiting for Task (y) to finish.
- Task (x) frees the allocated resources.
- When The Idle task runs, it will try to free the resources of Task (y) and access a freed memory (and causes a Memory Corruption)
So I proposed to delete Task (y) from the Task(x) (to trigger immediate resources freeing) and handle the task exit properly.
There is a workaround for this issue by adding vTaskDelay in Task (x) (before step 5) to give a chance for the idle task to run and free the resources of Task (y).
@cobusve Yes, I was trying to handle the case where the task function finishes. And It looks like it is not the proper way by design. I got confused because the task is exiting silently in Posix port without assertion or crashes. I will update the PR to just assert in this case to be easy to catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RichardBarry @cobusve any suggestion on how to handle the above scenario?
portable/ThirdParty/GCC/Posix/port.c
Outdated
| pxThread->pxCode( pxThread->pvParams ); | ||
|
|
||
| /* Task function should not return */ | ||
| prvTaskExitError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the normal way to handle asserts is like this https://github.com/FreeRTOS/FreeRTOS/blob/6cc5310f380524c7885181a3be0c60ead4b59a22/FreeRTOS/Demo/Posix_GCC/main.c#L283
That means you probably just have to call configASSERT( pdFALSE ); over here and be done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cobusve This is exactly what prvTaskExitError(); is doing in addition to stop if configASSERT() is not actually configured. This is useful to cover the issue of task ending in FreeRTOS and be easy to catch.
The same function is used in many ports (For example https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/master/portable/GCC/ARM_CM3/port.c#L194)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In some ports it is defined by the demo/application, and in some ports it is defined by the port
in this case this function is defined by the demo application in: https://github.com/FreeRTOS/FreeRTOS/blob/master/FreeRTOS/Demo/Posix_GCC/main.c#L283
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I did not get what is the issue here. If Posix port can be used without defining configASSERT then the stop loop is needed. If Posix port can not be used without defining configASSERT then no need for the stop loop but we need to write this down in some documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://www.freertos.org/a00110.html#configASSERT
As you can see in the doc, config assert is an application side setting not a port side setting, as the user has the option of turning it on or off depending on the maturity of the application in order to save space...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is exactly what I am talking about. So that the stop loop in prvTaskExitError after config assert is important to avoid dead silent stop when config assert is not set in the application side
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the user is confident about his application, and is sure that they called vTaskDelete at the end of every task, extra code and code size is not desired, so vConfigAssert will rightfully be disabled to remove the extra code. By adding the prvTaskExitError function this would not be possible
Worth mentioning, this is a linux port and extra code is not as important, but to stay consistent, with other new ports, be as true as possible to an embedded environment, and provide a good example.
909eee6 to
7f0cad3
Compare
Description
When the main function of a task exits, no task switching happened.
This is because all the remaining tasks are waiting on the condition
variable. The fix is to trigger a task switch and mark the exiting
task as "Dying" to be suspened and exited properly from the scheduler.
Test Steps
Create an application with multi tasks. Make one task to exit normally from its main function. Then no task switching will happen.
Related Issue
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.