Skip to content

Commit 6e51ef0

Browse files
dok-netdevyte
authored andcommitted
Wakeup delayed scheduling (#6485)
* Fix for scheduling recurrent functions while inside scheduled function * Check that fn are valid. Invoking invalid functions throws otherwise. * Added wakeup token to scheduler. Toggling the token value breaks a scheduled function out from a delayed execution and makes it run on the next scheduler iteration. * Timer reset reliability fix. * Shrink interrupts-locked regions. Add check for periodic yield to scheduled functions run-loop. * Ordered, more predictable, scheduling. Before, it had different ordering compared to FastScheduler as well as sequential calls from loop(). * Optional, for the paranoid: revert changes to (non-recurrent) schedule_function() / run_scheduled_functions(). * Comment * Adapt one-line ifs to general style in same source file. * Fix wakeupToken handling - don't respond to toggle, but to different value vs. that at registering function with scheduler. * Reword comment. * Putting aside std::atomic concerns, use a callback for scheduler alarming. In the future, async future's .then() might take advantage of this direction. * Drop atomic include, align function type syntax. * Reduce flash use. * Prefer const ref over call by value plus std::move().
1 parent e4c6a7a commit 6e51ef0

File tree

2 files changed

+61
-26
lines changed

2 files changed

+61
-26
lines changed

cores/esp8266/Schedule.cpp

+55-20
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,18 @@ struct recurrent_fn_t
2424
recurrent_fn_t* mNext = nullptr;
2525
mRecFuncT mFunc;
2626
esp8266::polledTimeout::periodicFastUs callNow;
27-
recurrent_fn_t (esp8266::polledTimeout::periodicFastUs interval): callNow(interval) { }
27+
std::function<bool(void)> alarm = nullptr;
28+
recurrent_fn_t(esp8266::polledTimeout::periodicFastUs interval) : callNow(interval) { }
2829
};
2930

30-
static recurrent_fn_t* rFirst = nullptr; // fifo not needed
31+
static recurrent_fn_t* rFirst = nullptr;
32+
static recurrent_fn_t* rLast = nullptr;
3133

3234
// Returns a pointer to an unused sched_fn_t,
3335
// or if none are available allocates a new one,
3436
// or nullptr if limit is reached
3537
IRAM_ATTR // called from ISR
36-
static scheduled_fn_t* get_fn_unsafe ()
38+
static scheduled_fn_t* get_fn_unsafe()
3739
{
3840
scheduled_fn_t* result = nullptr;
3941
// try to get an item from unused items list
@@ -52,16 +54,19 @@ static scheduled_fn_t* get_fn_unsafe ()
5254
return result;
5355
}
5456

55-
static void recycle_fn_unsafe (scheduled_fn_t* fn)
57+
static void recycle_fn_unsafe(scheduled_fn_t* fn)
5658
{
5759
fn->mFunc = nullptr; // special overload in c++ std lib
5860
fn->mNext = sUnused;
5961
sUnused = fn;
6062
}
6163

6264
IRAM_ATTR // (not only) called from ISR
63-
bool schedule_function (const std::function<void(void)>& fn)
65+
bool schedule_function(const std::function<void(void)>& fn)
6466
{
67+
if (!fn)
68+
return false;
69+
6570
esp8266::InterruptLock lockAllInterruptsInThisScope;
6671

6772
scheduled_fn_t* item = get_fn_unsafe();
@@ -80,27 +85,37 @@ bool schedule_function (const std::function<void(void)>& fn)
8085
return true;
8186
}
8287

83-
bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us)
88+
bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
89+
uint32_t repeat_us, const std::function<bool(void)>& alarm)
8490
{
8591
assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
8692

87-
esp8266::InterruptLock lockAllInterruptsInThisScope;
93+
if (!fn)
94+
return false;
8895

8996
recurrent_fn_t* item = new (std::nothrow) recurrent_fn_t(repeat_us);
9097
if (!item)
9198
return false;
9299

93100
item->mFunc = fn;
101+
item->alarm = alarm;
94102

95-
if (rFirst)
96-
item->mNext = rFirst;
103+
esp8266::InterruptLock lockAllInterruptsInThisScope;
97104

98-
rFirst = item;
105+
if (rLast)
106+
{
107+
rLast->mNext = item;
108+
}
109+
else
110+
{
111+
rFirst = item;
112+
}
113+
rLast = item;
99114

100115
return true;
101116
}
102117

103-
void run_scheduled_functions ()
118+
void run_scheduled_functions()
104119
{
105120
esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
106121

@@ -128,15 +143,18 @@ void run_scheduled_functions ()
128143
}
129144
}
130145

131-
void run_scheduled_recurrent_functions ()
146+
void run_scheduled_recurrent_functions()
132147
{
148+
esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
149+
133150
// Note to the reader:
134151
// There is no exposed API to remove a scheduled function:
135152
// Scheduled functions are removed only from this function, and
136153
// its purpose is that it is never called from an interrupt
137154
// (always on cont stack).
138155

139-
if (!rFirst)
156+
auto current = rFirst;
157+
if (!current)
140158
return;
141159

142160
static bool fence = false;
@@ -153,26 +171,35 @@ void run_scheduled_recurrent_functions ()
153171
}
154172

155173
recurrent_fn_t* prev = nullptr;
156-
recurrent_fn_t* current = rFirst;
174+
// prevent scheduling of new functions during this run
175+
auto stop = rLast;
157176

158-
while (current)
177+
bool done;
178+
do
159179
{
160-
if (current->callNow && !current->mFunc())
180+
done = current == stop;
181+
const bool wakeup = current->alarm && current->alarm();
182+
bool callNow = current->callNow;
183+
184+
if ((wakeup || callNow) && !current->mFunc())
161185
{
162186
// remove function from stack
163187
esp8266::InterruptLock lockAllInterruptsInThisScope;
164188

165189
auto to_ditch = current;
166190

191+
// removing rLast
192+
if (rLast == current)
193+
rLast = prev;
194+
195+
current = current->mNext;
167196
if (prev)
168197
{
169-
current = current->mNext;
170198
prev->mNext = current;
171199
}
172200
else
173201
{
174-
rFirst = rFirst->mNext;
175-
current = rFirst;
202+
rFirst = current;
176203
}
177204

178205
delete(to_ditch);
@@ -182,7 +209,15 @@ void run_scheduled_recurrent_functions ()
182209
prev = current;
183210
current = current->mNext;
184211
}
185-
}
212+
213+
if (yieldNow)
214+
{
215+
// because scheduled functions might last too long for watchdog etc,
216+
// this is yield() in cont stack:
217+
esp_schedule();
218+
cont_yield(g_pcont);
219+
}
220+
} while (current && !done);
186221

187222
fence = false;
188223
}

cores/esp8266/Schedule.h

+6-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// in user stack (called CONT stack) without the common restrictions from
1111
// system context. Details are below.
1212

13-
// The purpose of recurrent scheduled function is to independantly execute
13+
// The purpose of recurrent scheduled function is to independently execute
1414
// user code in CONT stack on a regular basis.
1515
// It has been introduced with ethernet service in mind, it can also be used
1616
// for all libraries in the need of a regular `libdaemon_handlestuff()`.
@@ -58,14 +58,14 @@ void run_scheduled_functions();
5858
// functions. However a user function returning false will cancel itself.
5959
// * Long running operations or yield() or delay() are not allowed in the
6060
// recurrent function.
61-
// * A recurrent function currently must not schedule another recurrent
62-
// functions.
63-
64-
bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us);
61+
// * If alarm is used, anytime during scheduling when it returns true,
62+
// any remaining delay from repeat_us is disregarded, and fn is executed.
63+
bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
64+
uint32_t repeat_us, const std::function<bool(void)>& alarm = nullptr);
6565

6666
// Test recurrence and run recurrent scheduled functions.
6767
// (internally called at every `yield()` and `loop()`)
6868

69-
void run_scheduled_recurrent_functions ();
69+
void run_scheduled_recurrent_functions();
7070

7171
#endif // ESP_SCHEDULE_H

0 commit comments

Comments
 (0)