Skip to content

Commit bf79332

Browse files
authored
[tasklet] fix Unpost() behavior during tasklet processing (openthread#13039)
This commit fixes an issue where a tasklet could not be successfully unposted if it was already scheduled for execution in the current event loop iteration. Previously, `Scheduler::ProcessQueuedTasklets()` copied and cleared the queued tasklets before running them. If a running tasklet called `Unpost()` on another tasklet that was also in the copied list, the unpost operation would fail to remove it because it only checked the main queue. To address this, the `Scheduler` now explicitly maintains two separate queues: `mPostedQueue` and `mRuningQueue`. The `Tasklet::Unpost()` method is updated to remove the target tasklet from both queues, ensuring it is correctly dequeued even if it is pending in the running list. The queue logic is encapsulated into a nested `Queue` class to manage the circular singly linked-list operations cleanly. Additionally, unit tests are expanded to cover scenarios where tasklets post or unpost other tasklets during execution.
1 parent d0cab9a commit bf79332

3 files changed

Lines changed: 303 additions & 41 deletions

File tree

src/core/common/tasklet.cpp

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,17 @@ void Tasklet::Post(void)
4242
{
4343
if (!IsPosted())
4444
{
45-
Get<Scheduler>().PostTasklet(*this);
45+
Get<Scheduler>().mPostedQueue.PostTasklet(*this);
4646
}
4747
}
4848

4949
void Tasklet::Unpost(void)
5050
{
51-
if (IsPosted())
52-
{
53-
Get<Scheduler>().RemoveTasklet(*this);
54-
}
51+
Get<Scheduler>().mPostedQueue.RemoveTasklet(*this);
52+
Get<Scheduler>().mRunningQueue.RemoveTasklet(*this);
5553
}
5654

57-
void Tasklet::Scheduler::PostTasklet(Tasklet &aTasklet)
55+
void Tasklet::Scheduler::Queue::PostTasklet(Tasklet &aTasklet)
5856
{
5957
// Tasklets are saved in a circular singly linked list.
6058

@@ -72,13 +70,24 @@ void Tasklet::Scheduler::PostTasklet(Tasklet &aTasklet)
7270
}
7371
}
7472

75-
void Tasklet::Scheduler::RemoveTasklet(Tasklet &aTasklet)
73+
void Tasklet::Scheduler::Queue::RemoveTasklet(Tasklet &aTasklet)
7674
{
77-
Tasklet *prev = mTail;
75+
Tasklet *prev;
76+
77+
VerifyOrExit(aTasklet.IsPosted());
78+
79+
VerifyOrExit(!IsEmpty());
80+
81+
prev = mTail;
7882

7983
while (prev->mNext != &aTasklet)
8084
{
8185
prev = prev->mNext;
86+
87+
if (prev == mTail)
88+
{
89+
ExitNow();
90+
}
8291
}
8392

8493
prev->mNext = aTasklet.mNext;
@@ -88,34 +97,42 @@ void Tasklet::Scheduler::RemoveTasklet(Tasklet &aTasklet)
8897
{
8998
mTail = (prev != &aTasklet) ? prev : nullptr;
9099
}
100+
101+
exit:
102+
return;
91103
}
92104

93-
void Tasklet::Scheduler::ProcessQueuedTasklets(void)
105+
Tasklet *Tasklet::Scheduler::Queue::PopTasklet(void)
94106
{
95-
Tasklet *tail = mTail;
107+
Tasklet *tasklet;
96108

97-
// This method processes all tasklets queued when this is called. We
98-
// keep a copy the current list and then clear the main list by
99-
// setting `mTail` to `nullptr`. A newly posted tasklet while
100-
// processing the currently queued tasklets will then trigger a call
101-
// to `otTaskletsSignalPending()`.
109+
if (IsEmpty())
110+
{
111+
tasklet = nullptr;
112+
}
113+
else
114+
{
115+
tasklet = mTail->mNext;
116+
RemoveTasklet(*tasklet);
117+
}
102118

103-
mTail = nullptr;
119+
return tasklet;
120+
}
104121

105-
while (tail != nullptr)
106-
{
107-
Tasklet *tasklet = tail->mNext;
122+
void Tasklet::Scheduler::ProcessQueuedTasklets(void)
123+
{
124+
Tasklet *tasklet;
108125

109-
if (tasklet == tail)
110-
{
111-
tail = nullptr;
112-
}
113-
else
114-
{
115-
tail->mNext = tasklet->mNext;
116-
}
126+
// We transfer all currently posted tasklets to the `mRunningQueue` and
127+
// clear the `mPostedQueue`. This ensures that any new tasklet posted
128+
// while we are processing `mRunningQueue` will be added to `mPostedQueue`
129+
// and will trigger a call to `otTaskletsSignalPending()`.
130+
131+
mRunningQueue = mPostedQueue;
132+
mPostedQueue.Clear();
117133

118-
tasklet->mNext = nullptr;
134+
while ((tasklet = mRunningQueue.PopTasklet()) != nullptr)
135+
{
119136
tasklet->RunTask();
120137
}
121138
}

src/core/common/tasklet.hpp

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,32 +68,40 @@ class Tasklet : public InstanceLocator
6868
friend class Tasklet;
6969

7070
public:
71-
/**
72-
* Initializes the object.
73-
*/
74-
Scheduler(void)
75-
: mTail(nullptr)
76-
{
77-
}
78-
7971
/**
8072
* Indicates whether or not there are tasklets pending.
8173
*
8274
* @retval TRUE If there are tasklets pending.
8375
* @retval FALSE If there are no tasklets pending.
8476
*/
85-
bool AreTaskletsPending(void) const { return mTail != nullptr; }
77+
bool AreTaskletsPending(void) const { return !mPostedQueue.IsEmpty(); }
8678

8779
/**
8880
* Processes all tasklets queued when this is called.
8981
*/
9082
void ProcessQueuedTasklets(void);
9183

9284
private:
93-
void PostTasklet(Tasklet &aTasklet);
94-
void RemoveTasklet(Tasklet &aTasklet);
95-
96-
Tasklet *mTail; // A circular singly linked-list
85+
class Queue // A circular singly linked-list
86+
{
87+
public:
88+
Queue(void)
89+
: mTail(nullptr)
90+
{
91+
}
92+
93+
void Clear(void) { mTail = nullptr; }
94+
bool IsEmpty(void) const { return (mTail == nullptr); }
95+
void PostTasklet(Tasklet &aTasklet);
96+
void RemoveTasklet(Tasklet &aTasklet);
97+
Tasklet *PopTasklet(void);
98+
99+
private:
100+
Tasklet *mTail;
101+
};
102+
103+
Queue mPostedQueue;
104+
Queue mRunningQueue;
97105
};
98106

99107
/**

0 commit comments

Comments
 (0)