/src/rtpproxy/src/rtpp_queue.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2014-2019 Sippy Software, Inc., http://www.sippysoft.com |
3 | | * All rights reserved. |
4 | | * |
5 | | * Redistribution and use in source and binary forms, with or without |
6 | | * modification, are permitted provided that the following conditions |
7 | | * are met: |
8 | | * 1. Redistributions of source code must retain the above copyright |
9 | | * notice, this list of conditions and the following disclaimer. |
10 | | * 2. Redistributions in binary form must reproduce the above copyright |
11 | | * notice, this list of conditions and the following disclaimer in the |
12 | | * documentation and/or other materials provided with the distribution. |
13 | | * |
14 | | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
15 | | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
16 | | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
17 | | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
18 | | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
19 | | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
20 | | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
21 | | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
22 | | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
23 | | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
24 | | * SUCH DAMAGE. |
25 | | * |
26 | | */ |
27 | | |
28 | | #if defined(LINUX_XXX) && !defined(_GNU_SOURCE) |
29 | | /* Apparently needed for vasprintf(3) */ |
30 | | #define _GNU_SOURCE |
31 | | #endif |
32 | | |
33 | | #include <assert.h> |
34 | | #include <pthread.h> |
35 | | #include <stdarg.h> |
36 | | #include <stdio.h> |
37 | | #include <stdlib.h> |
38 | | #include <string.h> |
39 | | |
40 | | #include "rtpp_types.h" |
41 | | #include "rtpp_codeptr.h" |
42 | | #include "rtpp_refcnt.h" |
43 | | #include "rtpp_queue.h" |
44 | | #include "rtpp_mallocs.h" |
45 | | #include "rtpp_wi.h" |
46 | | #include "rtpp_debug.h" |
47 | | #include "rtpp_time.h" |
48 | | |
49 | | #define RTPQ_DEBUG 0 |
50 | | |
51 | | typedef struct { |
52 | | unsigned int buflen; |
53 | | unsigned int head; |
54 | | unsigned int tail; |
55 | | struct rtpp_wi *buffer[0]; |
56 | | } circ_buf_t; |
57 | | |
58 | | static int |
59 | | circ_buf_isempty(const circ_buf_t *c) |
60 | 435k | { |
61 | | |
62 | 435k | return (c->head == c->tail); /* if the head == tail, we don't have any data */ |
63 | 435k | } |
64 | | |
65 | | static int |
66 | | circ_buf_push(circ_buf_t *c, struct rtpp_wi *data) |
67 | 435k | { |
68 | 435k | unsigned int next; |
69 | | |
70 | 435k | next = c->head + 1; /* next is where head will point to after this write. */ |
71 | 435k | if (next == c->buflen) |
72 | 2.89k | next = 0; |
73 | | |
74 | 435k | if (next == c->tail) /* if the head + 1 == tail, circular buffer is full */ |
75 | 0 | return(-1); |
76 | | |
77 | | #if RTPQ_DEBUG |
78 | | assert(c->buffer[c->head] == NULL); |
79 | | #endif |
80 | 435k | c->buffer[c->head] = data; /* Load data and then move */ |
81 | 435k | c->head = next; /* head to next data offset. */ |
82 | 435k | return(0); /* return success to indicate successful push. */ |
83 | 435k | } |
84 | | |
85 | | static unsigned int |
86 | | circ_buf_popmany(circ_buf_t *c, struct rtpp_wi *data[], unsigned int howmany) |
87 | 359k | { |
88 | 359k | unsigned int next; |
89 | 359k | unsigned int last; |
90 | 359k | unsigned int copyn; |
91 | 359k | unsigned int rval; |
92 | | |
93 | 359k | rval = 0; |
94 | 359k | RTPP_DBG_ASSERT(howmany > 0); |
95 | 393k | while (!circ_buf_isempty(c)) { |
96 | 359k | next = last = c->tail + howmany - rval; |
97 | 359k | if (c->head < c->tail) { |
98 | 58.1k | if (last >= c->buflen) { |
99 | 2.62k | last = c->buflen; |
100 | 2.62k | next = 0; |
101 | 2.62k | } |
102 | 301k | } else { |
103 | 301k | if (last > c->head) { |
104 | 33.8k | last = c->head; |
105 | 33.8k | next = c->head; |
106 | 33.8k | } |
107 | 301k | } |
108 | 359k | copyn = last - c->tail; |
109 | 359k | memcpy(data, &(c->buffer[c->tail]), copyn * sizeof(data[0])); |
110 | | #if RTPQ_DEBUG |
111 | | memset(&(c->buffer[c->tail]), '\0', copyn * sizeof(data[0])); |
112 | | #endif |
113 | 359k | c->tail = next; |
114 | 359k | rval += copyn; |
115 | 359k | if (rval == howmany) |
116 | 325k | break; |
117 | 33.9k | data += copyn; |
118 | 33.9k | } |
119 | | #if RTPQ_DEBUG |
120 | | assert(rval <= howmany); |
121 | | assert(c->tail < c->buflen); |
122 | | #endif |
123 | | |
124 | 359k | return(rval); /* Return number of objects popped */ |
125 | 359k | } |
126 | | |
127 | | static int |
128 | | circ_buf_peek(const circ_buf_t *c, unsigned int offset, struct rtpp_wi **data) |
129 | 28.3k | { |
130 | 28.3k | unsigned int itmidx, clen; |
131 | | |
132 | 28.3k | if (circ_buf_isempty(c)) |
133 | 0 | return(-1); |
134 | | |
135 | 28.3k | if (c->head < c->tail) { |
136 | 4.51k | clen = (c->head + c->buflen) - c->tail; |
137 | 23.8k | } else { |
138 | 23.8k | clen = c->head - c->tail; |
139 | 23.8k | } |
140 | 28.3k | if (offset >= clen) |
141 | 1.54k | return(-1); |
142 | | |
143 | 26.8k | itmidx = c->tail + offset; /* itmidx points to the item in question */ |
144 | 26.8k | if(itmidx >= c->buflen) |
145 | 1.20k | itmidx -= c->buflen; |
146 | | #if RTPQ_DEBUG |
147 | | assert(itmidx < c->buflen); |
148 | | assert(c->buffer[itmidx] != NULL); |
149 | | #endif |
150 | | |
151 | 26.8k | *data = c->buffer[itmidx]; /* Read data and then move */ |
152 | 26.8k | return(0); /* return success to indicate successful pop. */ |
153 | 28.3k | } |
154 | | |
155 | | static int |
156 | | circ_buf_replace(circ_buf_t *c, unsigned int offset, struct rtpp_wi **data) |
157 | 9.32k | { |
158 | 9.32k | unsigned int itmidx, clen; |
159 | 9.32k | struct rtpp_wi *tdata; |
160 | | |
161 | 9.32k | if (circ_buf_isempty(c)) |
162 | 0 | return(-1); |
163 | | |
164 | 9.32k | if (c->head < c->tail) { |
165 | 1.65k | clen = (c->head + c->buflen) - c->tail; |
166 | 7.67k | } else { |
167 | 7.67k | clen = c->head - c->tail; |
168 | 7.67k | } |
169 | 9.32k | if (offset >= clen) |
170 | 0 | return(-1); |
171 | | |
172 | 9.32k | itmidx = c->tail + offset; /* itmidx points to the item in question */ |
173 | 9.32k | if(itmidx >= c->buflen) |
174 | 828 | itmidx -= c->buflen; |
175 | | #if RTPQ_DEBUG |
176 | | assert(itmidx < c->buflen); |
177 | | assert(c->buffer[itmidx] != NULL); |
178 | | #endif |
179 | 9.32k | tdata = c->buffer[itmidx]; |
180 | | #if RTPQ_DEBUG |
181 | | assert(tdata != NULL); |
182 | | #endif |
183 | 9.32k | c->buffer[itmidx] = *data; /* Read data and then replace */ |
184 | 9.32k | *data = tdata; |
185 | 9.32k | return(0); /* return success to indicate successful pop. */ |
186 | 9.32k | } |
187 | | |
188 | | static int |
189 | | circ_buf_remove(circ_buf_t *c, unsigned int offset) |
190 | 4.65k | { |
191 | 4.65k | unsigned int clen; |
192 | 4.65k | struct rtpp_wi *data; |
193 | | |
194 | 4.65k | if (circ_buf_isempty(c)) |
195 | 0 | return(-1); |
196 | | |
197 | 4.65k | if (c->head < c->tail) { |
198 | 823 | clen = (c->head + c->buflen) - c->tail; |
199 | 3.83k | } else { |
200 | 3.83k | clen = c->head - c->tail; |
201 | 3.83k | } |
202 | 4.65k | if (offset >= clen) |
203 | 0 | return(-1); |
204 | | |
205 | 13.9k | for (; offset > 0; offset--) { |
206 | 9.32k | assert(circ_buf_peek(c, offset - 1, &data) == 0); |
207 | 9.32k | assert(circ_buf_replace(c, offset, &data) == 0); |
208 | 9.32k | } |
209 | | #if RTPQ_DEBUG |
210 | | assert(c->buffer[c->tail] != NULL); |
211 | | c->buffer[c->tail] = NULL; |
212 | | #endif |
213 | 4.65k | c->tail += 1; |
214 | 4.65k | if (c->tail == c->buflen) |
215 | 273 | c->tail = 0; |
216 | 4.65k | return(0); /* return success to indicate successful removal. */ |
217 | 4.65k | } |
218 | | |
219 | | struct rtpp_queue |
220 | | { |
221 | | struct rtpp_wi *head; |
222 | | struct rtpp_wi *tail; |
223 | | pthread_cond_t cond; |
224 | | pthread_mutex_t mutex; |
225 | | unsigned int length; |
226 | | unsigned int qlen; |
227 | | unsigned int mlen; |
228 | | circ_buf_t circb; |
229 | | char name[128]; |
230 | | }; |
231 | | |
232 | | struct rtpp_queue * |
233 | | rtpp_queue_init(unsigned int cb_capacity, const char *fmt, ...) |
234 | 18 | { |
235 | 18 | struct rtpp_queue *queue; |
236 | 18 | unsigned int cb_buflen; |
237 | 18 | va_list ap; |
238 | 18 | int eval; |
239 | 18 | pthread_condattr_t cond_attr; |
240 | | |
241 | 18 | cb_buflen = cb_capacity + 1; |
242 | 18 | queue = rtpp_zmalloc(sizeof(*queue) + (sizeof(queue->circb.buffer[0]) * cb_buflen)); |
243 | 18 | if (queue == NULL) |
244 | 0 | goto e0; |
245 | | |
246 | | /* Set the clock type for the condition variable to CLOCK_MONOTONIC */ |
247 | 18 | if (pthread_condattr_init(&cond_attr) != 0) { |
248 | 0 | goto e1; |
249 | 0 | } |
250 | 18 | if (pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC) != 0) { |
251 | 0 | goto e2; |
252 | 0 | } |
253 | 18 | if ((eval = pthread_cond_init(&queue->cond, &cond_attr)) != 0) { |
254 | 0 | goto e2; |
255 | 0 | } |
256 | 18 | if (pthread_mutex_init(&queue->mutex, NULL) != 0) { |
257 | 0 | goto e3; |
258 | 0 | } |
259 | 18 | va_start(ap, fmt); |
260 | 18 | int r = vsnprintf(queue->name, sizeof(queue->name), fmt, ap); |
261 | 18 | va_end(ap); |
262 | 18 | if (r >= sizeof(queue->name)) { |
263 | 0 | goto e4; |
264 | 0 | } |
265 | 18 | queue->qlen = 1; |
266 | 18 | queue->mlen = -1; |
267 | 18 | queue->circb.buflen = cb_buflen; |
268 | 18 | pthread_condattr_destroy(&cond_attr); |
269 | 18 | return (queue); |
270 | 0 | e4: |
271 | 0 | pthread_mutex_destroy(&queue->mutex); |
272 | 0 | e3: |
273 | 0 | pthread_cond_destroy(&queue->cond); |
274 | 0 | e2: |
275 | 0 | pthread_condattr_destroy(&cond_attr); |
276 | 0 | e1: |
277 | 0 | free(queue); |
278 | 0 | e0: |
279 | 0 | return (NULL); |
280 | 0 | } |
281 | | |
282 | | int |
283 | | rtpp_queue_setmaxlen(struct rtpp_queue *queue, unsigned int new_mlen) |
284 | 8 | { |
285 | | |
286 | 8 | pthread_mutex_lock(&queue->mutex); |
287 | 8 | int mlen = queue->mlen; |
288 | 8 | queue->mlen = new_mlen; |
289 | 8 | pthread_mutex_unlock(&queue->mutex); |
290 | 8 | return (mlen); |
291 | 8 | } |
292 | | |
293 | | void |
294 | | rtpp_queue_destroy(struct rtpp_queue *queue) |
295 | 18 | { |
296 | 18 | while (rtpp_queue_get_length(queue) > 0) { |
297 | 0 | struct rtpp_wi *wip; |
298 | 0 | wip = rtpp_queue_get_item(queue, 0); |
299 | 0 | RTPP_OBJ_DECREF(wip); |
300 | 0 | } |
301 | 18 | pthread_cond_destroy(&queue->cond); |
302 | 18 | pthread_mutex_destroy(&queue->mutex); |
303 | 18 | free(queue); |
304 | 18 | } |
305 | | |
306 | | static int |
307 | | rtpp_queue_getclen(const struct rtpp_queue *queue) |
308 | 920k | { |
309 | 920k | int clen; |
310 | | |
311 | 920k | clen = queue->length; |
312 | 920k | if (queue->circb.head < queue->circb.tail) { |
313 | 133k | clen += (queue->circb.head + queue->circb.buflen) - queue->circb.tail; |
314 | 786k | } else if (queue->circb.head > queue->circb.tail) { |
315 | 677k | clen += queue->circb.head - queue->circb.tail; |
316 | 677k | } |
317 | | |
318 | 920k | return (clen); |
319 | 920k | } |
320 | | |
321 | | static int |
322 | | rtpp_queue_extract_items(struct rtpp_queue *queue, struct rtpp_wi **items, int ilen) |
323 | 359k | { |
324 | 359k | int i, j; |
325 | | |
326 | 359k | i = circ_buf_popmany(&queue->circb, items, ilen); |
327 | 359k | if ((i == ilen) || (queue->length == 0)) |
328 | 359k | return (i); |
329 | 171 | items += i; |
330 | 171 | ilen -= i; |
331 | 171 | for (j = 0; j < ilen; j++) { |
332 | 0 | items[j] = queue->head; |
333 | 0 | queue->head = items[j]->next; |
334 | 0 | if (queue->head == NULL) { |
335 | 0 | queue->tail = NULL; |
336 | 0 | j += 1; |
337 | 0 | break; |
338 | 0 | } |
339 | 0 | } |
340 | 171 | queue->length -= j; |
341 | 171 | i += j; |
342 | 171 | return (i); |
343 | 359k | } |
344 | | |
345 | | unsigned int |
346 | | rtpp_queue_setqlen(struct rtpp_queue *queue, unsigned int qlen) |
347 | 4 | { |
348 | 4 | unsigned int rval; |
349 | | |
350 | 4 | pthread_mutex_lock(&queue->mutex); |
351 | 4 | rval = queue->qlen; |
352 | 4 | queue->qlen = qlen; |
353 | 4 | pthread_mutex_unlock(&queue->mutex); |
354 | 4 | return (rval); |
355 | 4 | } |
356 | | |
357 | | int |
358 | | rtpp_queue_put_item(struct rtpp_wi *wi, struct rtpp_queue *queue) |
359 | 474k | { |
360 | 474k | int rval = 0; |
361 | | |
362 | 474k | pthread_mutex_lock(&queue->mutex); |
363 | | /* |
364 | | * If queue is not empty, push to the queue so that order of elements |
365 | | * is preserved while pulling them out. |
366 | | */ |
367 | 474k | if (queue->mlen != -1 && rtpp_queue_getclen(queue) >= queue->mlen) { |
368 | 38.2k | rval = -1; |
369 | 38.2k | goto out; |
370 | 38.2k | } |
371 | 435k | if ((queue->length > 0) || (circ_buf_push(&queue->circb, wi) != 0)) { |
372 | 0 | RTPPQ_APPEND(queue, wi); |
373 | | #if 0 |
374 | | if (queue->length > 99 && queue->length % 100 == 0) |
375 | | fprintf(stderr, "queue(%s): length %d\n", queue->name, queue->length); |
376 | | #endif |
377 | 0 | } |
378 | | |
379 | 435k | if ((queue->qlen == 1) || (queue->qlen > 1 && rtpp_queue_getclen(queue) % queue->qlen == 0) || wi->wi_type == RTPP_WI_TYPE_SGNL) { |
380 | | /* notify worker thread */ |
381 | 431k | pthread_cond_signal(&queue->cond); |
382 | 431k | } |
383 | | |
384 | 474k | out: |
385 | 474k | pthread_mutex_unlock(&queue->mutex); |
386 | 474k | return (rval); |
387 | 435k | } |
388 | | |
389 | | void |
390 | | rtpp_queue_pump(struct rtpp_queue *queue) |
391 | 10.4k | { |
392 | | |
393 | 10.4k | pthread_mutex_lock(&queue->mutex); |
394 | 10.4k | if (rtpp_queue_getclen(queue) > 0) { |
395 | | /* notify worker thread */ |
396 | 1.24k | pthread_cond_signal(&queue->cond); |
397 | 1.24k | } |
398 | | |
399 | 10.4k | pthread_mutex_unlock(&queue->mutex); |
400 | 10.4k | } |
401 | | |
402 | | void |
403 | | rtpp_queue_wakeup(struct rtpp_queue *queue) |
404 | 0 | { |
405 | |
|
406 | 0 | pthread_mutex_lock(&queue->mutex); |
407 | | /* notify worker thread */ |
408 | 0 | pthread_cond_signal(&queue->cond); |
409 | 0 | pthread_mutex_unlock(&queue->mutex); |
410 | 0 | } |
411 | | |
412 | | struct rtpp_wi * |
413 | | rtpp_queue_get_item_by(struct rtpp_queue *queue, struct timespec *deadline, int *rval) |
414 | 0 | { |
415 | 0 | struct rtpp_wi *wi; |
416 | |
|
417 | 0 | if (rtpp_queue_get_items_by(queue, &wi, 1, deadline, rval) == 0) |
418 | 0 | return (NULL); |
419 | 0 | wi->next = NULL; |
420 | 0 | return (wi); |
421 | 0 | } |
422 | | |
423 | | struct rtpp_wi * |
424 | | rtpp_queue_get_item(struct rtpp_queue *queue, int return_on_wake) |
425 | 325k | { |
426 | 325k | struct rtpp_wi *wi; |
427 | | |
428 | 325k | if (rtpp_queue_get_items(queue, &wi, 1, return_on_wake) == 0) |
429 | 0 | return (NULL); |
430 | 325k | wi->next = NULL; |
431 | 325k | return (wi); |
432 | 325k | } |
433 | | |
434 | | int |
435 | | rtpp_queue_get_items(struct rtpp_queue *queue, struct rtpp_wi **items, int ilen, int return_on_wake) |
436 | 359k | { |
437 | 359k | int i; |
438 | | |
439 | 359k | pthread_mutex_lock(&queue->mutex); |
440 | 393k | while (rtpp_queue_getclen(queue) == 0) { |
441 | 34.5k | pthread_cond_wait(&queue->cond, &queue->mutex); |
442 | 34.5k | if (rtpp_queue_getclen(queue) == 0 && return_on_wake != 0) { |
443 | 0 | pthread_mutex_unlock(&queue->mutex); |
444 | 0 | return (0); |
445 | 0 | } |
446 | 34.5k | } |
447 | 359k | i = rtpp_queue_extract_items(queue, items, ilen); |
448 | 359k | pthread_mutex_unlock(&queue->mutex); |
449 | 359k | return (i); |
450 | 359k | } |
451 | | |
452 | | int |
453 | | rtpp_queue_get_items_by(struct rtpp_queue *queue, struct rtpp_wi **items, int ilen, |
454 | | struct timespec *deadline, int *rval) |
455 | 0 | { |
456 | 0 | int i, rc; |
457 | |
|
458 | 0 | pthread_mutex_lock(&queue->mutex); |
459 | 0 | while (rtpp_queue_getclen(queue) == 0) { |
460 | 0 | rc = pthread_cond_timedwait(&queue->cond, &queue->mutex, deadline); |
461 | 0 | if (rtpp_queue_getclen(queue) != 0) |
462 | 0 | break; |
463 | 0 | if (rval != NULL) |
464 | 0 | *rval = rc; |
465 | 0 | pthread_mutex_unlock(&queue->mutex); |
466 | 0 | return (0); |
467 | 0 | } |
468 | 0 | i = rtpp_queue_extract_items(queue, items, ilen); |
469 | 0 | pthread_mutex_unlock(&queue->mutex); |
470 | 0 | return (i); |
471 | 0 | } |
472 | | |
473 | | int |
474 | | rtpp_queue_get_length(struct rtpp_queue *queue) |
475 | 117k | { |
476 | 117k | int length; |
477 | | |
478 | 117k | pthread_mutex_lock(&queue->mutex); |
479 | 117k | length = rtpp_queue_getclen(queue); |
480 | 117k | pthread_mutex_unlock(&queue->mutex); |
481 | 117k | return (length); |
482 | 117k | } |
483 | | |
484 | | #if 0 |
485 | | int |
486 | | rtpp_queue_count_matching(struct rtpp_queue *queue, rtpp_queue_match_fn_t match_fn, void *fn_args) |
487 | | { |
488 | | struct rtpp_wi *wi; |
489 | | int mcnt; |
490 | | |
491 | | mcnt = 0; |
492 | | pthread_mutex_lock(&queue->mutex); |
493 | | for (wi = queue->head; wi != NULL; wi = wi->next) { |
494 | | if (match_fn(wi, fn_args) == 0) { |
495 | | mcnt++; |
496 | | } |
497 | | } |
498 | | pthread_mutex_unlock(&queue->mutex); |
499 | | return (mcnt); |
500 | | } |
501 | | #endif |
502 | | |
503 | | struct rtpp_wi * |
504 | | rtpp_queue_get_first_matching(struct rtpp_queue *queue, rtpp_queue_match_fn_t match_fn, void *fn_args) |
505 | 6.19k | { |
506 | 6.19k | struct rtpp_wi *wi, *wi_prev; |
507 | 6.19k | int i; |
508 | | |
509 | 6.19k | pthread_mutex_lock(&queue->mutex); |
510 | 19.0k | for (i = 0;; i++) { |
511 | 19.0k | if (circ_buf_peek(&queue->circb, i, &wi) != 0) |
512 | 1.54k | break; |
513 | 17.5k | if (match_fn(wi, fn_args) == 0) { |
514 | 4.65k | assert(circ_buf_remove(&queue->circb, i) == 0); |
515 | 4.65k | pthread_mutex_unlock(&queue->mutex); |
516 | 4.65k | return (wi); |
517 | 4.65k | } |
518 | 17.5k | } |
519 | 1.54k | wi_prev = NULL; |
520 | 1.54k | for (wi = queue->head; wi != NULL; wi_prev = wi, wi = wi->next) { |
521 | 0 | if (match_fn(wi, fn_args) == 0) { |
522 | 0 | RTPPQ_REMOVE_AFTER(queue, wi_prev); |
523 | 0 | pthread_mutex_unlock(&queue->mutex); |
524 | 0 | return (wi); |
525 | 0 | } |
526 | 0 | } |
527 | 1.54k | pthread_mutex_unlock(&queue->mutex); |
528 | | return (NULL); |
529 | 1.54k | } |