1 // The MIT License (MIT)
2 //
3 // 	Copyright (c) 2015 Sergey Makeev, Vadim Slyusarev
4 //
5 // 	Permission is hereby granted, free of charge, to any person obtaining a copy
6 // 	of this software and associated documentation files (the "Software"), to deal
7 // 	in the Software without restriction, including without limitation the rights
8 // 	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // 	copies of the Software, and to permit persons to whom the Software is
10 // 	furnished to do so, subject to the following conditions:
11 //
12 //  The above copyright notice and this permission notice shall be included in
13 // 	all copies or substantial portions of the Software.
14 //
15 // 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // 	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // 	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // 	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // 	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 // 	THE SOFTWARE.
22 
23 #include "Tests.h"
24 #include <UnitTest++.h>
25 #include <MTScheduler.h>
26 
27 /*
28 
29 Check that every worker thread executed tasks in specified priority. HIGH/NORMAL/LOW
30 
31 */
SUITE(PriorityTests)32 SUITE(PriorityTests)
33 {
34 	static const uint32 TASK_COUNT = 512;
35 
36 	MT::Atomic32<int32> switchCountToNormal;
37 	MT::Atomic32<int32> switchCountToLow;
38 
39 
40 	struct ThreadState
41 	{
42 		uint32 taskPrio;
43 		uint32 highProcessed;
44 		uint32 normalProcessed;
45 		uint32 lowProcessed;
46 		byte cacheLine[64];
47 
48 		ThreadState()
49 		{
50 			Reset();
51 		}
52 
53 		void Reset()
54 		{
55 			taskPrio = 0;
56 			highProcessed = 0;
57 			normalProcessed = 0;
58 			lowProcessed = 0;
59 		}
60 	};
61 
62 	ThreadState workerState[64];
63 
64 	struct TaskHigh
65 	{
66 		MT_DECLARE_TASK(TaskHigh, MT::StackRequirements::STANDARD, MT::TaskPriority::HIGH, MT::Color::Blue);
67 
68 		uint32 id;
69 
70 		TaskHigh(uint32 _id)
71 			: id(_id)
72 		{
73 		}
74 
75 		void Do(MT::FiberContext& ctx)
76 		{
77 			uint32 workerIndex = ctx.GetThreadContext()->workerIndex;
78 			MT_ASSERT(workerIndex < MT_ARRAY_SIZE(workerState), "Invalid worker index");
79 			ThreadState& state = workerState[workerIndex];
80 
81 			CHECK_EQUAL((uint32)0, state.normalProcessed);
82 			CHECK_EQUAL((uint32)0, state.lowProcessed);
83 
84 			state.highProcessed++;
85 
86 			// Check we in right state (executing HIGH priority tasks)
87 			CHECK_EQUAL((uint32)0, state.taskPrio);
88 
89 		}
90 	};
91 
92 
93 	struct TaskNormal
94 	{
95 		MT_DECLARE_TASK(TaskNormal, MT::StackRequirements::STANDARD, MT::TaskPriority::NORMAL, MT::Color::Blue);
96 
97 		uint32 id;
98 
99 		TaskNormal(uint32 _id)
100 			: id(_id)
101 		{
102 		}
103 
104 		void Do(MT::FiberContext& ctx)
105 		{
106 			uint32 workerIndex = ctx.GetThreadContext()->workerIndex;
107 			MT_ASSERT(workerIndex < MT_ARRAY_SIZE(workerState), "Invalid worker index");
108 			ThreadState& state = workerState[workerIndex];
109 
110 			CHECK_EQUAL((uint32)0, state.lowProcessed);
111 
112 			state.normalProcessed++;
113 
114 			//if state is set to HIGH tasks, change state to NORMAL tasks
115 			if (state.taskPrio == 0)
116 			{
117 				state.taskPrio = 1;
118 				switchCountToNormal.IncFetch();
119 			}
120 
121 			// Check we in right state (executing NORMAL priority tasks)
122 			CHECK_EQUAL((uint32)1, state.taskPrio);
123 		}
124 	};
125 
126 	struct TaskLow
127 	{
128 		MT_DECLARE_TASK(TaskLow, MT::StackRequirements::STANDARD, MT::TaskPriority::LOW, MT::Color::Blue);
129 
130 		uint32 id;
131 
132 		TaskLow(uint32 _id)
133 			: id(_id)
134 		{
135 		}
136 
137 
138 		void Do(MT::FiberContext& ctx)
139 		{
140 			uint32 workerIndex = ctx.GetThreadContext()->workerIndex;
141 			MT_ASSERT(workerIndex < MT_ARRAY_SIZE(workerState), "Invalid worker index");
142 			ThreadState& state = workerState[workerIndex];
143 
144 			state.lowProcessed++;
145 
146 			//if state is set to NORMAL tasks, change state to LOW tasks
147 			if (state.taskPrio == 1)
148 			{
149 				state.taskPrio = 2;
150 				switchCountToLow.IncFetch();
151 			}
152 
153 			// Check we in right state (executing LOW priority tasks)
154 			CHECK_EQUAL((uint32)2, state.taskPrio);
155 		}
156 	};
157 
158 
159 
160 
161 	TEST(SimplePriorityTest)
162 	{
163 		MT::TaskPool<TaskLow, TASK_COUNT> lowPriorityTasksPool;
164 		MT::TaskPool<TaskNormal, TASK_COUNT> normalPriorityTasksPool;
165 		MT::TaskPool<TaskHigh, TASK_COUNT> highPriorityTasksPool;
166 
167 		// Disable task stealing (for testing purposes only)
168 #ifdef MT_INSTRUMENTED_BUILD
169 		MT::TaskScheduler scheduler(0, nullptr, nullptr, MT::TaskStealingMode::DISABLED);
170 #else
171 		MT::TaskScheduler scheduler(0, nullptr, MT::TaskStealingMode::DISABLED);
172 #endif
173 
174 		// Use task handles to add multiple tasks with different priorities in one RunAsync call
175 		MT::TaskHandle taskHandles[TASK_COUNT*3];
176 
177 		uint32 index = 0;
178 		for(uint32 i = 0; i < TASK_COUNT; i++)
179 		{
180 			taskHandles[index] = lowPriorityTasksPool.Alloc(TaskLow(i));
181 			index++;
182 		}
183 
184 		for(uint32 i = 0; i < TASK_COUNT; i++)
185 		{
186 			taskHandles[index] = highPriorityTasksPool.Alloc(TaskHigh(i));
187 			index++;
188 		}
189 
190 		for(uint32 i = 0; i < TASK_COUNT; i++)
191 		{
192 			taskHandles[index] = normalPriorityTasksPool.Alloc(TaskNormal(i));
193 			index++;
194 		}
195 
196 		switchCountToNormal.Store(0);
197 		switchCountToLow.Store(0);
198 
199 		for(uint32 i = 0; i < MT_ARRAY_SIZE(workerState); i++)
200 		{
201 			workerState[i].Reset();
202 		}
203 
204 		scheduler.RunAsync(MT::TaskGroup::Default(), &taskHandles[0], MT_ARRAY_SIZE(taskHandles));
205 		CHECK(scheduler.WaitAll(2000));
206 
207 		int32 workersCount = scheduler.GetWorkersCount();
208 		float minTasksExecuted  = (float)TASK_COUNT / (float)workersCount;
209 		minTasksExecuted *= 0.95f;
210 		uint32 minTasksExecutedThreshold = (uint32)minTasksExecuted;
211 
212 		uint32 lowProcessedTotal = 0;
213 		uint32 normalProcessedTotal = 0;
214 		uint32 highProcessedTotal = 0;
215 
216 		for(int32 j = 0; j < workersCount; j++)
217 		{
218 			lowProcessedTotal += workerState[j].lowProcessed;
219 			normalProcessedTotal += workerState[j].normalProcessed;
220 			highProcessedTotal += workerState[j].highProcessed;
221 		}
222 
223 		CHECK_EQUAL(TASK_COUNT, lowProcessedTotal);
224 		CHECK_EQUAL(TASK_COUNT, normalProcessedTotal);
225 		CHECK_EQUAL(TASK_COUNT, highProcessedTotal);
226 
227 		for(int32 j = 0; j < workersCount; j++)
228 		{
229 			printf("worker #%d\n", j);
230 
231 			CHECK_EQUAL((uint32)2, workerState[j].taskPrio);
232 			CHECK(workerState[j].lowProcessed >= minTasksExecutedThreshold);
233 			CHECK(workerState[j].normalProcessed >= minTasksExecutedThreshold);
234 			CHECK(workerState[j].highProcessed >= minTasksExecutedThreshold);
235 
236 			printf("   low : %d\n", workerState[j].lowProcessed);
237 			printf("   normal : %d\n", workerState[j].normalProcessed);
238 			printf("   high : %d\n", workerState[j].highProcessed);
239 		}
240 
241 		//
242 		// Every worker thread can't change state more than once.
243 		//
244 		CHECK_EQUAL(workersCount, switchCountToNormal.Load());
245 		CHECK_EQUAL(workersCount, switchCountToLow.Load());
246 
247 
248 	}
249 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
250 }
251 
252