1 /**
2  * @file concurrency.cpp
3  * Concurrency: threads, mutexes, semaphores.
4  *
5  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
6  * @authors Copyright © 2006-2013 Daniel Swanson <danij@dengine.net>
7  * @authors Copyright © 2006 Jamie Jones <jamie_jones_au@yahoo.com.au>
8  *
9  * @par License
10  * GPL: http://www.gnu.org/licenses/gpl.html
11  *
12  * <small>This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by the
14  * Free Software Foundation; either version 2 of the License, or (at your
15  * option) any later version. This program is distributed in the hope that it
16  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
17  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
18  * Public License for more details. You should have received a copy of the GNU
19  * General Public License along with this program; if not, write to the Free
20  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA</small>
22  */
23 
24 #include "de/concurrency.h"
25 #include <QMutex>
26 #include <QCoreApplication>
27 #include <QDebug>
28 #include <de/App>
29 #include <de/Time>
30 #include <de/Log>
31 #include <de/Garbage>
32 #include <assert.h>
33 
CallbackThread(systhreadfunc_t func,void * param)34 CallbackThread::CallbackThread(systhreadfunc_t func, void *param)
35     : _callback(func), _parm(param), _returnValue(0),
36       _exitStatus(DENG_THREAD_STOPPED_NORMALLY),
37       _terminationFunc(0)
38 {
39     //qDebug() << "CallbackThread:" << this << "created.";
40 
41     // Only used if the thread needs to be shut down forcibly.
42     setTerminationEnabled(true);
43 
44     // Cleanup at app exit time for threads whose exit value hasn't been checked.
45     connect(qApp, SIGNAL(destroyed()), this, SLOT(deleteNow()));
46 }
47 
~CallbackThread()48 CallbackThread::~CallbackThread()
49 {
50     if (isRunning())
51     {
52         //qDebug() << "CallbackThread:" << this << "forcibly stopping, deleting.";
53         terminate();
54         wait(1000);
55     }
56     else
57     {
58         //qDebug() << "CallbackThread:" << this << "deleted.";
59     }
60 }
61 
deleteNow()62 void CallbackThread::deleteNow()
63 {
64     delete this;
65 }
66 
run()67 void CallbackThread::run()
68 {
69     _exitStatus = DENG_THREAD_STOPPED_WITH_FORCE;
70 
71     try
72     {
73         if (_callback)
74         {
75             _returnValue = _callback(_parm);
76         }
77         _exitStatus = DENG_THREAD_STOPPED_NORMALLY;
78     }
79     catch (std::exception const &error)
80     {
81         LOG_AS("CallbackThread");
82         LOG_ERROR(QString("Uncaught exception: ") + error.what());
83         _returnValue = -1;
84         _exitStatus = DENG_THREAD_STOPPED_WITH_EXCEPTION;
85     }
86 
87     if (_terminationFunc)
88     {
89         _terminationFunc(_exitStatus);
90     }
91 
92     Garbage_ClearForThread();
93 
94     // No more log output from this thread.
95     de::Log::disposeThreadLog();
96 }
97 
exitValue() const98 int CallbackThread::exitValue() const
99 {
100     return _returnValue;
101 }
102 
exitStatus() const103 systhreadexitstatus_t CallbackThread::exitStatus() const
104 {
105     return _exitStatus;
106 }
107 
setTerminationFunc(void (* func)(systhreadexitstatus_t))108 void CallbackThread::setTerminationFunc(void (*func)(systhreadexitstatus_t))
109 {
110     _terminationFunc = func;
111 }
112 
113 /*void Sys_MarkAsMainThread(void)
114 {
115     // This is the main thread.
116     mainThreadId = Sys_CurrentThreadId();
117 }*/
118 
Sys_InMainThread(void)119 dd_bool Sys_InMainThread(void)
120 {
121     return de::App::inMainThread();
122 }
123 
Thread_Sleep(int milliseconds)124 void Thread_Sleep(int milliseconds)
125 {
126     de::TimeSpan::fromMilliSeconds(milliseconds).sleep();
127 }
128 
Sys_StartThread(systhreadfunc_t startpos,void * parm,void (* terminationFunc)(systhreadexitstatus_t))129 thread_t Sys_StartThread(systhreadfunc_t startpos, void *parm, void (*terminationFunc)(systhreadexitstatus_t))
130 {
131     CallbackThread *t = new CallbackThread(startpos, parm);
132     t->setTerminationFunc(terminationFunc);
133     t->start();
134     return t;
135 }
136 
Sys_StartThread(int (* startpos)(void *),void * parm,void (* terminationFunc)(systhreadexitstatus_t))137 thread_t Sys_StartThread(int (*startpos)(void *), void *parm, void (*terminationFunc)(systhreadexitstatus_t))
138 {
139     return Sys_StartThread(systhreadfunc_t(startpos), parm, terminationFunc);
140 }
141 
Thread_KillAbnormally(thread_t handle)142 void Thread_KillAbnormally(thread_t handle)
143 {
144     QThread *t = reinterpret_cast<QThread *>(handle);
145     if (!handle)
146     {
147         t = QThread::currentThread();
148     }
149     assert(t);
150     t->terminate();
151 }
152 
Thread_SetCallback(thread_t thread,void (* terminationFunc)(systhreadexitstatus_t))153 void Thread_SetCallback(thread_t thread, void (*terminationFunc)(systhreadexitstatus_t))
154 {
155     CallbackThread *t = reinterpret_cast<CallbackThread *>(thread);
156     DENG_ASSERT(t);
157     if (!t) return;
158 
159     t->setTerminationFunc(terminationFunc);
160 }
161 
Sys_WaitThread(thread_t handle,int timeoutMs,systhreadexitstatus_t * exitStatus)162 int Sys_WaitThread(thread_t handle, int timeoutMs, systhreadexitstatus_t *exitStatus)
163 {
164     if (!handle)
165     {
166         if (exitStatus) *exitStatus = DENG_THREAD_STOPPED_NORMALLY;
167         return 0;
168     }
169 
170     CallbackThread *t = reinterpret_cast<CallbackThread *>(handle);
171     assert(static_cast<QThread *>(t) != QThread::currentThread());
172     t->wait(timeoutMs);
173     if (!t->isFinished())
174     {
175         LOG_WARNING("Thread did not stop in time, forcibly killing it.");
176         if (exitStatus) *exitStatus = DENG_THREAD_STOPPED_WITH_FORCE;
177     }
178     else
179     {
180         if (exitStatus) *exitStatus = t->exitStatus();
181     }
182     t->deleteLater(); // get rid of it
183     return t->exitValue();
184 }
185 
Sys_ThreadId(thread_t handle)186 uint32_t Sys_ThreadId(thread_t handle)
187 {
188     QThread *t = reinterpret_cast<QThread *>(handle);
189     if (!t) t = QThread::currentThread();
190     return uint32_t(PTR2INT(t));
191 }
192 
Sys_CurrentThreadId(void)193 uint32_t Sys_CurrentThreadId(void)
194 {
195     return Sys_ThreadId(NULL /*this thread*/);
196 }
197 
198 /// @todo remove the name parameter
Sys_CreateMutex(const char *)199 mutex_t Sys_CreateMutex(const char *)
200 {
201     return new QMutex(QMutex::Recursive);
202 }
203 
Sys_DestroyMutex(mutex_t handle)204 void Sys_DestroyMutex(mutex_t handle)
205 {
206     if (handle)
207     {
208         delete reinterpret_cast<QMutex *>(handle);
209     }
210 }
211 
Sys_Lock(mutex_t handle)212 void Sys_Lock(mutex_t handle)
213 {
214     QMutex *m = reinterpret_cast<QMutex *>(handle);
215     assert(m != 0);
216     if (m)
217     {
218         m->lock();
219     }
220 }
221 
Sys_Unlock(mutex_t handle)222 void Sys_Unlock(mutex_t handle)
223 {
224     QMutex *m = reinterpret_cast<QMutex *>(handle);
225     assert(m != 0);
226     if (m)
227     {
228         m->unlock();
229     }
230 }
231