Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / nel / src / misc / co_task.cpp
blob7446a72d363d4a9aeddff73d822485d80ea7cc06
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2015-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdmisc.h"
22 #include "nel/misc/types_nl.h"
23 #include "nel/misc/co_task.h"
24 #include "nel/misc/tds.h"
25 #include "nel/misc/time_nl.h"
26 #include "nel/misc/debug.h"
28 // Flag to use thread instead of coroutine primitives (i.e windows fibers or gcc context)
29 #ifndef NL_OS_WINDOWS
30 #define NL_USE_THREAD_COTASK
31 #endif
32 // flag to activate debug message
33 //#define NL_GEN_DEBUG_MSG
35 #ifdef NL_GEN_DEBUG_MSG
36 #define NL_CT_DEBUG nldebug
37 #else
38 #define NL_CT_DEBUG while(0)nldebug
39 #endif
41 #if defined(NL_USE_THREAD_COTASK)
42 #ifndef __GNUC__
43 #pragma message(NL_LOC_MSG "Using threaded coroutine")
44 #endif
45 # include "nel/misc/thread.h"
46 #else //NL_USE_THREAD_COTASK
47 // some platform specifics
48 #if defined (NL_OS_WINDOWS)
49 # define NL_WIN_CALLBACK CALLBACK
50 #elif defined (NL_OS_UNIX)
51 # define NL_WIN_CALLBACK
52 # include <ucontext.h>
53 #else
54 # error "Coroutine task are not supported yet by your platform, do it ?"
55 #endif
56 #endif //NL_USE_THREAD_COTASK
58 #ifdef DEBUG_NEW
59 #define new DEBUG_NEW
60 #endif
62 namespace NLMISC
65 // platform specific data
66 #if defined(NL_USE_THREAD_COTASK)
67 struct TCoTaskData : public IRunnable
68 #else //NL_USE_THREAD_COTASK
69 struct TCoTaskData
70 #endif //NL_USE_THREAD_COTASK
72 #if defined(NL_USE_THREAD_COTASK)
73 /// The thread id for the co task
74 // TThreadId *_TaskThreadId;
75 /// The parent thread id
76 // TThreadId *_ParentThreadId;
78 // the thread of the task
79 IThread *_TaskThread;
80 /// The mutex of the task task
81 CFastMutex _TaskMutex;
83 CCoTask *_CoTask;
85 // set by master, cleared by task
86 volatile bool _ResumeTask;
87 // set by task, cleared by master
88 volatile bool _TaskHasYield;
91 TCoTaskData(CCoTask *task)
92 : _TaskThread(NULL),
93 _CoTask(task),
94 _ResumeTask(false),
95 _TaskHasYield(false)
99 virtual ~TCoTaskData()
101 NL_CT_DEBUG("CoTaskData : ~TCoTaskData %p : deleting cotask data", this);
102 if (_TaskThread != NULL)
104 NL_CT_DEBUG("CoTask : ~TCoTaskData (%p) waiting for thread termination", this);
106 // waiting for thread to terminate
107 _TaskThread->wait();
109 delete _TaskThread;
110 _TaskThread = NULL;
114 void run();
116 #else //NL_USE_THREAD_COTASK
117 #if defined (NL_OS_WINDOWS)
118 /// The fiber pointer for the task fiber
119 LPVOID _Fiber;
120 /// The fiber pointer of the main (or master, or parent, as you want)
121 LPVOID _ParentFiber;
122 #elif defined (NL_OS_UNIX)
123 /// The coroutine stack pointer (allocated memory)
124 uint8 *_Stack;
125 /// The task context
126 ucontext_t _Ctx;
127 /// The main (or master or parent, as you want) task context
128 ucontext_t _ParentCtx;
129 #endif
130 #endif //NL_USE_THREAD_COTASK
132 #if !defined(NL_USE_THREAD_COTASK)
133 /** task bootstrap function
134 * NB : this function is in this structure because of the
135 * NL_WIN_CALLBACK symbol that need <windows.h> to be defined, so
136 * to remove it from the header, I moved the function here
137 * (otherwise, it should be declared in the CCoTask class as
138 * a private member)
140 static void NL_WIN_CALLBACK startFunc(void* param)
142 CCoTask *task = reinterpret_cast<CCoTask*>(param);
144 NL_CT_DEBUG("CoTask : task %p start func called", task);
146 // Attach exception handler
147 attachExceptionHandler();
151 // run the task
152 task->run();
154 catch(...)
156 nlwarning("CCoTask::startFunc : the task has generated an unhandled exeption and will terminate");
157 NLMISC_BREAKPOINT;
160 task->_Finished = true;
162 NL_CT_DEBUG("CoTask : task %p finished, entering infinite yield loop (waiting destruction)", task);
164 // nothing more to do
165 for (;;)
166 // return to parent task
167 task->yield();
169 #endif //NL_USE_THREAD_COTASK
172 /** Management of current task in a thread.
173 * This class is used to store and retrieve the current
174 * CCoTask pointer in the current thread.
175 * It is build upon the SAFE_SINGLETON paradigm, making it
176 * safe to use with NeL DLL.
177 * For windows platform, this singleton also hold the
178 * fiber pointer of the current thread. This is needed because
179 * of the bad design of the fiber API before Windows XP.
181 class CCurrentCoTask
183 NLMISC_SAFE_SINGLETON_DECL(CCurrentCoTask);
185 /// A thread dependent storage to hold by thread coroutine info
186 CTDS _CurrentTaskTDS;
188 #if defined (NL_OS_WINDOWS)
189 /// A Thread dependent storage to hold fiber pointer.
190 CTDS _ThreadMainFiber;
191 #endif
193 CCurrentCoTask()
196 public:
197 /// Set the current task for the calling thread
198 void setCurrentTask(CCoTask *task)
200 NL_CT_DEBUG("CoTask : setting current co task to %p", task);
201 _CurrentTaskTDS.setPointer(task);
204 /// retrieve the current task for the calling thread
205 CCoTask *getCurrentTask()
207 return reinterpret_cast<CCoTask*>(_CurrentTaskTDS.getPointer());
209 #if defined (NL_OS_WINDOWS) && !defined(NL_USE_THREAD_COTASK)
210 void setMainFiber(LPVOID fiber)
212 _ThreadMainFiber.setPointer(fiber);
215 /** Return the main fiber for the calling thread. Return NULL if
216 * the thread has not been converted to fiber.
218 LPVOID getMainFiber()
220 return _ThreadMainFiber.getPointer();
222 #endif
224 static void releaseInstance()
226 if (_Instance)
228 NLMISC::INelContext::getInstance().releaseSingletonPointer("CCurrentCoTask", _Instance);
229 delete _Instance;
230 _Instance = NULL;
235 NLMISC_SAFE_SINGLETON_IMPL(CCurrentCoTask);
237 CCoTask *CCoTask::getCurrentTask()
239 return CCurrentCoTask::getInstance().getCurrentTask();
243 CCoTask::CCoTask(uint stackSize)
244 : _Started(false),
245 _TerminationRequested(false),
246 _Finished(false)
248 NL_CT_DEBUG("CoTask : creating task %p", this);
249 #if defined(NL_USE_THREAD_COTASK)
250 // allocate platform specific data storage
251 _PImpl = new TCoTaskData(this);
252 // _PImpl->_TaskThreadId = 0;
253 // _PImpl->_ParentThreadId = 0;
254 nlunreferenced(stackSize);
255 #else //NL_USE_THREAD_COTASK
256 // allocate platform specific data storage
257 _PImpl = new TCoTaskData;
258 nlunreferenced(stackSize);
259 #if defined (NL_OS_WINDOWS)
260 _PImpl->_Fiber = NULL;
261 _PImpl->_ParentFiber = NULL;
262 nlunreferenced(stackSize);
263 #elif defined(NL_OS_UNIX)
264 // allocate the stack
265 _PImpl->_Stack = new uint8[stackSize];
266 #endif
267 #endif //NL_USE_THREAD_COTASK
270 CCoTask::~CCoTask()
272 NL_CT_DEBUG("CoTask : deleting task %p", this);
273 _TerminationRequested = true;
275 if (_Started)
277 while (!_Finished)
278 resume();
281 #if defined(NL_USE_THREAD_COTASK)
283 #else //NL_USE_THREAD_COTASK
284 #if defined (NL_OS_WINDOWS)
285 if (_PImpl->_Fiber)
287 DeleteFiber(_PImpl->_Fiber);
289 #elif defined(NL_OS_UNIX)
290 // free the stack
291 delete [] _PImpl->_Stack;
292 #endif
293 #endif //NL_USE_THREAD_COTASK
295 // free platform specific storage
296 delete _PImpl;
299 void CCoTask::start()
301 NL_CT_DEBUG("CoTask : Starting task %p", this);
302 nlassert(!_Started);
304 _Started = true;
306 #if defined(NL_USE_THREAD_COTASK)
308 // create the thread
309 _PImpl->_TaskThread = IThread::create(_PImpl);
311 NL_CT_DEBUG("CoTask : start() task %p entering mutex", this);
312 // get the mutex
313 _PImpl->_TaskMutex.enter();
314 NL_CT_DEBUG("CoTask : start() task %p mutex entered", this);
316 // set the resume flag to true
317 _PImpl->_ResumeTask = true;
319 // start the thread
320 _PImpl->_TaskThread->start();
322 NL_CT_DEBUG("CoTask : start() task %p leaving mutex", this);
323 // leave the mutex
324 _PImpl->_TaskMutex.leave();
326 // wait until the task has yield
327 for (;;)
329 // give up the time slice to the co task
330 nlSleep(0);
331 NL_CT_DEBUG("CoTask : start() task %p entering mutex", this);
332 // get the mutex
333 _PImpl->_TaskMutex.enter();
334 NL_CT_DEBUG("CoTask : start() task %p mutex entered", this);
336 if (!_PImpl->_TaskHasYield)
338 // not finished
339 NL_CT_DEBUG("CoTask : start() task %p has not yield, leaving mutex", this);
340 // leave the mutex
341 _PImpl->_TaskMutex.leave();
343 else
345 break;
349 // clear the yield flag
350 _PImpl->_TaskHasYield = false;
352 NL_CT_DEBUG("CoTask : start() task %p has yield", this);
354 // in the treaded mode, there is no need to call resume() inside start()
356 #else //NL_USE_THREAD_COTASK
357 #if defined (NL_OS_WINDOWS)
359 LPVOID mainFiber = CCurrentCoTask::getInstance().getMainFiber();
361 if (mainFiber == NULL)
363 // we need to convert this thread to a fiber
364 mainFiber = ConvertThreadToFiber(NULL);
366 if (mainFiber == NULL)
368 DWORD dw = GetLastError();
369 #if defined(ERROR_ALREADY_FIBER)
370 if (dw == ERROR_ALREADY_FIBER) nlerror("ConvertThreadToFiber ERROR_ALREADY_FIBER: "
371 "If you are using nel in dynamic libraries, you should have a 'pure "
372 "nel library' entry point, see definition of NLMISC_DECL_PURE_LIB");
373 else
374 #endif
375 nlerror("ConvertThreadToFiber error %u", dw);
378 CCurrentCoTask::getInstance().setMainFiber(mainFiber);
381 _PImpl->_ParentFiber = mainFiber;
382 _PImpl->_Fiber = CreateFiber(NL_TASK_STACK_SIZE, TCoTaskData::startFunc, this);
383 nlassert(_PImpl->_Fiber != NULL);
384 #elif defined (NL_OS_UNIX)
385 // store the parent ctx
386 nlverify(getcontext(&_PImpl->_ParentCtx) == 0);
387 // build the task context
388 nlverify(getcontext(&_PImpl->_Ctx) == 0);
390 // change the task context
391 _PImpl->_Ctx.uc_stack.ss_sp = _PImpl->_Stack;
392 _PImpl->_Ctx.uc_stack.ss_size = NL_TASK_STACK_SIZE;
394 _PImpl->_Ctx.uc_link = NULL;
395 _PImpl->_Ctx.uc_stack.ss_flags = 0;
397 makecontext(&_PImpl->_Ctx, reinterpret_cast<void (*)()>(TCoTaskData::startFunc), 1, this);
398 #endif
399 resume();
400 #endif //NL_USE_THREAD_COTASK
403 void CCoTask::yield()
405 NL_CT_DEBUG("CoTask : task %p yield", this);
406 nlassert(_Started);
407 nlassert(CCurrentCoTask::getInstance().getCurrentTask() == this);
408 #if defined(NL_USE_THREAD_COTASK)
410 // set the yield flag
411 _PImpl->_TaskHasYield = true;
413 // release the mutex
414 NL_CT_DEBUG("CoTask : yield() task %p leaving mutex", this);
415 _PImpl->_TaskMutex.leave();
417 // now, wait until the resume flag is set
418 for (;;)
420 // give up the time slice to the master thread
421 nlSleep(0);
422 // And get back the mutex for waiting for next resume (this should lock)
423 NL_CT_DEBUG("CoTask : yield() task %p entering mutex", this);
424 _PImpl->_TaskMutex.enter();
425 NL_CT_DEBUG("CoTask : yield() task %p mutex entered", this);
427 if (!_PImpl->_ResumeTask)
429 // not time to resume, release the mutex and sleep
430 NL_CT_DEBUG("CoTask : yield() task %p not time to resume, leaving mutex", this);
431 _PImpl->_TaskMutex.leave();
432 // nlSleep(0);
434 else
435 break;
438 // clear the resume flag
439 _PImpl->_ResumeTask = false;
441 #else //NL_USE_THREAD_COTASK
442 CCurrentCoTask::getInstance().setCurrentTask(NULL);
443 #if defined (NL_OS_WINDOWS)
444 SwitchToFiber(_PImpl->_ParentFiber);
445 #elif defined (NL_OS_UNIX)
446 // swap to the parent context
447 nlverify(swapcontext(&_PImpl->_Ctx, &_PImpl->_ParentCtx) == 0);
448 #endif
449 #endif //NL_USE_THREAD_COTASK
451 NL_CT_DEBUG("CoTask : task %p have been resumed", this);
454 void CCoTask::resume()
456 NL_CT_DEBUG("CoTask : resuming task %p", this);
457 nlassert(CCurrentCoTask::getInstance().getCurrentTask() != this);
458 if (!_Started)
459 start();
460 else if (!_Finished)
462 nlassert(_Started);
464 #if defined(NL_USE_THREAD_COTASK)
466 // set the resume flag to true
467 _PImpl->_ResumeTask = true;
468 _PImpl->_TaskHasYield = false;
469 // Release the mutex
470 NL_CT_DEBUG("CoTask : resume() task %p leaving mutex", this);
471 _PImpl->_TaskMutex.leave();
472 // wait that the task has started
473 while (_PImpl->_ResumeTask)
474 nlSleep(0);
476 NL_CT_DEBUG("CoTask : resume() task %p is started, waiting yield", this);
477 // ok the task has started
478 // now wait for task to yield
479 for (;;)
481 // give up the time slice to the co task
482 nlSleep(0);
484 // acquire the mutex
485 NL_CT_DEBUG("CoTask : resume() task %p entering mutex", this);
486 _PImpl->_TaskMutex.enter();
487 NL_CT_DEBUG("CoTask : resume() task %p mutex entered", this);
489 if (!_PImpl->_TaskHasYield)
491 NL_CT_DEBUG("CoTask : resume() task %p still not yielding, leaving mutex", this);
492 _PImpl->_TaskMutex.leave();
493 // give the focus to another thread before acquiring the mutex
494 // nlSleep(0);
496 else
498 // the task has yield
499 break;
503 // clear the yield flag
504 _PImpl->_TaskHasYield = false;
506 #else // NL_USE_THREAD_COTASK
507 CCurrentCoTask::getInstance().setCurrentTask(this);
508 #if defined (NL_OS_WINDOWS)
509 SwitchToFiber(_PImpl->_Fiber);
510 #elif defined (NL_OS_UNIX)
511 // swap to the parent context
512 nlverify(swapcontext(&_PImpl->_ParentCtx, &_PImpl->_Ctx) == 0);
513 #endif
514 #endif //NL_USE_THREAD_COTASK
517 NL_CT_DEBUG("CoTask : task %p has yield", this);
520 /// wait until the task terminate
521 void CCoTask::wait()
523 NL_CT_DEBUG("CoTask : waiting for task %p to terminate", this);
524 // resume the task until termination
525 while (!_Finished)
526 resume();
529 #if defined(NL_USE_THREAD_COTASK)
530 void TCoTaskData::run()
532 NL_CT_DEBUG("CoTask : entering TCoTaskData::run for task %p", _CoTask);
533 // set the current task
534 CCurrentCoTask::getInstance().setCurrentTask(_CoTask);
535 // Set the task as running
536 // _Running = true;
537 NL_CT_DEBUG("CoTask : TCoTaskData::run() task %p entering mutex", this);
538 // Acquire the task mutex
539 _TaskMutex.enter();
540 NL_CT_DEBUG("CoTask : TCoTaskData::run mutex aquired, calling '_CoTask->run()' for task %p", _CoTask);
542 // clear the resume flag
543 _CoTask->_PImpl->_ResumeTask = false;
545 // run the task
546 _CoTask->run();
548 // mark the task has yielding
549 _CoTask->_PImpl->_TaskHasYield = true;
550 // mark the task has finished
551 _CoTask->_Finished = true;
553 // nothing more to do, just return to terminate the thread
554 NL_CT_DEBUG("CoTask : leaving TCoTaskData::run for task %p", _CoTask);
556 NL_CT_DEBUG("CoTask : TCoTaskData::run() task %p leaving mutex", this);
557 // Release the parent mutex
558 _TaskMutex.leave();
561 #endif //NL_USE_THREAD_COTASK
563 void CCoTask::requestTerminate()
565 _TerminationRequested = true;
568 void CCoTask::sleep(uint milliseconds)
570 nlassert(getCurrentTask() == this); // called outside run() !
571 TTime startTime = CTime::getLocalTime();
572 while(!isTerminationRequested())
574 TTime currTime = CTime::getLocalTime();
575 if (currTime - startTime >= milliseconds) break;
576 yield();
580 void CCoTask::releaseInstance()
582 CCurrentCoTask::releaseInstance();
585 } // namespace NLMISC