Dear Support,
它看起来我偶然发现了ke_timer_set,触发消息和ke_timer_clear之间的竞争条件(至少它看起来)。
我正在使用下面的计时器,作为10ms的刻度计时器(因此您目睹此种族条件的可能性增加)。
void app_timer_unset(ke_msg_id_t const timer_id,ke_task_id_t const task_id)
{
ke_timer_clear(timer_id,task_id);
}
void my_start_app_timer(void)
{
app_timer_set(MY_APP_TIMER, TASK_APP, 1);
}
void my_stop_app_timer(void)
{
app_timer_unset(my_app_timer,task_app);
}
int my_timer_fired(ke_msg_id_t const msgs,void const * param,ke_task_id_t const dest_id,ke_task_id_t const src_id)
{
if(getBits16(sys_stat_reg,per_is_down))
periph_init();
开关(msgs)
{
case MY_APP_TIMER:
my_app_timer_fired();
break;
default:
break;
}
返回ke_msg_consumed;
}
静态void my_app_timer_fired(void)
{
//do stuff and reschedule timer
app_timer_set(MY_APP_TIMER, TASK_APP, 1);
}
In many occasions, when I call the my_stop_app_timer, the timer keeps running. The documentation on ke_timer_set mentions the following:
//当计时器到期时,将消息发送到作为参数提供的任务,将定时器ID为消息ID。
嗯,这是否意味着我们不能简单地调用Ke_timer_clear,因为消息可能已经在消息队列中?
In that case, the timer does stop but is rescheduled because the message is still processed and hence the timer is rescheduled.
This looks like a classical race-condition to me, the only way I can think of how to solve this is to put a message in the same queue as where the timer expire message is put.
我看过对话框的例子,但只需调用ke_timer_clear,没有特殊的魔法。是我的结论错误,我正在监督某些东西吗?
你完全正确。当计时器触发时,它会向队列发送消息,并清除计时器对象/状态。当邮件位于队列上时,如果调用ke_timer_clear,没有任何情况发生,如果调用ke_timer_active它将返回false。
由于此问题,我们有一些额外的变量,指示计时器是否真正处于活动状态,在处理程序中,我们检查此变量是否为真。如果是真的,请像往常一样进程。如果为false,请丢弃它。
Boolean不会完全解决此问题(除非您将其与其他计数器相结合),请考虑以下方案:
1. a timer is running and is scheduled to go off @ t = 100
2. @ t =100, the message is put in the queue
3. @ t = 100, after step 2, you set a bool is_active = false and call ke_timer_clear
4. @t = 100,在步骤3之后,您将BOOL IS_Active = TRUE设置,并使用相同的TIMER_ID调用ke_timer_set和5min的超时
5. @ T = 100,在步骤4之后,处理队列中的消息。处理程序看到是_active = true,立即处理而不是5分钟后处理!
The easiest solution is to increase a global counter along with the ke_timer_set and ke_timer_clear.
The counter has to be given as a parameter in the timer handler, done by the ke_timer_set.
如果参数计数器==全局计数器然后处理,否则丢弃。
嗯,悲伤的对话框没有给我们将私有指针添加到计时器中的手段,因此我的想法无法完成。
这是否意味着对话will modify their own profiles, like the battery server application so that they work correctly? I would see this as a major bug that affects many places.
你再次正确。我们的逻辑比简单的布尔值得更复杂。但布尔可能适用于大多数情况。
This below is our (what I believe is) a correct solution.
SDK 5有一些新的包装器API,但它具有相同的问题。
stable_timer.h:
#include "ke_timer.h"
/ *
Use these functions instead of ke_timer_set, ke_timer_clear
Use state_variable.is_active to know if the timer is active instead of the ke_timer_active function
The timer handler should look like this:
int handler(...){
if (stable_timer_should_process(&state_variable)) {
// Process as normal
}
return(ke_msg_consumed);
}
* /
typedef struct StableTimerState {
unsigned char is_active: 1;
unsigned short ignore_count:15;
} StableTimerstate;
空白stable_timer_set (ke_msg_id_t const timer_id,ke_task_id_t const task, uint16_t const delay, StableTimerState* state_variable);
bool stable_timer_clear(ke_msg_id_t const timer_id, ke_task_id_t const task, StableTimerState* state_variable);
bool stable_timer_should_process(StableTimerState* state_variable);
stable_timer.c:
#include "stable_timer.h"
空白stable_timer_set (ke_msg_id_t const timer_id,ke_task_id_t const task, uint16_t const delay, StableTimerState* state_variable) {
if (state_variable->is_active && !ke_timer_active(timer_id, task)) {
//它留下了定时器队列并输入了消息队列
nation_variable-> ignore_count ++;
}
ke_timer_set(timer_id,任务,延迟);
state_variable - > is_active = 1;
}
bool stable_timer_clear(ke_msg_id_t const timer_id, ke_task_id_t const task, StableTimerState* state_variable) {
if (state_variable->is_active == 0)
返回false;
if(!ke_timer_active(timer_id,任务)){
//它留下了定时器队列并输入了消息队列
nation_variable-> ignore_count ++;
} else {
ke_timer_clear(timer_id,任务);
}
nats_variable - > is_active = 0;
返回真;
}
bool stable_timer_should_process(StableTimerState* state_variable) {
if(state_variable-> ignore_count> 0){
state_variable->ignore_count--;
返回false;
} else {
nats_variable - > is_active = 0;
返回真;
}
}
此代码适用于单次计时器,而不是重复计时器。'nation_variable - > is_active = 0;'在'stable_timer_shource_process'中杀死解决方案。
你是什么意思?
To make a timer periodic, just restart it in the end of the handler?
是的,真实的,但随着“稳定_timer_should_process”称之为众多次,endy_variable - > is_active设置为零,即使在我称为'stable_timer_clear'之前也是如此。由于第一个语句'如果(event_varibey-> is_active == 0)返回false,后者函数现在永远不会停止计时器。
我从'stable_timer_should_process'中删除了is_active到零的设置,现在它可以在我预期的工作。
And yes, I placed the setting to zero outside of 'stable_timer_should_process' as it is still needed...
据我所知,这也有效。这是一个耻辱,每个开发商都必须自己找到并解决它(我偶然地偶然发现了)。我宁愿在他们的代码中为所有人解决这个问题,以便在未来的未来中占世界其他地方的利益。
我必须在我们已经发布的产品中检查这一点,并在需要时带出辅助。
嗨paul.deboer,
If you are using the timer in the minimum step it may not function properly. If you are setting a timer and you cancel it in the same 10ms you may not be sure if the timer has been triggered or not. The Timer message may be already in the queue waiting for execution while you are trying to clear it. What you could do, is to clear the timer and have a fail-safe mechanism in your callback to “filter out” those calls. In the SDK 5 there is an implementation where when a timer gets canceled the callback of the timer is replaced by an empty callback in case the handler hasn't properly canceled.
谢谢mt_dialog.
我认为你选择了什么步骤并不重要,这是一个时间问题。我会看看SDK5你如何解决它,但在我解释它时,这也不是防水。在队列中处理消息之前的一系列停止和开始相同的计时器仍然是一个问题。
我宁愿在回调中给出的计时器中有私人数据,那么这将是一个简单的修复。回调原型已经拥有它,但根本没有意味着设置它。
I just had a look in the SDK5, app_easy_timer.c and can confirm that this appears to even solve the race-condition in the scenario I sketched. As a pool of timers is used, this fixes the issue. I'm happy that such code exists and will port it to my SDK.
我认为新的Easy Timer API中有一个错误,您在使用“app_easy_timer_modify”功能“app_easy_timer_modify”修改定时器时,您尚未处理此功能。考虑计时器已被放入消息队列但尚未处理时的情况。如果您当时调用app_easy_timer_modify,则不会应用新延迟,但处理程序将很快执行,如果应用程序依赖于新延迟,则可能是一个问题。此外,将使用新的延迟设置新的“幽灵定时器”,该新延迟设置为在火灾时执行与该句柄相关联的回调,因此如果用户在触发前的时间恰好设置新计时器(它已经存在)从ke timer api移动到消息队列中),并且这个新的计时器与上一个“ghost timer”获得相同的手柄,Ghost Timer处理程序将几乎立即执行新的计时器的回调,而是在指定的延迟之后,另一个“幽灵”定时器“已创建,我们返回上一步...
结论:必须对App_easy_timer_modify应用同样的谨慎。
A tip I have is that you can make use of ke_timer_active in app_easy_timer_cancel. If the timer is active, you are sure the message has not yet entered the message queue and can therefore simply set the callback to NULL and ignore the need to send the APP_CANCEL_TIMER message.
An easy way to fix app_easy_timer_modify is to simply make it a wrapper around the two functions app_easy_timer_cancel followed by app_easy_timer and return the (potentially new) handle. The only issue I can see is when there are no more handles for creating the new timer...
Hi Joacimwe,
感谢您的指示,我将您的观察转发给SDK团队,以便看看您的建议。
谢谢mt_dialog.