亲爱的支持,
it appears I have stumbled upon a race condition (at least it looks so) between the ke_timer_set, the triggered message and the ke_timer_clear.
I'm using the timer as below, as a 10ms tick timer (hence the chance that you witness this race-condition increases).
空白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 msgid, 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();
switch (msgid)
{
案例my_app_timer:
my_app_timer_fired();
休息;
默认:
休息;
}
return KE_MSG_CONSUMED;
}
static void my_app_timer_fired(void)
{
//做的东西和重新安排计时器
app_timer_set(my_app_timer,task_app,1);
}
在许多场合,当我调用my_stop_app_timer时,计时器会导致运行。KE_TIMER_SET提出的文档以下内容:
//When the timer expires, a message is sent to the task provided as argument, with the timer id as message id.
Hmm, does this means that we cannot simply call ke_timer_clear, as a message could already be in the message queue?
在这种情况下,定时器确实停止但被重新安排,因为仍然处理消息,因此重新安排计时器。
这看起来像我的古典竞赛条件,我可以想到如何解决这一点的唯一方法是将一个消息放在与定时器到期消息的位置相同的队列中。
I've looked at Dialogs examples but all simply call ke_timer_clear, no special magic around that. Is my conclusion wrong and am I overseeing something?
你完全正确。什么时候the timer triggers, it sends a message to the queue and clears the timer object/status. When the message is on the queue, if you call ke_timer_clear nothing will happen and if you call ke_timer_active it will return false.
Due to this problem, we have some extra variables indicating whether the timer really is active or not and in the handler we check if this variable is true. If it is true, process as usual. If false, discard it.
A boolean will not fully fix this (unless you combine that with an additional counter), think of the following scenario:
1.计时器正在运行,并计划下车@ t = 100
2. @ T = 100,邮件放入队列中
3. @ T = 100,在步骤2之后,您将BOOL IS_Active = false设置为false并调用ke_timer_clear
4. @t = 100, after step 3, you set a bool is_active = true and call ke_timer_set with the same timer_id and a timeout of 5min
5. @ t= 100, after step 4, the message in the queue is processed. The handler sees is_active = true, is processed immediatly instead of after 5min!
最简单的解决方案是将全局计数器与KE_TIMER_SET和KE_TIMER_CLEAR增加。
必须将计数器作为计时器处理程序中的参数给出,由ke_timer_set完成。
If parameter counter == global counter then process, otherwise discard.
嗯,悲伤的对话没有given us the means to add a private pointer into a timer, so my idea simply cannot be done.
这是否意味着对话框将修改自己的配置文件,如电池服务器应用程序,以便它们正常工作?我会将其视为影响许多地方的主要错误。
You are again correct. Our logic is a bit more complicated than a simple boolean. But a boolean probably works for most cases.
下面是我们的(我相信的是)一个正确的解决方案。
SDK 5 has some new wrapper API but it has the same problems.
stable_timer.h:
#include“ke_timer.h”
/ *
使用这些函数而不是ke_timer_set,ke_timer_clear
使用state_variable.is_active以知道计时器是否处于活动状态而不是ke_timer_active函数
定时器处理程序应该如下所示:
int handler(...) {
if(stable_timer_should_process(&nats_variable)){
//像正常的过程
}
return (KE_MSG_CONSUMED);
}
*/
typedef结构stabletimerstate {
unsigned char is_active:1;
unsigned short ignore_count: 15;
}StableTimerState;
void 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任务,stabletimerstate * nation_variable);
bool stable_timer_should_process(stabletiferstate * nation_variable);
stable_timer.c:
#include“stable_timer.h”
void stable_timer_set(ke_msg_id_t const timer_id,ke_task_id_t const任务,uint16_t const延迟,stabletimerstate * nation_variable){
if(nats_variable - > is_active &&!ke_timer_active(timer_id,任务)){
// It has left the timer queue and entered the message queue
state_variable->ignore_count++;
}
ke_timer_set(timer_id, task, delay);
state_variable->is_active = 1;
}
bool stable_timer_clear(ke_msg_id_t const timer_id,ke_task_id_t const任务,stabletimerstate * nation_variable){
if(nation_variable - > is_active == 0)
return false;
if (!ke_timer_active(timer_id, task)) {
// It has left the timer queue and entered the message queue
state_variable->ignore_count++;
} 别的 {
ke_timer_clear(timer_id, task);
}
state_variable->is_active = 0;
return true;
}
bool stable_timer_should_process(stablediferstate * nation_variable){
if (state_variable->ignore_count > 0) {
equend_variable-> ignore_count--;
return false;
} 别的 {
state_variable->is_active = 0;
return true;
}
}
This code works fine for a one-shot timer, not for a repeating timer. The 'state_variable->is_active = 0;' in 'stable_timer_should_process' kills the solution.
How do you mean?
要进行计时器定期,只需在处理程序的末尾重新启动它?
是的,真实,但作为'稳定_timer_should_process' is called numerous times, the state_variable->is_active is set to zero, even before I have called the 'stable_timer_clear'. The latter function now never stops the timer anymore due to the first statement 'if (state_variable->is_active == 0) return false;'
I removed the setting of is_active to zero from the 'stable_timer_should_process' and now it works as I expected.
是的,我将设置放在“稳定_timer_should_process”之外的零,因为它仍然需要...
As far as I can see, this also works. It's a shame that every developer has to find out by himself and solve it (I stumbled on it by accident). I'd rather have Dialog fix this once and for all in their code so that the rest of the world benefit in the coming future.
I'll have to check the impact of this in our already released product and bring out a SUOTA if needed.
Hi paul.deboer,
如果您在最小步骤中使用定时器,则可能无法正常运行。如果要设置计时器,并且在相同的10ms中取消它可能无法确定计时器是否已被触发。定时器消息可能已经在等待执行的队列中,同时尝试清除它。您可以做的是,要清除计时器,并在回调中有一个故障安全机制,以“过滤掉”这些呼叫。在SDK 5中,存在一个实现,其中当定时器被取消时,在处理程序未正确取消的情况下,计时器的回调被空回调替换。
Thanks MT_dialog
我认为你选择了什么步骤并不重要,这是一个时间问题。我会看看SDK5你如何解决它,但在我解释它时,这也不是防水。在队列中处理消息之前的一系列停止和开始相同的计时器仍然是一个问题。
I'd rather have had private data in the timer that was given back in the callback, then this would have been an easy fix. The callback prototype already has it, but there is simply no mean to set it.
我只是看看SDK5,App_easy_timer.c,并且可以确认这似乎甚至在我勾勒出的场景中解决了比赛条件。作为使用的计时器池,这修复了这个问题。我很高兴这样的代码存在,并将它移植到我的SDK。
I think there is a bug in the new easy timer API that you have not taken care of this when modifying a timer with the function "app_easy_timer_modify". Consider the case when the timer has been put in the message queue but not yet been handled. If you at that time call app_easy_timer_modify, the new delay will not be applied but the handler will rather execute shortly, which might be a problem if the app relies on the new delay. Also, a new "ghost timer" will be set with the new delay that is set to execute the callback associated with that handle when it fires, so if the user sets a new timer at exactly the time before it fires (when it has been moved from the ke timer api into the message queue), and this new timer gets the same handle as the previous "ghost timer", the ghost timer handler will execute the new timer's callback almost immediately instead after the specified delay, and another "ghost timer" has been created and we are back to the previous step...
Conclusion: the same carefullness must be applied to app_easy_timer_modify.
提示我可以在app_easy_timer_cancel中使用ke_timer_active。如果计时器处于活动状态,则确保邮件尚未输入消息队列,因此可以将回调设置为NULL并忽略发送APP_CANCEL_TIMER消息的需要。
一个简单的方法来修复app_easy_timer_modify是简单地使它围绕两个函数app_easy_timer_cancel围绕app_easy_timer返回(潜在的新)句柄。我可以看到的唯一问题是当没有更多的句柄来创建新的计时器...
嗨Joacimwe,
Thanks for the indication, i forwarded your observation to the SDK team in order to have a look at your suggestion.
Thanks MT_dialog