1 /* sqExternalSemaphores.c
2 * Cross-platform thread-safe external semaphore signalling.
4 * Authors: Eliot Miranda & Brad Fowlow
6 * Copyright (C) 2009 by Teleplace, Inc.
10 * This file is part of Squeak.
12 * Permission is hereby granted, free of charge, to any person obtaining a
13 * copy of this software and associated documentation files (the "Software"),
14 * to deal in the Software without restriction, including without limitation
15 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 * and/or sell copies of the Software, and to permit persons to whom the
17 * Software is furnished to do so, subject to the following conditions:
19 * The above copyright notice and this permission notice shall be included in
20 * all copies or substantial portions of the Software.
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28 * DEALINGS IN THE SOFTWARE.
33 #include "sqAtomicOps.h"
34 #include "sqMemoryFence.h"
36 /* This implements "lock-free" signalling of external semaphores where there is
37 * no lock between the signal responder (the VM) and signal requestors, but
38 * there may be spin-locking between signal requestors, depending on the implem-
39 * entation of atomicAddConst.
41 * Freedom from locks is very helpful in making the QAudioPlugin function on
42 * linux, where the absence of thread priorities for non-setuid programs means
43 * we cannot run the QAudioPlugin's ticker in a separate thread and must instead
44 * derive it from the interval-timer based signal-driven (software interrupt)
45 * heartbeat. If the external semaphore state is locked while the VM is
46 * responding to external semaphore signal requests and the heartbeat interrupts
47 * causing a locking request for an external semaphore signal request then the
48 * system will deadlock, since the interrupt occurs in the VM thread.
50 * Lock freedom is achieved by having an array of request counters, and an array
51 * of response counters, one per external semaphore index. To request a signal
52 * the requests are locked and the relevant request is incremented. To respond
53 * to a request the VM increments the corresponding response until it matches
54 * the request, signalling the associated semaphore on each increment.
58 sqOSThread ioVMThread; /* initialized in the various <plat>/vm/sqFooMain.c */
60 extern void forceInterruptCheck(void);
61 extern sqInt doSignalSemaphoreWithIndex(sqInt semaIndex);
63 static volatile int sigIndexLow = 0;
64 static volatile int sigIndexHigh = 0;
65 static volatile int sigLock = 0;
68 /* do we need to dynamically grow the queue? */
69 #define SQ_DYNAMIC_QUEUE_SIZE 0
71 /* a hardcoded limit for static-sized queue */
72 #define SQ_SIGNAL_QUEUE_SIZE 512
74 #if SQ_DYNAMIC_QUEUE_SIZE
75 static int maxPendingSignals = 0;
76 static volatile sqInt * signalQueue = 0;
78 #define maxPendingSignals SQ_SIGNAL_QUEUE_SIZE
79 static sqInt signalQueue [maxPendingSignals];
82 static inline int hasPendingSignals(void) {
83 return sigIndexLow != sigIndexHigh;
86 static inline int maxQueueSize(void) {
87 return maxPendingSignals;
90 /* return a successive index in queue, wrap around in round-robin style */
91 static inline int succIndex(int index) {
92 return ((index + 1) == maxQueueSize()) ? 0 : index + 1;
98 static inline void lockSignalQueue()
101 /* spin to obtain a lock */
105 sqCompareAndSwapRes(sigLock, 0, 1, old );
110 static inline void unlockSignalQueue() {
114 /* Setting this at any time other than start-up can potentially lose requests.
115 * i.e. during the realloc new storage is allocated, the old contents are copied
116 * and then pointersd are switched. Requests occurring during copying won't
117 * be seen if they occur to indices already copied.
118 * We could make this safer in the linux case by disabling interrupts, but
119 * there is little point. The intended use is to set the table to some adequate
120 * maximum at start-up and avoid locking altogether.
123 void ioGrowSignalQueue( int n ) {
124 #if SQ_DYNAMIC_QUEUE_SIZE // ignore, if queue size is static
127 if (maxPendingSignals < n) {
128 extern sqInt highBit(sqInt);
129 int sz = 1 << highBit(n-1);
133 sqInt * newBuf = realloc(signalQueue, sz * sizeof(sqInt));
135 /* we should lock queue when growing, so nobody can access the queue buffer when we mutate the pointer */
137 signalQueue = newBuf;
140 maxPendingSignals = sz;
147 ioInitExternalSemaphores(void)
149 ioGrowSignalQueue(SQ_SIGNAL_QUEUE_SIZE);
152 /* Signal the external semaphore with the given index. Answer non-zero on
153 * success, zero otherwise. This function is (should be) thread-safe;
154 * multiple threads may attempt to signal the same semaphore without error.
155 * An index of zero should be and is silently ignored.
157 * (sig) As well as negative index.
160 signalSemaphoreWithIndex(sqInt index)
164 /* An index of zero should be and is silently ignored. */
168 /* we must use the locking semantics to avoid ABA problem on writing a semaphore index to queue,
169 so there is no chance for fetching thread to observe queue in inconsistent state.
173 /* check for queue overflow */
174 next = succIndex(sigIndexHigh);
175 if (next == sigIndexLow ) {
177 #if SQ_DYNAMIC_QUEUE_SIZE
180 ioGrowSignalQueue( maxPendingSignals + 100);
181 return signalSemaphoreWithIndex(index);
185 // error if queue size is static (perhaps better would be to sleep for a while and retry?)
186 error("External semaphore signal queue overflow");
190 signalQueue[sigIndexHigh] = index;
191 /* make sure semaphore index is written before we advance sigIndexHigh */
198 forceInterruptCheck();
203 static inline sqInt fetchQueueItem()
205 // if queue size is dynamic, we should lock while accessing signalQueue
206 #if SQ_DYNAMIC_QUEUE_SIZE
209 sqInt result = signalQueue[sigIndexLow];
210 #if SQ_DYNAMIC_QUEUE_SIZE
218 /* Signal any external semaphores for which signal request(s) are pending in queue.
219 * Answer whether a context switch occurred.
220 * sigIndexHigh may advance during processing, which is not big deal,
221 * since we flushing the queue anyways.
225 doSignalExternalSemaphores(int externalSemaphoreTableSize)
229 while (hasPendingSignals()) {
230 if(doSignalSemaphoreWithIndex(fetchQueueItem()))
232 sigIndexLow = succIndex(sigIndexLow);
238 #if FOR_SQUEAK_VM_TESTS
239 /* see e.g. tests/sqExternalSemaphores/unixmain.c */
241 allRequestsAreAnswered(int externalSemaphoreTableSize)
243 /* yes, it is, otherwise we will get a queue overflow error ;) */
246 #endif /* FOR_SQUEAK_VM_TESTS */
250 /* Following two are left for compatibility with language side */
252 void ioSetMaxExtSemTableSize(int n) {
256 int ioGetMaxExtSemTableSize(void) {
257 // answer an arbitrary large number.. to not confuse image side about it
259 /* return maxQueueSize(); */