1 /** @file thread.c  Thread object.
2 
3 @authors Copyright (c) 2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 
5 @par License
6 
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9 
10 1. Redistributions of source code must retain the above copyright notice, this
11    list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright notice,
13    this list of conditions and the following disclaimer in the documentation
14    and/or other materials provided with the distribution.
15 
16 <small>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</small>
26 */
27 
28 #include "the_Foundation/thread.h"
29 #include "the_Foundation/blockhash.h"
30 #include "the_Foundation/mutex.h"
31 
32 /* garbage.c */
33 void deinitForThread_Garbage_(void);
34 
35 iDefinePlainKeyBlockHash(ThreadHash, ThreadId, Thread)
36 
37 /*-------------------------------------------------------------------------------------*/
38 
39 iDefineLockableObject(ThreadHash)
40 
41 static iLockableThreadHash *runningThreads_;
42 
deinit_Threads_(void)43 void deinit_Threads_(void) {
44     delete_LockableThreadHash(runningThreads_);
45     runningThreads_ = NULL;
46 }
47 
init_Threads_(void)48 static iLockableThreadHash *init_Threads_(void) {
49     if (!runningThreads_) {
50         runningThreads_ = new_LockableThreadHash();
51     }
52     return runningThreads_;
53 }
54 
init_Threads(void)55 void init_Threads(void) {
56     init_Threads_();
57 }
58 
finish_Thread_(iThread * d)59 void finish_Thread_(iThread *d) { /* called from threadpool.c as well */
60     iGuardMutex(&d->mutex, {
61         d->state = finished_ThreadState;
62         signalAll_Condition(&d->finishedCond);
63     });
64     iNotifyAudience(d, finished, ThreadFinished);
65     iRecycle();
66 }
67 
run_Threads_(void * arg)68 static int run_Threads_(void *arg) {
69     iThread *d = (iThread *) arg;
70     ref_Object(d);
71     if (!isEmpty_String(&d->name)) {
72 #if defined (iPlatformApple)
73         pthread_setname_np(cstr_String(&d->name));
74 #endif
75 #if defined (iPlatformLinux)
76         pthread_setname_np(d->id, cstr_String(&d->name));
77 #endif
78     }
79     if (d->flags & terminationEnabled_ThreadFlag) {
80 #if defined (iHavePThread)
81         pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
82 #endif
83     }
84     d->result = d->run(d);
85     /* Deregister the thread since it's stopping. */
86     iGuard(runningThreads_, remove_ThreadHash(runningThreads_->value, &d->id));
87     /* Notify observers that the thread is done. */
88     finish_Thread_(d);
89     deref_Object(d);
90     deinitForThread_Garbage_();
91     thrd_exit(0); // thread-local data gets deleted
92     return 0;
93 }
94 
95 /*-------------------------------------------------------------------------------------*/
96 
97 iDefineClass(Thread)
98 iDefineObjectConstructionArgs(Thread, (iThreadRunFunc run), run)
99 
init_Thread(iThread * d,iThreadRunFunc run)100 void init_Thread(iThread *d, iThreadRunFunc run) {
101     init_Mutex(&d->mutex);
102     init_Condition(&d->finishedCond);
103     init_String(&d->name);
104     d->result = 0;
105     d->run = run;
106     d->id = 0;
107     d->flags = 0;
108     d->userData = NULL;
109     d->state = created_ThreadState;
110     d->finished = NULL;
111 }
112 
deinit_Thread(iThread * d)113 void deinit_Thread(iThread *d) {
114     iAssert(d->state != running_ThreadState);
115     if (d->state == running_ThreadState) {
116         iWarning("[Thread] thread %p is being destroyed while still running\n", d);
117     }
118     delete_Audience(d->finished);
119     deinit_Condition(&d->finishedCond);
120     deinit_Mutex(&d->mutex);
121     deinit_String(&d->name);
122 }
123 
start_Thread(iThread * d)124 void start_Thread(iThread *d) {
125     iLockableThreadHash *threads = init_Threads_();
126     iGuardMutex(&d->mutex, {
127         iAssert(d->state == created_ThreadState);
128         d->state = running_ThreadState;
129         thrd_create(&d->id, run_Threads_, d);
130         iDebug("[Thread] created thread ID %p (%s)\n", d->id, cstr_String(&d->name));
131     });
132     /* Register this thread as a running thread. */
133     iGuard(threads, insert_ThreadHash(threads->value, &d->id, d));
134 }
135 
setName_Thread(iThread * d,const char * name)136 void setName_Thread(iThread *d, const char *name) {
137     iAssert(d->state == created_ThreadState);
138     setCStr_String(&d->name, name);
139 }
140 
setUserData_Thread(iThread * d,void * userData)141 void setUserData_Thread(iThread *d, void *userData) {
142     iGuardMutex(&d->mutex, d->userData = userData);
143 }
144 
setTerminationEnabled_Thread(iThread * d,iBool enable)145 void setTerminationEnabled_Thread(iThread *d, iBool enable) {
146     iChangeFlags(d->flags, terminationEnabled_ThreadFlag, enable);
147 }
148 
isRunning_Thread(const iThread * d)149 iBool isRunning_Thread(const iThread *d) {
150     iBool ret;
151     iGuardMutex(&d->mutex, ret = (d->state == running_ThreadState));
152     return ret;
153 }
154 
isFinished_Thread(const iThread * d)155 iBool isFinished_Thread(const iThread *d) {
156     iBool ret;
157     iGuardMutex(&d->mutex, ret = (d->state == finished_ThreadState));
158     return ret;
159 }
160 
name_Thread(const iThread * d)161 const iString *name_Thread(const iThread *d) {
162     return &d->name;
163 }
164 
userData_Thread(const iThread * d)165 void *userData_Thread(const iThread *d) {
166     return d->userData;
167 }
168 
result_Thread(const iThread * d)169 iThreadResult result_Thread(const iThread *d) {
170     join_Thread(iConstCast(iThread *, d));
171     iAssert(d->state == finished_ThreadState);
172     return d->result;
173 }
174 
join_Thread(iThread * d)175 void join_Thread(iThread *d) {
176     if (!d) return;
177     iAssert(d->id != thrd_current());
178     if (d->id == thrd_current()) return;
179     lock_Mutex(&d->mutex);
180     if (d->state == running_ThreadState) {
181         wait_Condition(&d->finishedCond, &d->mutex);
182     }
183     unlock_Mutex(&d->mutex);
184     thrd_join(d->id, NULL);
185 }
186 
terminate_Thread(iThread * d)187 void terminate_Thread(iThread *d) {
188     iAssert(d->flags & terminationEnabled_ThreadFlag);
189 #if defined (iHavePThread)
190     pthread_cancel(d->id);
191 #endif
192 }
193 
sleep_Thread(double seconds)194 void sleep_Thread(double seconds) {
195     iTime dur;
196     initSeconds_Time(&dur, seconds);
197     thrd_sleep(&dur.ts, NULL);
198 }
199 
current_Thread(void)200 iThread *current_Thread(void) {
201     iThread *d = NULL;
202     const iThreadId cur = thrd_current();
203     const iLockableThreadHash *threads = init_Threads_();
204     iGuard(threads, d = value_ThreadHash(threads->value, &cur));
205     return d;
206 }
207 
isCurrent_Thread(const iThread * d)208 iBool isCurrent_Thread(const iThread *d) {
209     iAssert(d);
210     return d->id == thrd_current();
211 }
212