1 /*
2 * mutex.c - Scheme-level synchronization devices
3 *
4 * Copyright (c) 2000-2020 Shiro Kawai <shiro@acm.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the authors nor the names of its contributors
18 * may be used to endorse or promote products derived from this
19 * software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
27 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34 #include <math.h>
35 #include <gauche.h>
36 #include <gauche/class.h>
37 #include <gauche/exception.h>
38 #include "threads.h"
39
40 /*=====================================================
41 * Mutex
42 */
43
44 static ScmObj mutex_allocate(ScmClass *klass, ScmObj initargs);
45 static void mutex_print(ScmObj mutex, ScmPort *port, ScmWriteContext *ctx);
46
47 static ScmClass *default_cpl[] = {
48 SCM_CLASS_STATIC_PTR(Scm_TopClass), NULL
49 };
50
51 SCM_DEFINE_BASE_CLASS(Scm_MutexClass, ScmMutex,
52 mutex_print, NULL, NULL, mutex_allocate,
53 default_cpl);
54
mutex_finalize(ScmObj obj,void * data SCM_UNUSED)55 static void mutex_finalize(ScmObj obj, void* data SCM_UNUSED)
56 {
57 ScmMutex *mutex = SCM_MUTEX(obj);
58 SCM_INTERNAL_MUTEX_DESTROY(mutex->mutex);
59 SCM_INTERNAL_COND_DESTROY(mutex->cv);
60 }
61
mutex_allocate(ScmClass * klass,ScmObj initargs SCM_UNUSED)62 static ScmObj mutex_allocate(ScmClass *klass, ScmObj initargs SCM_UNUSED)
63 {
64 ScmMutex *mutex = SCM_NEW_INSTANCE(ScmMutex, klass);
65 SCM_INTERNAL_MUTEX_INIT(mutex->mutex);
66 SCM_INTERNAL_COND_INIT(mutex->cv);
67 Scm_RegisterFinalizer(SCM_OBJ(mutex), mutex_finalize, NULL);
68 mutex->name = SCM_FALSE;
69 mutex->specific = SCM_UNDEFINED;
70 mutex->locked = FALSE;
71 mutex->owner = NULL;
72 mutex->locker_proc = mutex->unlocker_proc = SCM_FALSE;
73 return SCM_OBJ(mutex);
74 }
75
mutex_print(ScmObj obj,ScmPort * port,ScmWriteContext * ctx SCM_UNUSED)76 static void mutex_print(ScmObj obj, ScmPort *port, ScmWriteContext *ctx SCM_UNUSED)
77 {
78 ScmMutex *mutex = SCM_MUTEX(obj);
79
80 (void)SCM_INTERNAL_MUTEX_LOCK(mutex->mutex);
81 int locked = mutex->locked;
82 ScmVM *vm = mutex->owner;
83 ScmObj name = mutex->name;
84 (void)SCM_INTERNAL_MUTEX_UNLOCK(mutex->mutex);
85
86 if (SCM_FALSEP(name)) Scm_Printf(port, "#<mutex %p ", mutex);
87 else Scm_Printf(port, "#<mutex %S ", name);
88 if (locked) {
89 if (vm) {
90 if (vm->state == SCM_VM_TERMINATED) {
91 Scm_Printf(port, "unlocked/abandoned>");
92 } else {
93 Scm_Printf(port, "locked/owned by %S>", vm);
94 }
95 } else {
96 Scm_Printf(port, "locked/not-owned>");
97 }
98 } else {
99 Scm_Printf(port, "unlocked/not-abandoned>");
100 }
101 }
102
103 /*
104 * Accessors
105 */
106 static ScmObj sym_not_owned;
107 static ScmObj sym_abandoned;
108 static ScmObj sym_not_abandoned;
109
mutex_state_get(ScmMutex * mutex)110 static ScmObj mutex_state_get(ScmMutex *mutex)
111 {
112 ScmObj r;
113 (void)SCM_INTERNAL_MUTEX_LOCK(mutex->mutex);
114 if (mutex->locked) {
115 if (mutex->owner) {
116 if (mutex->owner->state == SCM_VM_TERMINATED) r = sym_abandoned;
117 else r = SCM_OBJ(mutex->owner);
118 } else {
119 r = sym_not_owned;
120 }
121 } else {
122 r = sym_not_abandoned;
123 }
124 (void)SCM_INTERNAL_MUTEX_UNLOCK(mutex->mutex);
125 return r;
126 }
127
mutex_name_get(ScmMutex * mutex)128 static ScmObj mutex_name_get(ScmMutex *mutex)
129 {
130 return mutex->name;
131 }
132
mutex_name_set(ScmMutex * mutex,ScmObj name)133 static void mutex_name_set(ScmMutex *mutex, ScmObj name)
134 {
135 mutex->name = name;
136 }
137
mutex_specific_get(ScmMutex * mutex)138 static ScmObj mutex_specific_get(ScmMutex *mutex)
139 {
140 return mutex->specific;
141 }
142
mutex_specific_set(ScmMutex * mutex,ScmObj value)143 static void mutex_specific_set(ScmMutex *mutex, ScmObj value)
144 {
145 mutex->specific = value;
146 }
147
148 static ScmClassStaticSlotSpec mutex_slots[] = {
149 SCM_CLASS_SLOT_SPEC("name", mutex_name_get, mutex_name_set),
150 SCM_CLASS_SLOT_SPEC("state", mutex_state_get, NULL),
151 SCM_CLASS_SLOT_SPEC("specific", mutex_specific_get, mutex_specific_set),
152 SCM_CLASS_SLOT_SPEC_END()
153 };
154
155 /*
156 * Make mutex
157 */
Scm_MakeMutex(ScmObj name)158 ScmObj Scm_MakeMutex(ScmObj name)
159 {
160 ScmObj m = mutex_allocate(SCM_CLASS_MUTEX, SCM_NIL);
161 SCM_MUTEX(m)->name = name;
162 return m;
163 }
164
165 /*
166 * Lock and unlock mutex
167 */
168
Scm_MutexLock(ScmMutex * mutex,ScmObj timeout,ScmVM * owner)169 ScmObj Scm_MutexLock(ScmMutex *mutex, ScmObj timeout, ScmVM *owner)
170 {
171 #ifdef GAUCHE_HAS_THREADS
172 ScmTimeSpec ts;
173 ScmObj r = SCM_TRUE;
174 ScmVM * volatile abandoned = NULL;
175 volatile int intr = FALSE;
176
177 ScmTimeSpec *pts = Scm_GetTimeSpec(timeout, &ts);
178 SCM_INTERNAL_MUTEX_SAFE_LOCK_BEGIN(mutex->mutex);
179 while (mutex->locked) {
180 if (mutex->owner && mutex->owner->state == SCM_VM_TERMINATED) {
181 abandoned = mutex->owner;
182 mutex->locked = FALSE;
183 break;
184 }
185 if (pts) {
186 int tr = SCM_INTERNAL_COND_TIMEDWAIT(mutex->cv, mutex->mutex, pts);
187 if (tr == SCM_INTERNAL_COND_TIMEDOUT) { r = SCM_FALSE; break; }
188 else if (tr == SCM_INTERNAL_COND_INTR) { intr = TRUE; break; }
189 } else {
190 SCM_INTERNAL_COND_WAIT(mutex->cv, mutex->mutex);
191 }
192 }
193 if (SCM_TRUEP(r)) {
194 mutex->locked = TRUE;
195 mutex->owner = owner;
196 }
197 SCM_INTERNAL_MUTEX_SAFE_LOCK_END();
198 if (intr) Scm_SigCheck(Scm_VM());
199 if (abandoned) {
200 ScmObj exc
201 = Scm_MakeThreadException(SCM_CLASS_ABANDONED_MUTEX_EXCEPTION,
202 (ScmVM*)abandoned);
203 SCM_THREAD_EXCEPTION(exc)->data = SCM_OBJ(mutex);
204 r = Scm_Raise(exc, 0);
205 }
206 return r;
207 #else /* !GAUCHE_HAS_THREADS */
208 return SCM_TRUE; /* dummy */
209 #endif /* !GAUCHE_HAS_THREADS */
210 }
211
Scm_MutexUnlock(ScmMutex * mutex,ScmConditionVariable * cv,ScmObj timeout)212 ScmObj Scm_MutexUnlock(ScmMutex *mutex, ScmConditionVariable *cv, ScmObj timeout)
213 {
214 volatile ScmObj r = SCM_TRUE;
215 #ifdef GAUCHE_HAS_THREADS
216 ScmTimeSpec ts;
217 volatile int intr = FALSE;
218
219 ScmTimeSpec *pts = Scm_GetTimeSpec(timeout, &ts);
220 SCM_INTERNAL_MUTEX_SAFE_LOCK_BEGIN(mutex->mutex);
221 mutex->locked = FALSE;
222 mutex->owner = NULL;
223 SCM_INTERNAL_COND_SIGNAL(mutex->cv);
224 if (cv) {
225 if (pts) {
226 int tr = SCM_INTERNAL_COND_TIMEDWAIT(cv->cv, mutex->mutex, pts);
227 if (tr == SCM_INTERNAL_COND_TIMEDOUT) { r = SCM_FALSE; }
228 else if (tr == SCM_INTERNAL_COND_INTR) { intr = TRUE; }
229 } else {
230 SCM_INTERNAL_COND_WAIT(cv->cv, mutex->mutex);
231 }
232 }
233 SCM_INTERNAL_MUTEX_SAFE_LOCK_END();
234 if (intr) Scm_SigCheck(Scm_VM());
235 #endif /* GAUCHE_HAS_THREADS */
236 return r;
237 }
238
mutex_locker(ScmObj * args SCM_UNUSED,int argc SCM_UNUSED,void * mutex)239 static ScmObj mutex_locker(ScmObj *args SCM_UNUSED,
240 int argc SCM_UNUSED,
241 void *mutex)
242 {
243 return Scm_MutexLock((ScmMutex*)mutex, SCM_FALSE, Scm_VM());
244 }
245
246
Scm_MutexLocker(ScmMutex * mutex)247 ScmObj Scm_MutexLocker(ScmMutex *mutex)
248 {
249 ScmObj p = mutex->locker_proc;
250 if (SCM_FALSEP(p)) {
251 /* safe; race is ok here */
252 p = Scm_MakeSubr(mutex_locker, (void*)mutex, 0, 0, SCM_FALSE);
253 mutex->locker_proc = p;
254 }
255 return p;
256 }
257
mutex_unlocker(ScmObj * args SCM_UNUSED,int argc SCM_UNUSED,void * mutex)258 static ScmObj mutex_unlocker(ScmObj *args SCM_UNUSED,
259 int argc SCM_UNUSED,
260 void *mutex)
261 {
262 return Scm_MutexUnlock((ScmMutex*)mutex, NULL, SCM_FALSE);
263 }
264
Scm_MutexUnlocker(ScmMutex * mutex)265 ScmObj Scm_MutexUnlocker(ScmMutex *mutex)
266 {
267 ScmObj p = mutex->unlocker_proc;
268 if (SCM_FALSEP(p)) {
269 /* safe; race is ok here */
270 p = Scm_MakeSubr(mutex_unlocker, (void*)mutex, 0, 0, SCM_FALSE);
271 mutex->unlocker_proc = p;
272 }
273 return p;
274 }
275
276 /*=====================================================
277 * Condition variable
278 */
279
280 static ScmObj cv_allocate(ScmClass *klass, ScmObj initargs);
281 static void cv_print(ScmObj cv, ScmPort *port, ScmWriteContext *ctx);
282
283 SCM_DEFINE_BASE_CLASS(Scm_ConditionVariableClass, ScmConditionVariable,
284 cv_print, NULL, NULL, cv_allocate,
285 default_cpl);
286
cv_finalize(ScmObj obj,void * data SCM_UNUSED)287 static void cv_finalize(ScmObj obj, void *data SCM_UNUSED)
288 {
289 ScmConditionVariable *cv = SCM_CONDITION_VARIABLE(obj);
290 SCM_INTERNAL_COND_DESTROY(cv->cv);
291 }
292
cv_allocate(ScmClass * klass,ScmObj initargs SCM_UNUSED)293 static ScmObj cv_allocate(ScmClass *klass, ScmObj initargs SCM_UNUSED)
294 {
295 ScmConditionVariable *cv = SCM_NEW_INSTANCE(ScmConditionVariable, klass);
296 SCM_INTERNAL_COND_INIT(cv->cv);
297 Scm_RegisterFinalizer(SCM_OBJ(cv), cv_finalize, NULL);
298 cv->name = SCM_FALSE;
299 cv->specific = SCM_UNDEFINED;
300 return SCM_OBJ(cv);
301 }
302
cv_print(ScmObj obj,ScmPort * port,ScmWriteContext * ctx SCM_UNUSED)303 static void cv_print(ScmObj obj, ScmPort *port, ScmWriteContext *ctx SCM_UNUSED)
304 {
305 ScmConditionVariable *cv = SCM_CONDITION_VARIABLE(obj);
306 ScmObj name = cv->name;
307 if (SCM_FALSEP(name)) Scm_Printf(port, "#<condition-variable %p>", cv);
308 else Scm_Printf(port, "#<condition-variable %S>", name);
309 }
310
311 /*
312 * Accessors
313 */
314
cv_name_get(ScmConditionVariable * cv)315 static ScmObj cv_name_get(ScmConditionVariable *cv)
316 {
317 return cv->name;
318 }
319
cv_name_set(ScmConditionVariable * cv,ScmObj name)320 static void cv_name_set(ScmConditionVariable *cv, ScmObj name)
321 {
322 cv->name = name;
323 }
324
cv_specific_get(ScmConditionVariable * cv)325 static ScmObj cv_specific_get(ScmConditionVariable *cv)
326 {
327 return cv->specific;
328 }
329
cv_specific_set(ScmConditionVariable * cv,ScmObj val)330 static void cv_specific_set(ScmConditionVariable *cv, ScmObj val)
331 {
332 cv->specific = val;
333 }
334
335 static ScmClassStaticSlotSpec cv_slots[] = {
336 SCM_CLASS_SLOT_SPEC("name", cv_name_get, cv_name_set),
337 SCM_CLASS_SLOT_SPEC("specific", cv_specific_get, cv_specific_set),
338 SCM_CLASS_SLOT_SPEC_END()
339 };
340
341 /*
342 * Make condition variable
343 */
Scm_MakeConditionVariable(ScmObj name)344 ScmObj Scm_MakeConditionVariable(ScmObj name)
345 {
346 ScmObj cv = cv_allocate(SCM_CLASS_CONDITION_VARIABLE, SCM_NIL);
347 SCM_CONDITION_VARIABLE(cv)->name = name;
348 return cv;
349 }
350
Scm_ConditionVariableSignal(ScmConditionVariable * cond)351 ScmObj Scm_ConditionVariableSignal(ScmConditionVariable *cond)
352 {
353 SCM_INTERNAL_COND_SIGNAL(cond->cv);
354 return SCM_UNDEFINED;
355 }
356
Scm_ConditionVariableBroadcast(ScmConditionVariable * cond)357 ScmObj Scm_ConditionVariableBroadcast(ScmConditionVariable *cond)
358 {
359 SCM_INTERNAL_COND_BROADCAST(cond->cv);
360 return SCM_UNDEFINED;
361 }
362
363 /*
364 * Initialization
365 */
366
Scm_Init_mutex(ScmModule * mod)367 void Scm_Init_mutex(ScmModule *mod)
368 {
369 sym_not_owned = SCM_INTERN("not-owned");
370 sym_abandoned = SCM_INTERN("abandoned");
371 sym_not_abandoned = SCM_INTERN("not-abandoned");
372 Scm_InitStaticClass(&Scm_MutexClass, "<mutex>", mod, mutex_slots, 0);
373 Scm_InitStaticClass(&Scm_ConditionVariableClass, "<condition-variable>", mod, cv_slots, 0);
374 }
375