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