Skip to content

Commit 3c8af8a

Browse files
author
dave
committed
#5 back fill testing into project to ensure good coverage. Now quite well tested.
1 parent a6855c4 commit 3c8af8a

12 files changed

+814
-84
lines changed

src/TaskBlock.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// Created by David Cherry on 28/08/2020.
3+
//
4+
5+
#ifndef _TASKMANAGERIO_TASKBLOCK_H_
6+
#define _TASKMANAGERIO_TASKBLOCK_H_
7+
8+
#include <TaskPlatformDeps.h>
9+
#include <TaskTypes.h>
10+
11+
/**
12+
* This is an internal class, and users of the library generally don't see it.
13+
*
14+
* Task manager can never deallocate memory that it has already allocated for tasks, this is in order to make thread
15+
* safety much easier. Given this we allocate tasks in blocks of DEFAULT_TASK_SIZE and each tranche contains it's start
16+
* and end point in the "array". DEFAULT task size is set to 20 on 32 bit hardware where the size is negligible, 10
17+
* on MEGA2560 and all other AVR boards default to 6. We allow up to 8 tranches on AVR and up to 16 on 32 bit boards.
18+
* This should provide more than enough tasks for most boards.
19+
*/
20+
class TaskBlock {
21+
private:
22+
TimerTask tasks[DEFAULT_TASK_SIZE];
23+
const taskid_t first;
24+
const taskid_t arraySize;
25+
public:
26+
explicit TaskBlock(taskid_t first_) : first(first_), arraySize(DEFAULT_TASK_SIZE) {}
27+
28+
/**
29+
* Checks if taskId is contained within this block
30+
* @param task the task ID to check
31+
* @return true if contained, otherwise false
32+
*/
33+
bool isTaskContained(taskid_t task) const {
34+
return task >= first && task < (first + arraySize);
35+
};
36+
37+
TimerTask* getContainedTask(taskid_t task) {
38+
return isTaskContained(task) ? &tasks[task - first] : nullptr;
39+
}
40+
41+
void clearAll() {
42+
for(taskid_t i=0; i<arraySize;i++) {
43+
tasks[i].clear();
44+
}
45+
}
46+
47+
taskid_t allocateTask() {
48+
for(taskid_t i=0; i<arraySize; i++) {
49+
if(tasks[i].allocateIfPossible()) {
50+
return i + first;
51+
}
52+
}
53+
return TASKMGR_INVALIDID;
54+
}
55+
56+
taskid_t lastSlot() const {
57+
return first + arraySize - 1;
58+
}
59+
60+
taskid_t firstSlot() const {
61+
return first;
62+
}
63+
};
64+
65+
66+
#endif //_TASKMANAGERIO_TASKBLOCK_H_

src/TaskManager.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ taskid_t TaskManager::scheduleOnce(uint32_t when, TimerFn timerFunction, TimerUn
101101
auto taskId = findFreeTask();
102102
if (taskId != TASKMGR_INVALIDID) {
103103
auto task = getTask(taskId);
104-
task->initialise(when, timeUnit, timerFunction);
104+
task->initialise(when, timeUnit, timerFunction, false);
105105
putItemIntoQueue(task);
106106
}
107107
return taskId;
@@ -111,7 +111,7 @@ taskid_t TaskManager::scheduleFixedRate(uint32_t when, TimerFn timerFunction, Ti
111111
auto taskId = findFreeTask();
112112
if (taskId != TASKMGR_INVALIDID) {
113113
auto task = getTask(taskId);
114-
task->initialise(when, TimerUnit(timeUnit | TM_TIME_REPEATING), timerFunction);
114+
task->initialise(when, timeUnit, timerFunction, true);
115115
putItemIntoQueue(task);
116116
}
117117
return taskId;
@@ -121,7 +121,7 @@ taskid_t TaskManager::scheduleOnce(uint32_t when, Executable* execRef, TimerUnit
121121
auto taskId = findFreeTask();
122122
if (taskId != TASKMGR_INVALIDID) {
123123
auto task = getTask(taskId);
124-
task->initialise(when, timeUnit, execRef, deleteWhenDone);
124+
task->initialise(when, timeUnit, execRef, deleteWhenDone, false);
125125
putItemIntoQueue(task);
126126
}
127127
return taskId;
@@ -131,7 +131,7 @@ taskid_t TaskManager::scheduleFixedRate(uint32_t when, Executable* execRef, Time
131131
auto taskId = findFreeTask();
132132
if (taskId != TASKMGR_INVALIDID) {
133133
auto task = getTask(taskId);
134-
task->initialise(when, TimerUnit(timeUnit | TM_TIME_REPEATING), execRef, deleteWhenDone);
134+
task->initialise(when, timeUnit, execRef, deleteWhenDone, true);
135135
putItemIntoQueue(task);
136136
}
137137
return taskId;

src/TaskManager.h

Lines changed: 1 addition & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "TaskPlatformDeps.h"
1010
#include "TaskTypes.h"
11+
#include "TaskBlock.h"
1112

1213
/**
1314
* @file TaskManager.h
@@ -82,60 +83,6 @@ class BasicArduinoInterruptAbstraction : public InterruptAbstraction {
8283
};
8384
#endif // IOA_USE_ARDUINO - Arduino Only
8485

85-
/**
86-
* This is an internal class, and users of the library generally don't see it.
87-
*
88-
* Task manager can never deallocate memory that it has already allocated for tasks, this is in order to make thread
89-
* safety much easier. Given this we allocate tasks in blocks of DEFAULT_TASK_SIZE and each tranche contains it's start
90-
* and end point in the "array". DEFAULT task size is set to 20 on 32 bit hardware where the size is negligible, 10
91-
* on MEGA2560 and all other AVR boards default to 6. We allow up to 8 tranches on AVR and up to 16 on 32 bit boards.
92-
* This should provide more than enough tasks for most boards.
93-
*/
94-
class TaskBlock {
95-
private:
96-
TimerTask tasks[DEFAULT_TASK_SIZE];
97-
const taskid_t first;
98-
const taskid_t arraySize;
99-
public:
100-
explicit TaskBlock(taskid_t first_) : first(first_), arraySize(DEFAULT_TASK_SIZE) {}
101-
102-
/**
103-
* Checks if taskId is contained within this block
104-
* @param task the task ID to check
105-
* @return true if contained, otherwise false
106-
*/
107-
bool isTaskContained(taskid_t task) const {
108-
return task >= first && task < (first + arraySize);
109-
};
110-
111-
TimerTask* getContainedTask(taskid_t task) {
112-
return isTaskContained(task) ? &tasks[task - first] : nullptr;
113-
}
114-
115-
void clearAll() {
116-
for(taskid_t i=0; i<arraySize;i++) {
117-
tasks[i].clear();
118-
}
119-
}
120-
121-
taskid_t allocateTask() {
122-
for(taskid_t i=0; i<arraySize; i++) {
123-
if(tasks[i].allocateIfPossible()) {
124-
return i + first;
125-
}
126-
}
127-
return TASKMGR_INVALIDID;
128-
}
129-
130-
taskid_t lastSlot() const {
131-
return first + arraySize - 1;
132-
}
133-
134-
taskid_t firstSlot() const {
135-
return first;
136-
}
137-
};
138-
13986
/**
14087
* TaskManager is a lightweight cooperative co-routine implementation for Arduino, it works by scheduling tasks to be
14188
* done either immediately, or at a future point in time. It is quite efficient at scheduling tasks as internally tasks

src/TaskTypes.cpp

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,34 +35,34 @@ TimerTask::TimerTask() {
3535
tm_internal::atomicWriteBool(&taskInUse, false);
3636
}
3737

38-
void TimerTask::initialise(sched_t execInfo, TimerUnit unit, TimerFn execCallback) {
39-
this->myTimingSchedule = execInfo;
40-
this->timingInformation = unit;
38+
void TimerTask::initialise(sched_t when, TimerUnit unit, TimerFn execCallback, bool repeating) {
39+
handleScheduling(when, unit, repeating);
4140
this->callback = execCallback;
4241
this->executeMode = EXECTYPE_FUNCTION;
42+
}
4343

44-
this->scheduledAt = (isMicrosSchedule()) ? micros() : millis();
44+
void TimerTask::handleScheduling(sched_t when, TimerUnit unit, bool repeating) {
4545
tm_internal::atomicWritePtr(&next, nullptr);
46+
47+
if(unit == TIME_SECONDS) {
48+
when = when * sched_t(1000);
49+
unit = TIME_MILLIS;
50+
}
51+
this->myTimingSchedule = when;
52+
this->timingInformation = repeating ? TimerUnit(unit | TM_TIME_REPEATING) : unit;
53+
this->scheduledAt = (isMicrosSchedule()) ? micros() : millis();
4654
}
4755

48-
void TimerTask::initialise(uint32_t execInfo, TimerUnit unit, Executable* execCallback, bool deleteWhenDone) {
49-
this->myTimingSchedule = execInfo;
50-
this->timingInformation = unit;
56+
void TimerTask::initialise(uint32_t when, TimerUnit unit, Executable* execCallback, bool deleteWhenDone, bool repeating) {
57+
handleScheduling(when, unit, repeating);
5158
this->taskRef = execCallback;
5259
this->executeMode = deleteWhenDone ? ExecutionType(EXECTYPE_EXECUTABLE | EXECTYPE_DELETE_ON_DONE) : EXECTYPE_EXECUTABLE;
53-
54-
this->scheduledAt = (isMicrosSchedule()) ? micros() : millis();
55-
tm_internal::atomicWritePtr(&next, nullptr);
5660
}
5761

5862
void TimerTask::initialiseEvent(BaseEvent* event, bool deleteWhenDone) {
59-
this->timingInformation = (TimerUnit)(TIME_MICROS | TM_TIME_REPEATING);
60-
this->myTimingSchedule = 0;
63+
handleScheduling(0, TIME_MICROS, true);
6164
this->eventRef = event;
6265
this->executeMode = deleteWhenDone ? ExecutionType(EXECTYPE_EVENT | EXECTYPE_DELETE_ON_DONE) : EXECTYPE_EVENT;
63-
64-
this->scheduledAt = micros();
65-
tm_internal::atomicWritePtr(&next, nullptr);
6666
}
6767

6868
bool TimerTask::isReady() {
@@ -72,10 +72,6 @@ bool TimerTask::isReady() {
7272
uint32_t delay = myTimingSchedule;
7373
return (micros() - scheduledAt) >= delay;
7474
}
75-
else if(isSecondsSchedule()) {
76-
uint32_t delay = uint32_t(myTimingSchedule) * 1000UL;
77-
return (millis() - scheduledAt) >= delay;
78-
}
7975
else {
8076
uint32_t delay = myTimingSchedule;
8177
return (millis() - scheduledAt) >= delay;
@@ -91,9 +87,6 @@ unsigned long TimerTask::microsFromNow() {
9187
}
9288
else {
9389
uint32_t delay = myTimingSchedule;
94-
if (isSecondsSchedule()) {
95-
delay *= 1000UL;
96-
}
9790
uint32_t alreadyTaken = (millis() - scheduledAt);
9891
microsFromNow = (delay < alreadyTaken) ? 0 : ((delay - alreadyTaken) * 1000UL);
9992
}

src/TaskTypes.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ enum TimerUnit : uint8_t {
133133
TM_TIME_RUNNING = 0x20,
134134

135135
TIME_REP_MICROS = TIME_MICROS | TM_TIME_REPEATING,
136-
TIME_REP_SECONDS = TIME_SECONDS | TM_TIME_REPEATING,
137136
TIME_REP_MILLIS = TIME_MILLIS | TM_TIME_REPEATING,
138137
};
139138

@@ -204,7 +203,7 @@ class TimerTask {
204203
* @param unit the unit of time measurement
205204
* @param execCallback the function to call back
206205
*/
207-
void initialise(sched_t executionInfo, TimerUnit unit, TimerFn execCallback);
206+
void initialise(sched_t when, TimerUnit unit, TimerFn execCallback, bool repeating);
208207

209208
/**
210209
* Initialise a task slot with execution information
@@ -213,7 +212,7 @@ class TimerTask {
213212
* @param executable the class instance to call back
214213
* @param deleteWhenDone indicates taskmanager owns this memory and should delete it when clear is called.
215214
*/
216-
void initialise(sched_t executionInfo, TimerUnit unit, Executable *executable, bool deleteWhenDone);
215+
void initialise(sched_t when, TimerUnit unit, Executable *executable, bool deleteWhenDone, bool repeating);
217216

218217
/**
219218
* Initialise an event structure, which will call the event immediately to get the next poll time
@@ -222,6 +221,13 @@ class TimerTask {
222221
*/
223222
void initialiseEvent(BaseEvent *event, bool deleteWhenDone);
224223

224+
/**
225+
* Called by all the initialise methods to actually do the initial scheduling.
226+
* @param when when the task is to take place.
227+
* @param unit the time unit upon which it will occur.
228+
*/
229+
void handleScheduling(sched_t when, TimerUnit unit, bool repeating);
230+
225231
/**
226232
* Atomically checks if the task is in use at the moment.
227233
* @return true if in use, otherwise false.
@@ -305,9 +311,14 @@ class TimerTask {
305311
*/
306312
void processEvent();
307313

314+
/**
315+
* @return true if the task is on a microsecond schedule
316+
*/
308317
bool isMicrosSchedule() { return (timingInformation & 0x0fU)==TIME_MICROS; }
318+
/**
319+
* @return true if the task in on a millisecond schedule
320+
*/
309321
bool isMillisSchedule() { return (timingInformation & 0x0fU)==TIME_MILLIS; }
310-
bool isSecondsSchedule() { return (timingInformation & 0x0fU)==TIME_SECONDS; }
311322
};
312323

313324
#endif //TASKMANAGER_IO_TASKTYPES_H
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#ifdef ESP32
2+
3+
#include <AUnit.h>
4+
#include <TaskManagerIO.h>
5+
#include "test_utils.h"
6+
#include <pthread.h>
7+
8+
using namespace aunit;
9+
10+
int executions = 0;
11+
int failedCreations = 0;
12+
13+
void callbackFunction() {
14+
executions++;
15+
}
16+
17+
void* creationThread(void* param) {
18+
Serial.println("Start thread to create tasks");
19+
20+
for(int i = 0; i < 500; i++) {
21+
if(TASKMGR_INVALIDID == taskManager.execute(callbackFunction)) failedCreations++;
22+
delay(5);
23+
}
24+
25+
Serial.println("Completed task creation");
26+
}
27+
28+
void* scheduledCreationThread(void* param) {
29+
Serial.println("Start thread to schedule tasks");
30+
31+
for(int i = 0; i < 250; i++) {
32+
if(TASKMGR_INVALIDID == taskManager.scheduleOnce(15, callbackFunction)) failedCreations++;
33+
delay(10);
34+
}
35+
36+
Serial.println("Completed scheduled task creation");
37+
}
38+
39+
/**
40+
* when I create a lot of tasks over three threads that are either immediate executed or near immediately executed
41+
* then they should all be scheduled and there should be no failures
42+
*/
43+
test(multiThreadedTestForESP) {
44+
pthread_t threadId1;
45+
pthread_t threadId2;
46+
pthread_t threadId3;
47+
pthread_create(&threadId1, NULL, creationThread, (void*)1);
48+
pthread_create(&threadId2, NULL, creationThread, (void*)2);
49+
pthread_create(&threadId3, NULL, scheduledCreationThread, (void*)3);
50+
51+
unsigned long startTime = millis();
52+
53+
// wait until the task is marked as scheduled.
54+
while(executions < 2000 && (millis() - startTime) < 5000) {
55+
taskManager.yieldForMicros(10000);
56+
}
57+
58+
assertEqual(1250, executions);
59+
assertEqual(0, failedCreations);
60+
}
61+
62+
#endif

0 commit comments

Comments
 (0)