VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/posix/timer-posix.cpp@ 9839

Last change on this file since 9839 was 9839, checked in by vboxsync, 17 years ago

New implementation of RTTimer... using POSIX timers.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 19.4 KB
Line 
1/* $Id: timer-posix.cpp 9839 2008-06-20 09:27:34Z vboxsync $ */
2/** @file
3 * IPRT - Timer, POSIX.
4 */
5
6/*
7 * Copyright (C) 2006-2007 Sun Microsystems, Inc.
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 *
26 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
27 * Clara, CA 95054 USA or visit http://www.sun.com if you need
28 * additional information or have any questions.
29 */
30
31
32/*******************************************************************************
33* Header Files *
34*******************************************************************************/
35#include <iprt/timer.h>
36#include <iprt/alloc.h>
37#include <iprt/assert.h>
38#include <iprt/thread.h>
39#include <iprt/log.h>
40#include <iprt/asm.h>
41#include <iprt/semaphore.h>
42#include <iprt/string.h>
43#include <iprt/err.h>
44#include "internal/magics.h"
45
46#include <unistd.h>
47#include <sys/fcntl.h>
48#include <sys/ioctl.h>
49#ifdef RT_OS_LINUX
50# include <linux/rtc.h>
51#endif
52#include <sys/time.h>
53#include <signal.h>
54#include <errno.h>
55#ifndef RT_OS_OS2
56# include <pthread.h>
57#endif
58
59#define RT_TIMER_SIGNAL SIGUSR1
60
61/*******************************************************************************
62* Structures and Typedefs *
63*******************************************************************************/
64/**
65 * The internal representation of a timer handle.
66 */
67typedef struct RTTIMER
68{
69 /** Magic.
70 * This is RTTIMER_MAGIC, but changes to something else before the timer
71 * is destroyed to indicate clearly that thread should exit. */
72 uint32_t volatile u32Magic;
73 /** Flag indicating the the timer is suspended. */
74 uint8_t volatile fSuspended;
75 /** Flag indicating that the timer has been destroyed. */
76 uint8_t volatile fDestroyed;
77#ifndef IPRT_WITH_POSIX_TIMERS
78 /** The timer thread. */
79 RTTHREAD Thread;
80 /** Event semaphore on which the thread is blocked. */
81 RTSEMEVENT Event;
82#endif
83 /** User argument. */
84 void *pvUser;
85 /** Callback. */
86 PFNRTTIMER pfnTimer;
87 /** The timer interval. 0 if one-shot. */
88 uint64_t u64NanoInterval;
89#ifndef IPRT_WITH_POSIX_TIMERS
90 /** The first shot interval. 0 if ASAP. */
91 uint64_t volatile u64NanoFirst;
92#endif /* !IPRT_WITH_POSIX_TIMERS */
93 /** The current timer tick. */
94 uint64_t volatile iTick;
95#ifndef IPRT_WITH_POSIX_TIMERS
96 /** The error/status of the timer.
97 * Initially -1, set to 0 when the timer have been successfully started, and
98 * to errno on failure in starting the timer. */
99 int volatile iError;
100#else /* !IPRT_WITH_POSIX_TIMERS */
101 timer_t timer;
102#endif /* !IPRT_WITH_POSIX_TIMERS */
103
104} RTTIMER;
105
106#ifndef IPRT_WITH_POSIX_TIMERS
107
108/**
109 * Signal handler which ignore everything it gets.
110 *
111 * @param iSignal The signal number.
112 */
113static void rttimerSignalIgnore(int iSignal)
114{
115 //AssertBreakpoint();
116}
117
118
119/**
120 * SIGALRM wait thread.
121 */
122static DECLCALLBACK(int) rttimerThread(RTTHREAD Thread, void *pvArg)
123{
124 PRTTIMER pTimer = (PRTTIMER)(void *)pvArg;
125 RTTIMER Timer = *pTimer;
126 Assert(pTimer->u32Magic == RTTIMER_MAGIC);
127
128 /*
129 * Install signal handler.
130 */
131 struct sigaction SigAct;
132 memset(&SigAct, 0, sizeof(SigAct));
133 SigAct.sa_flags = SA_RESTART;
134 sigemptyset(&SigAct.sa_mask);
135 SigAct.sa_handler = rttimerSignalIgnore;
136 if (sigaction(SIGALRM, &SigAct, NULL))
137 {
138 SigAct.sa_flags &= ~SA_RESTART;
139 if (sigaction(SIGALRM, &SigAct, NULL))
140 AssertMsgFailed(("sigaction failed, errno=%d\n", errno));
141 }
142
143 /*
144 * Mask most signals except those which might be used by the pthread implementation (linux).
145 */
146 sigset_t SigSet;
147 sigfillset(&SigSet);
148 sigdelset(&SigSet, SIGTERM);
149 sigdelset(&SigSet, SIGHUP);
150 sigdelset(&SigSet, SIGINT);
151 sigdelset(&SigSet, SIGABRT);
152 sigdelset(&SigSet, SIGKILL);
153#ifdef SIGRTMIN
154 for (int iSig = SIGRTMIN; iSig < SIGRTMAX; iSig++)
155 sigdelset(&SigSet, iSig);
156#endif
157 if (sigprocmask(SIG_SETMASK, &SigSet, NULL))
158 {
159 int rc = pTimer->iError = RTErrConvertFromErrno(errno);
160 AssertMsgFailed(("sigprocmask -> errno=%d\n", errno));
161 return rc;
162 }
163
164 /*
165 * The work loop.
166 */
167 RTThreadUserSignal(Thread);
168 while ( !pTimer->fDestroyed
169 && pTimer->u32Magic == RTTIMER_MAGIC)
170 {
171 /*
172 * Wait for a start or destroy event.
173 */
174 if (pTimer->fSuspended)
175 {
176 int rc = RTSemEventWait(pTimer->Event, RT_INDEFINITE_WAIT);
177 if (RT_FAILURE(rc) && rc != VERR_INTERRUPTED)
178 {
179 AssertRC(rc);
180 RTThreadSleep(1000); /* Don't cause trouble! */
181 }
182 if ( pTimer->fSuspended
183 || pTimer->fDestroyed)
184 continue;
185 }
186
187 /*
188 * Start the timer.
189 *
190 * For some SunOS (/SysV?) threading compatibility Linux will only
191 * deliver the SIGALRM to the thread calling setitimer(). Therefore
192 * we have to call it here.
193 *
194 * It turns out this might not always be the case, see SIGALRM killing
195 * processes on RH 2.4.21.
196 */
197 struct itimerval TimerVal;
198 if (pTimer->u64NanoFirst)
199 {
200 uint64_t u64 = RT_MAX(1000, pTimer->u64NanoFirst);
201 TimerVal.it_value.tv_sec = u64 / 1000000000;
202 TimerVal.it_value.tv_usec = (u64 % 1000000000) / 1000;
203 }
204 else
205 {
206 TimerVal.it_value.tv_sec = 0;
207 TimerVal.it_value.tv_usec = 10;
208 }
209 if (pTimer->u64NanoInterval)
210 {
211 uint64_t u64 = RT_MAX(1000, pTimer->u64NanoInterval);
212 TimerVal.it_interval.tv_sec = u64 / 1000000000;
213 TimerVal.it_interval.tv_usec = (u64 % 1000000000) / 1000;
214 }
215 else
216 {
217 TimerVal.it_interval.tv_sec = 0;
218 TimerVal.it_interval.tv_usec = 0;
219 }
220
221 if (setitimer(ITIMER_REAL, &TimerVal, NULL))
222 {
223 ASMAtomicXchgU8(&pTimer->fSuspended, true);
224 pTimer->iError = RTErrConvertFromErrno(errno);
225 RTThreadUserSignal(Thread);
226 continue; /* back to suspended mode. */
227 }
228 pTimer->iError = 0;
229 RTThreadUserSignal(Thread);
230
231 /*
232 * Timer Service Loop.
233 */
234 sigemptyset(&SigSet);
235 sigaddset(&SigSet, SIGALRM);
236 do
237 {
238 siginfo_t SigInfo = {0};
239#ifdef RT_OS_DARWIN
240 if (RT_LIKELY(sigwait(&SigSet, &SigInfo.si_signo) >= 0))
241 {
242#else
243 if (RT_LIKELY(sigwaitinfo(&SigSet, &SigInfo) >= 0))
244 {
245 if (RT_LIKELY(SigInfo.si_signo == SIGALRM))
246#endif
247 {
248 if (RT_UNLIKELY( pTimer->fSuspended
249 || pTimer->fDestroyed
250 || pTimer->u32Magic != RTTIMER_MAGIC))
251 break;
252
253 pTimer->pfnTimer(pTimer, pTimer->pvUser, ++pTimer->iTick);
254
255 /* auto suspend one-shot timers. */
256 if (RT_UNLIKELY(!pTimer->u64NanoInterval))
257 {
258 ASMAtomicXchgU8(&pTimer->fSuspended, true);
259 break;
260 }
261 }
262 }
263 else if (errno != EINTR)
264 AssertMsgFailed(("sigwaitinfo -> errno=%d\n", errno));
265 } while (RT_LIKELY( !pTimer->fSuspended
266 && !pTimer->fDestroyed
267 && pTimer->u32Magic == RTTIMER_MAGIC));
268
269 /*
270 * Disable the timer.
271 */
272 struct itimerval TimerVal2 = {{0,0}, {0,0}};
273 if (setitimer(ITIMER_REAL, &TimerVal2, NULL))
274 AssertMsgFailed(("setitimer(ITIMER_REAL,&{0}, NULL) failed, errno=%d\n", errno));
275
276 /*
277 * ACK any pending suspend request.
278 */
279 if (!pTimer->fDestroyed)
280 {
281 pTimer->iError = 0;
282 RTThreadUserSignal(Thread);
283 }
284 }
285
286 /*
287 * Exit.
288 */
289 pTimer->iError = 0;
290 RTThreadUserSignal(Thread);
291
292 return VINF_SUCCESS;
293}
294#else /* !IPRT_WITH_POSIX_TIMERS */
295void rtSignalCallback(int sig, siginfo_t *sip, void *ucp)
296{
297 PRTTIMER pTimer = (PRTTIMER)sip->_sifields._timer.si_sigval.sival_ptr;
298 pTimer->pfnTimer(pTimer, pTimer->pvUser, ++pTimer->iTick);
299}
300#endif /* !IPRT_WITH_POSIX_TIMERS */
301
302
303RTDECL(int) RTTimerCreateEx(PRTTIMER *ppTimer, uint64_t u64NanoInterval, unsigned fFlags, PFNRTTIMER pfnTimer, void *pvUser)
304{
305 /*
306 * We don't support the fancy MP features.
307 */
308 if (fFlags & RTTIMER_FLAGS_CPU_SPECIFIC)
309 return VERR_NOT_SUPPORTED;
310
311#ifndef IPRT_WITH_POSIX_TIMERS
312 /*
313 * Check if timer is busy.
314 */
315 struct itimerval TimerVal;
316 if (getitimer(ITIMER_REAL, &TimerVal))
317 {
318 AssertMsgFailed(("getitimer() -> errno=%d\n", errno));
319 return VERR_NOT_IMPLEMENTED;
320 }
321 if ( TimerVal.it_value.tv_usec || TimerVal.it_value.tv_sec
322 || TimerVal.it_interval.tv_usec || TimerVal.it_interval.tv_sec
323 )
324 {
325 AssertMsgFailed(("A timer is running. System limit is one timer per process!\n"));
326 return VERR_TIMER_BUSY;
327 }
328
329 /*
330 * Block SIGALRM from calling thread.
331 */
332 sigset_t SigSet;
333 sigemptyset(&SigSet);
334 sigaddset(&SigSet, SIGALRM);
335 sigprocmask(SIG_BLOCK, &SigSet, NULL);
336
337 /** @todo Move this RTC hack else where... */
338 static bool fDoneRTC;
339 if (!fDoneRTC)
340 {
341 fDoneRTC = true;
342 /* check resolution. */
343 TimerVal.it_interval.tv_sec = 0;
344 TimerVal.it_interval.tv_usec = 1000;
345 TimerVal.it_value = TimerVal.it_interval;
346 if ( setitimer(ITIMER_REAL, &TimerVal, NULL)
347 || getitimer(ITIMER_REAL, &TimerVal)
348 || TimerVal.it_interval.tv_usec > 1000)
349 {
350 /*
351 * Try open /dev/rtc to set the irq rate to 1024 and
352 * turn periodic
353 */
354 Log(("RTTimerCreate: interval={%ld,%ld} trying to adjust /dev/rtc!\n", TimerVal.it_interval.tv_sec, TimerVal.it_interval.tv_usec));
355#ifdef RT_OS_LINUX
356 int fh = open("/dev/rtc", O_RDONLY);
357 if (fh >= 0)
358 {
359 if ( ioctl(fh, RTC_IRQP_SET, 1024) < 0
360 || ioctl(fh, RTC_PIE_ON, 0) < 0)
361 Log(("RTTimerCreate: couldn't configure rtc! errno=%d\n", errno));
362 ioctl(fh, F_SETFL, O_ASYNC);
363 ioctl(fh, F_SETOWN, getpid());
364 /* not so sure if closing it is a good idea... */
365 //close(fh);
366 }
367 else
368 Log(("RTTimerCreate: couldn't configure rtc! open failed with errno=%d\n", errno));
369#endif
370 }
371 /* disable it */
372 TimerVal.it_interval.tv_sec = 0;
373 TimerVal.it_interval.tv_usec = 0;
374 TimerVal.it_value = TimerVal.it_interval;
375 setitimer(ITIMER_REAL, &TimerVal, NULL);
376 }
377
378 /*
379 * Create a new timer.
380 */
381 int rc;
382 PRTTIMER pTimer = (PRTTIMER)RTMemAlloc(sizeof(*pTimer));
383 if (pTimer)
384 {
385 pTimer->u32Magic = RTTIMER_MAGIC;
386 pTimer->fSuspended = true;
387 pTimer->fDestroyed = false;
388 pTimer->Thread = NIL_RTTHREAD;
389 pTimer->Event = NIL_RTSEMEVENT;
390 pTimer->pfnTimer = pfnTimer;
391 pTimer->pvUser = pvUser;
392 pTimer->u64NanoInterval = u64NanoInterval;
393 pTimer->u64NanoFirst = 0;
394 pTimer->iTick = 0;
395 pTimer->iError = 0;
396 rc = RTSemEventCreate(&pTimer->Event);
397 AssertRC(rc);
398 if (RT_SUCCESS(rc))
399 {
400 rc = RTThreadCreate(&pTimer->Thread, rttimerThread, pTimer, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "Timer");
401 AssertRC(rc);
402 if (RT_SUCCESS(rc))
403 {
404 /*
405 * Wait for the timer thread to initialize it self.
406 * This might take a little while...
407 */
408 rc = RTThreadUserWait(pTimer->Thread, 45*1000);
409 AssertRC(rc);
410 if (RT_SUCCESS(rc))
411 {
412 rc = RTThreadUserReset(pTimer->Thread); AssertRC(rc);
413 rc = pTimer->iError;
414 AssertRC(rc);
415 if (RT_SUCCESS(rc))
416 {
417 RTThreadYield(); /* <-- Horrible hack to make tstTimer work. (linux 2.6.12) */
418 *ppTimer = pTimer;
419 return VINF_SUCCESS;
420 }
421 }
422
423 /* bail out */
424 ASMAtomicXchgU8(&pTimer->fDestroyed, true);
425 ASMAtomicXchgU32(&pTimer->u32Magic, RTTIMER_MAGIC + 1);
426 RTThreadWait(pTimer->Thread, 45*1000, NULL);
427 }
428 RTSemEventDestroy(pTimer->Event);
429 pTimer->Event = NIL_RTSEMEVENT;
430 }
431 RTMemFree(pTimer);
432 }
433 else
434 rc = VERR_NO_MEMORY;
435#else /* !IPRT_WITH_POSIX_TIMERS */
436 /*
437 * Create a new timer.
438 */
439 int rc, result;
440 PRTTIMER pTimer = (PRTTIMER)RTMemAlloc(sizeof(*pTimer));
441 if (pTimer)
442 {
443 struct sigevent evt;
444 struct sigaction action, old;
445
446 /* Initialize timer structure. */
447 pTimer->u32Magic = RTTIMER_MAGIC;
448 pTimer->fSuspended = true;
449 pTimer->fDestroyed = false;
450 pTimer->pfnTimer = pfnTimer;
451 pTimer->pvUser = pvUser;
452 pTimer->u64NanoInterval = u64NanoInterval;
453 pTimer->iTick = 0;
454
455 /* Set up the signal handler. */
456 sigemptyset(&action.sa_mask);
457 action.sa_sigaction = rtSignalCallback;
458 action.sa_flags = SA_SIGINFO | SA_RESTART;
459 rc = RTErrConvertFromErrno(sigaction(RT_TIMER_SIGNAL, &action, &old));
460 if (RT_SUCCESS(rc))
461 {
462
463 /* Ask to deliver RT_TIMER_SIGNAL upon timer expiration. */
464 evt.sigev_notify = SIGEV_SIGNAL;
465 evt.sigev_signo = RT_TIMER_SIGNAL;
466 evt.sigev_value.sival_ptr = pTimer; /* sigev_value gets copied to siginfo. */
467
468 rc = RTErrConvertFromErrno(timer_create(CLOCK_REALTIME, &evt, &pTimer->timer));
469 if (RT_SUCCESS(rc))
470 {
471 *ppTimer = pTimer;
472 return VINF_SUCCESS;
473 }
474 sigaction(RT_TIMER_SIGNAL, &old, NULL);
475 }
476 RTMemFree(pTimer);
477 }
478 else
479 rc = VERR_NO_MEMORY;
480
481#endif /* !IPRT_WITH_POSIX_TIMERS */
482 return rc;
483}
484
485
486RTR3DECL(int) RTTimerDestroy(PRTTIMER pTimer)
487{
488 LogFlow(("RTTimerDestroy: pTimer=%p\n", pTimer));
489
490 /*
491 * Validate input.
492 */
493 /* NULL is ok. */
494 if (!pTimer)
495 return VINF_SUCCESS;
496 int rc = VINF_SUCCESS;
497 AssertPtrReturn(pTimer, VERR_INVALID_POINTER);
498 AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC);
499#ifndef IPRT_WITH_POSIX_TIMERS
500 AssertReturn(pTimer->Thread != RTThreadSelf(), VERR_INTERNAL_ERROR);
501
502 /*
503 * Tell the thread to terminate and wait for it do complete.
504 */
505 ASMAtomicXchgU8(&pTimer->fDestroyed, true);
506 ASMAtomicXchgU32(&pTimer->u32Magic, RTTIMER_MAGIC + 1);
507 rc = RTSemEventSignal(pTimer->Event);
508 AssertRC(rc);
509 if (!pTimer->fSuspended)
510 {
511#ifndef RT_OS_OS2
512 pthread_kill((pthread_t)RTThreadGetNative(pTimer->Thread), SIGALRM);
513#endif
514 }
515 rc = RTThreadWait(pTimer->Thread, 30 * 1000, NULL);
516 AssertRC(rc);
517
518 RTSemEventDestroy(pTimer->Event);
519 pTimer->Event = NIL_RTSEMEVENT;
520#else /* !IPRT_WITH_POSIX_TIMERS */
521 if (ASMAtomicXchgU8(&pTimer->fDestroyed, true))
522 {
523 /* It is already being destroyed by another thread. */
524 return VINF_SUCCESS;
525 }
526 rc = RTErrConvertFromErrno(timer_delete(pTimer->timer));
527#endif /* !IPRT_WITH_POSIX_TIMERS */
528 if (RT_SUCCESS(rc))
529 RTMemFree(pTimer);
530 return rc;
531}
532
533
534RTDECL(int) RTTimerStart(PRTTIMER pTimer, uint64_t u64First)
535{
536 /*
537 * Validate input.
538 */
539 AssertPtrReturn(pTimer, VERR_INVALID_POINTER);
540 AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC);
541#ifndef IPRT_WITH_POSIX_TIMERS
542 AssertReturn(pTimer->Thread != RTThreadSelf(), VERR_INTERNAL_ERROR);
543
544 /*
545 * Already running?
546 */
547 if (!pTimer->fSuspended)
548 return VERR_TIMER_ACTIVE;
549
550 /*
551 * Tell the thread to start servicing the timer.
552 */
553 RTThreadUserReset(pTimer->Thread);
554 ASMAtomicUoWriteU64(&pTimer->u64NanoFirst, u64First);
555 ASMAtomicUoWriteU64(&pTimer->iTick, 0);
556 ASMAtomicWriteU8(&pTimer->fSuspended, false);
557 int rc = RTSemEventSignal(pTimer->Event);
558 if (RT_SUCCESS(rc))
559 {
560 rc = RTThreadUserWait(pTimer->Thread, 45*1000);
561 AssertRC(rc);
562 RTThreadUserReset(pTimer->Thread);
563 }
564 else
565 AssertRC(rc);
566 if (RT_FAILURE(rc))
567 ASMAtomicXchgU8(&pTimer->fSuspended, false);
568#else /* !IPRT_WITH_POSIX_TIMERS */
569 struct itimerspec ts;
570
571 if (!ASMAtomicXchgU8(&pTimer->fSuspended, false))
572 return VERR_TIMER_ACTIVE;
573
574 ts.it_value.tv_sec = u64First / 1000000000; /* nanosec => sec */
575 ts.it_value.tv_nsec = u64First ? u64First % 1000000000 : 1; /* 0 means disable, replace it with 1. */
576 ts.it_interval.tv_sec = pTimer->u64NanoInterval / 1000000000;
577 ts.it_interval.tv_nsec = pTimer->u64NanoInterval % 1000000000;
578 int rc = RTErrConvertFromErrno(timer_settime(pTimer->timer, 0, &ts, NULL));
579#endif /* !IPRT_WITH_POSIX_TIMERS */
580
581 return rc;
582}
583
584
585RTDECL(int) RTTimerStop(PRTTIMER pTimer)
586{
587 /*
588 * Validate input.
589 */
590 AssertPtrReturn(pTimer, VERR_INVALID_POINTER);
591 AssertReturn(pTimer->u32Magic == RTTIMER_MAGIC, VERR_INVALID_MAGIC);
592
593#ifndef IPRT_WITH_POSIX_TIMERS
594 /*
595 * Already running?
596 */
597 if (pTimer->fSuspended)
598 return VERR_TIMER_SUSPENDED;
599
600 /*
601 * Tell the thread to stop servicing the timer.
602 */
603 RTThreadUserReset(pTimer->Thread);
604 ASMAtomicXchgU8(&pTimer->fSuspended, true);
605 int rc = VINF_SUCCESS;
606 if (RTThreadSelf() != pTimer->Thread)
607 {
608#ifndef RT_OS_OS2
609 pthread_kill((pthread_t)RTThreadGetNative(pTimer->Thread), SIGALRM);
610#endif
611 rc = RTThreadUserWait(pTimer->Thread, 45*1000);
612 AssertRC(rc);
613 RTThreadUserReset(pTimer->Thread);
614 }
615#else /* !IPRT_WITH_POSIX_TIMERS */
616 struct itimerspec ts;
617
618 if (ASMAtomicXchgU8(&pTimer->fSuspended, true))
619 return VERR_TIMER_SUSPENDED;
620
621 ts.it_value.tv_sec = 0;
622 ts.it_value.tv_nsec = 0;
623 int rc = RTErrConvertFromErrno(timer_settime(pTimer->timer, 0, &ts, NULL));
624#endif /* !IPRT_WITH_POSIX_TIMERS */
625
626 return rc;
627}
628
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette