1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2015-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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/>.
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)
30 #define NL_USE_THREAD_COTASK
32 // flag to activate debug message
33 //#define NL_GEN_DEBUG_MSG
35 #ifdef NL_GEN_DEBUG_MSG
36 #define NL_CT_DEBUG nldebug
38 #define NL_CT_DEBUG while(0)nldebug
41 #if defined(NL_USE_THREAD_COTASK)
43 #pragma message(NL_LOC_MSG "Using threaded coroutine")
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>
54 # error "Coroutine task are not supported yet by your platform, do it ?"
56 #endif //NL_USE_THREAD_COTASK
65 // platform specific data
66 #if defined(NL_USE_THREAD_COTASK)
67 struct TCoTaskData
: public IRunnable
68 #else //NL_USE_THREAD_COTASK
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
80 /// The mutex of the task task
81 CFastMutex _TaskMutex
;
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
)
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
116 #else //NL_USE_THREAD_COTASK
117 #if defined (NL_OS_WINDOWS)
118 /// The fiber pointer for the task fiber
120 /// The fiber pointer of the main (or master, or parent, as you want)
122 #elif defined (NL_OS_UNIX)
123 /// The coroutine stack pointer (allocated memory)
127 /// The main (or master or parent, as you want) task context
128 ucontext_t _ParentCtx
;
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
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();
156 nlwarning("CCoTask::startFunc : the task has generated an unhandled exeption and will terminate");
160 task
->_Finished
= true;
162 NL_CT_DEBUG("CoTask : task %p finished, entering infinite yield loop (waiting destruction)", task
);
164 // nothing more to do
166 // return to parent task
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.
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
;
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();
224 static void releaseInstance()
228 NLMISC::INelContext::getInstance().releaseSingletonPointer("CCurrentCoTask", _Instance
);
235 NLMISC_SAFE_SINGLETON_IMPL(CCurrentCoTask
);
237 CCoTask
*CCoTask::getCurrentTask()
239 return CCurrentCoTask::getInstance().getCurrentTask();
243 CCoTask::CCoTask(uint stackSize
)
245 _TerminationRequested(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
];
267 #endif //NL_USE_THREAD_COTASK
272 NL_CT_DEBUG("CoTask : deleting task %p", this);
273 _TerminationRequested
= true;
281 #if defined(NL_USE_THREAD_COTASK)
283 #else //NL_USE_THREAD_COTASK
284 #if defined (NL_OS_WINDOWS)
287 DeleteFiber(_PImpl
->_Fiber
);
289 #elif defined(NL_OS_UNIX)
291 delete [] _PImpl
->_Stack
;
293 #endif //NL_USE_THREAD_COTASK
295 // free platform specific storage
299 void CCoTask::start()
301 NL_CT_DEBUG("CoTask : Starting task %p", this);
306 #if defined(NL_USE_THREAD_COTASK)
309 _PImpl
->_TaskThread
= IThread::create(_PImpl
);
311 NL_CT_DEBUG("CoTask : start() task %p entering mutex", this);
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;
320 _PImpl
->_TaskThread
->start();
322 NL_CT_DEBUG("CoTask : start() task %p leaving mutex", this);
324 _PImpl
->_TaskMutex
.leave();
326 // wait until the task has yield
329 // give up the time slice to the co task
331 NL_CT_DEBUG("CoTask : start() task %p entering mutex", this);
333 _PImpl
->_TaskMutex
.enter();
334 NL_CT_DEBUG("CoTask : start() task %p mutex entered", this);
336 if (!_PImpl
->_TaskHasYield
)
339 NL_CT_DEBUG("CoTask : start() task %p has not yield, leaving mutex", this);
341 _PImpl
->_TaskMutex
.leave();
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");
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);
400 #endif //NL_USE_THREAD_COTASK
403 void CCoTask::yield()
405 NL_CT_DEBUG("CoTask : task %p yield", this);
407 nlassert(CCurrentCoTask::getInstance().getCurrentTask() == this);
408 #if defined(NL_USE_THREAD_COTASK)
410 // set the yield flag
411 _PImpl
->_TaskHasYield
= true;
414 NL_CT_DEBUG("CoTask : yield() task %p leaving mutex", this);
415 _PImpl
->_TaskMutex
.leave();
417 // now, wait until the resume flag is set
420 // give up the time slice to the master thread
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();
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);
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);
464 #if defined(NL_USE_THREAD_COTASK)
466 // set the resume flag to true
467 _PImpl
->_ResumeTask
= true;
468 _PImpl
->_TaskHasYield
= false;
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
)
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
481 // give up the time slice to the co task
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
498 // the task has yield
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);
514 #endif //NL_USE_THREAD_COTASK
517 NL_CT_DEBUG("CoTask : task %p has yield", this);
520 /// wait until the task terminate
523 NL_CT_DEBUG("CoTask : waiting for task %p to terminate", this);
524 // resume the task until termination
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
537 NL_CT_DEBUG("CoTask : TCoTaskData::run() task %p entering mutex", this);
538 // Acquire the task mutex
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;
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
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;
580 void CCoTask::releaseInstance()
582 CCurrentCoTask::releaseInstance();
585 } // namespace NLMISC