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