Line data Source code
1 0 : /*
2 :
3 : OOAsyncQueue.m
4 : By Jens Ayton
5 :
6 :
7 : Copyright (C) 2007-2013 Jens Ayton
8 :
9 : Permission is hereby granted, free of charge, to any person obtaining a copy
10 : of this software and associated documentation files (the "Software"), to deal
11 : in the Software without restriction, including without limitation the rights
12 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 : copies of the Software, and to permit persons to whom the Software is
14 : furnished to do so, subject to the following conditions:
15 :
16 : The above copyright notice and this permission notice shall be included in all
17 : copies or substantial portions of the Software.
18 :
19 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 : SOFTWARE.
26 :
27 : */
28 :
29 : #include <assert.h>
30 :
31 : #import "OOAsyncQueue.h"
32 : #import "OOFunctionAttributes.h"
33 : #import "OOLogging.h"
34 : #import "NSThreadOOExtensions.h"
35 : #include <stdlib.h>
36 :
37 : #ifndef OO_BUGGY_PTHREADS
38 : #if OOLITE_WINDOWS
39 : // Maybe add #if OOLITE_64_BIT too?
40 : #define OO_BUGGY_PTHREADS 1
41 : #else
42 0 : #define OO_BUGGY_PTHREADS 0
43 : #endif
44 : #endif
45 :
46 0 : enum
47 : {
48 : kConditionNoData = 1,
49 : kConditionQueuedData,
50 : kConditionDead
51 : };
52 :
53 :
54 0 : enum
55 : {
56 : kMaxPoolElements = 5
57 : };
58 :
59 :
60 0 : typedef struct OOAsyncQueueElement OOAsyncQueueElement;
61 0 : struct OOAsyncQueueElement
62 : {
63 0 : OOAsyncQueueElement *next;
64 0 : id object;
65 : };
66 :
67 :
68 0 : OOINLINE OOAsyncQueueElement *AllocElement(void)
69 : {
70 : return malloc(sizeof (OOAsyncQueueElement));
71 : }
72 :
73 :
74 0 : OOINLINE void FreeElement(OOAsyncQueueElement *element)
75 : {
76 : free(element);
77 : }
78 :
79 :
80 : @interface OOAsyncQueue (OOPrivate)
81 :
82 0 : - (void)doEmptyQueueWithAcquiredLock;
83 0 : - (id)doDequeAndUnlockWithAcquiredLock;
84 0 : - (void)recycleElementWithAcquiredLock:(OOAsyncQueueElement *)element;
85 :
86 : @end
87 :
88 :
89 : @implementation OOAsyncQueue
90 :
91 0 : - (id)init
92 : {
93 : self = [super init];
94 : if (self != nil)
95 : {
96 : _lock = [[NSConditionLock alloc] initWithCondition:kConditionNoData];
97 : [_lock setName:@"OOAsyncQueue lock"];
98 : if (_lock == nil)
99 : {
100 : [self release];
101 : self = nil;
102 : }
103 : }
104 :
105 : return self;
106 : }
107 :
108 :
109 0 : - (void)dealloc
110 : {
111 : OOAsyncQueueElement *element = NULL;
112 :
113 : [_lock lock];
114 :
115 : if (_elemCount != 0)
116 : {
117 : OOLogWARN(@"asyncQueue.nonEmpty", @"%@ deallocated while non-empty, flushing.", self);
118 : [self doEmptyQueueWithAcquiredLock];
119 : }
120 :
121 : // Free element pool.
122 : while (_pool != NULL)
123 : {
124 : element = _pool;
125 : _pool = element->next;
126 : free(element);
127 : }
128 :
129 : [_lock unlockWithCondition:kConditionDead];
130 : [_lock release];
131 :
132 : [super dealloc];
133 : }
134 :
135 :
136 0 : - (NSString *)description
137 : {
138 : // Don't bother locking, the value would be out of date immediately anyway.
139 : return [NSString stringWithFormat:@"<%@ %p>{%u elements}", [self class], self, _elemCount];
140 : }
141 :
142 :
143 : - (BOOL)enqueue:(id)object
144 : {
145 : OOAsyncQueueElement *element = NULL;
146 : BOOL success = NO;
147 :
148 : if (EXPECT_NOT(object == nil)) return NO;
149 :
150 : [_lock lock];
151 :
152 : // Get an element.
153 : if (_pool != NULL)
154 : {
155 : element = _pool;
156 : _pool = element->next;
157 : --_poolCount;
158 : }
159 : else
160 : {
161 : element = AllocElement();
162 : if (element == NULL) goto FAIL;
163 : }
164 :
165 : // Set element fields.
166 : element->object = [object retain];
167 : element->next = NULL;
168 :
169 : // Insert in queue.
170 : if (_head == NULL)
171 : {
172 : // Queue was empty, element is entire queue.
173 : _head = _tail = element;
174 : element->next = NULL;
175 : assert(_elemCount == 0);
176 : _elemCount = 1;
177 : }
178 : else
179 : {
180 : assert(_tail != NULL);
181 : assert(_tail->next == NULL);
182 : assert(_elemCount != 0);
183 :
184 : _tail->next = element;
185 : _tail = element;
186 : ++_elemCount;
187 : }
188 : success = YES;
189 :
190 : FAIL:
191 : [_lock unlockWithCondition:kConditionQueuedData];
192 : return success;
193 : }
194 :
195 :
196 : - (id)dequeue
197 : {
198 : [_lock lockWhenCondition:kConditionQueuedData];
199 : return [self doDequeAndUnlockWithAcquiredLock];
200 : }
201 :
202 :
203 : - (id)tryDequeue
204 : {
205 : #if OO_BUGGY_PTHREADS
206 : /* pthread_mutex_trylock is buggy on 64-bit windows with the pthread
207 : * library in use, so avoid doing things which use it This is a little
208 : * more blocking, but no thread should be hanging on to _lock for very
209 : * long, so hopefully it won't be noticeable.
210 : */
211 : [_lock lock];
212 : if ([_lock condition] != kConditionQueuedData)
213 : {
214 : [_lock unlock];
215 : return NO;
216 : }
217 : #else
218 : // Mac and Linux can do it properly
219 : if (![_lock tryLockWhenCondition:kConditionQueuedData]) return nil;
220 : #endif
221 : return [self doDequeAndUnlockWithAcquiredLock];
222 : }
223 :
224 :
225 : - (BOOL)empty
226 : {
227 : return _head != NULL;
228 : }
229 :
230 :
231 : - (unsigned)count
232 : {
233 : return _elemCount;
234 : }
235 :
236 :
237 : - (void)emptyQueue
238 : {
239 : [_lock lock];
240 : [self doEmptyQueueWithAcquiredLock];
241 :
242 : assert(_head == NULL && _tail == NULL && _elemCount == 0);
243 : [_lock unlockWithCondition:kConditionNoData];
244 : }
245 :
246 : @end
247 :
248 :
249 : @implementation OOAsyncQueue (OOPrivate)
250 :
251 : - (void)doEmptyQueueWithAcquiredLock
252 : {
253 : OOAsyncQueueElement *element = NULL;
254 :
255 : // Loop over queue.
256 : while (_head != NULL)
257 : {
258 : // Dequeue element.
259 : element = _head;
260 : _head = _head->next;
261 : --_elemCount;
262 :
263 : // We don't need the payload any longer.
264 : [element->object release];
265 :
266 : // Or the element.
267 : [self recycleElementWithAcquiredLock:element];
268 : }
269 :
270 : _tail = NULL;
271 : }
272 :
273 :
274 : - (id)doDequeAndUnlockWithAcquiredLock
275 : {
276 : OOAsyncQueueElement *element = NULL;
277 : id result;
278 :
279 : if (_head == NULL)
280 : {
281 : // Can happen if you enter debugger.
282 : return nil;
283 : }
284 :
285 : // assert(_head != NULL);
286 :
287 : // Dequeue element.
288 : element = _head;
289 : _head = _head->next;
290 : if (_head == NULL) _tail = NULL;
291 : --_elemCount;
292 :
293 : // Unpack payload.
294 : result = [element->object autorelease];
295 :
296 : // Recycle element.
297 : [self recycleElementWithAcquiredLock:element];
298 :
299 : // Ensure sane status.
300 : assert((_head == NULL && _tail == NULL && _elemCount == 0) || (_head != NULL && _tail != NULL && _elemCount != 0));
301 :
302 : // Unlock with appropriate state.
303 : [_lock unlockWithCondition:(_head == NULL) ? kConditionNoData : kConditionQueuedData];
304 :
305 : return result;
306 : }
307 :
308 :
309 : - (void)recycleElementWithAcquiredLock:(OOAsyncQueueElement *)element
310 : {
311 : if (_poolCount < kMaxPoolElements)
312 : {
313 : // Add to pool for reuse.
314 : element->next = _pool;
315 : _pool = element;
316 : ++_poolCount;
317 : }
318 : else
319 : {
320 : // Delete.
321 : FreeElement(element);
322 : }
323 : }
324 :
325 : @end
|