From 229ea3f23c01543f98f4446bd9092dbf60c983cc Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Tue, 22 Jan 2019 15:23:05 +0100 Subject: Cygwin: posix timers: reimplement using OS timer - Rename files timer.* to posix_timer.*. - Reimplement using an OS timer rather than a handcrafted wait loop. - Use a Slim R/W Lock for synchronization. - Drop timer chaining. It doesn't server a purpose since all timers are local only. - Rename ttstart to itimer_tracker to better reflect its purpose. It's not the anchor for a timer chain anymore anyway. - Drop fixup_timers_after_fork. Everything is process-local, nothing gets inherited. - Rename timer_tracker::disarm_event to disarm_overrun_event for better readability. Signed-off-by: Corinna Vinschen --- winsup/cygwin/Makefile.in | 2 +- winsup/cygwin/cygheap_malloc.h | 3 +- winsup/cygwin/exceptions.cc | 8 +- winsup/cygwin/fork.cc | 2 - winsup/cygwin/posix_timer.cc | 644 +++++++++++++++++++++++++++++++++++++++++ winsup/cygwin/posix_timer.h | 55 ++++ winsup/cygwin/signal.cc | 4 +- winsup/cygwin/timer.cc | 598 -------------------------------------- winsup/cygwin/timer.h | 50 ---- 9 files changed, 708 insertions(+), 658 deletions(-) create mode 100644 winsup/cygwin/posix_timer.cc create mode 100644 winsup/cygwin/posix_timer.h delete mode 100644 winsup/cygwin/timer.cc delete mode 100644 winsup/cygwin/timer.h diff --git a/winsup/cygwin/Makefile.in b/winsup/cygwin/Makefile.in index 374272f4c..32c02b87b 100644 --- a/winsup/cygwin/Makefile.in +++ b/winsup/cygwin/Makefile.in @@ -354,6 +354,7 @@ DLL_OFILES:= \ pinfo.o \ poll.o \ posix_ipc.o \ + posix_timer.o \ pseudo-reloc.o \ pthread.o \ quotactl.o \ @@ -395,7 +396,6 @@ DLL_OFILES:= \ syslog.o \ termios.o \ thread.o \ - timer.o \ timerfd.o \ times.o \ tls_pbuf.o \ diff --git a/winsup/cygwin/cygheap_malloc.h b/winsup/cygwin/cygheap_malloc.h index 74f0bb619..cd545c35d 100644 --- a/winsup/cygwin/cygheap_malloc.h +++ b/winsup/cygwin/cygheap_malloc.h @@ -34,7 +34,8 @@ enum cygheap_types HEAP_2_DLL, HEAP_MMAP, HEAP_2_MAX = 200, - HEAP_3_FHANDLER + HEAP_3_FHANDLER, + HEAP_3_TIMER }; extern "C" { diff --git a/winsup/cygwin/exceptions.cc b/winsup/cygwin/exceptions.cc index 205ad850e..491eedbe4 100644 --- a/winsup/cygwin/exceptions.cc +++ b/winsup/cygwin/exceptions.cc @@ -27,7 +27,7 @@ details. */ #include "child_info.h" #include "ntdll.h" #include "exception.h" -#include "timer.h" +#include "posix_timer.h" /* Definitions for code simplification */ #ifdef __x86_64__ @@ -1483,7 +1483,7 @@ sigpacket::process () if (handler == SIG_IGN) { if (si.si_code == SI_TIMER) - ((timer_tracker *) si.si_tid)->disarm_event (); + ((timer_tracker *) si.si_tid)->disarm_overrun_event (); sigproc_printf ("signal %d ignored", si.si_signo); goto done; } @@ -1508,7 +1508,7 @@ sigpacket::process () || si.si_signo == SIGURG) { if (si.si_code == SI_TIMER) - ((timer_tracker *) si.si_tid)->disarm_event (); + ((timer_tracker *) si.si_tid)->disarm_overrun_event (); sigproc_printf ("signal %d default is currently ignore", si.si_signo); goto done; } @@ -1637,7 +1637,7 @@ _cygtls::call_signal_handler () { timer_tracker *tt = (timer_tracker *) infodata.si_tid; - infodata.si_overrun = tt->disarm_event (); + infodata.si_overrun = tt->disarm_overrun_event (); } /* Save information locally on stack to pass to handler. */ diff --git a/winsup/cygwin/fork.cc b/winsup/cygwin/fork.cc index 5cfdd9559..6813446cf 100644 --- a/winsup/cygwin/fork.cc +++ b/winsup/cygwin/fork.cc @@ -22,7 +22,6 @@ details. */ #include "tls_pbuf.h" #include "dll_init.h" #include "cygmalloc.h" -#include "timer.h" #include "ntdll.h" #define NPIDS_HELD 4 @@ -196,7 +195,6 @@ frok::child (volatile char * volatile here) ForceCloseHandle1 (fork_info->forker_finished, forker_finished); pthread::atforkchild (); - fixup_timers_after_fork (); cygbench ("fork-child"); ld_preload (); fixup_hooks_after_fork (); diff --git a/winsup/cygwin/posix_timer.cc b/winsup/cygwin/posix_timer.cc new file mode 100644 index 000000000..d4f2575f1 --- /dev/null +++ b/winsup/cygwin/posix_timer.cc @@ -0,0 +1,644 @@ +/* timer.cc: posix timers + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include "winsup.h" +#include "thread.h" +#include "cygtls.h" +#include "sigproc.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "posix_timer.h" +#include + +#define OVR_EVENT_DISARMED 0 +#define OVR_EVENT_ARMED 1 + +timer_tracker NO_COPY itimer_tracker (CLOCK_REALTIME, NULL); + +bool +timer_tracker::cancel () +{ + DWORD res; + + if (!cancel_evt) + return false; + SetEvent (cancel_evt); + if (sync_thr) + { + res = WaitForSingleObject (sync_thr, INFINITE); + if (res != WAIT_OBJECT_0) + system_printf ("WFSO returned unexpected value %u, %E", res); + } + return true; +} + +timer_tracker::timer_tracker (clockid_t c, const sigevent *e) +: magic (TT_MAGIC), clock_id (c), timer (NULL), cancel_evt (NULL), + sync_thr (NULL), interval (0), exp_ts (0), overrun_event_running (0), + overrun_count_curr (0), overrun_count (0) +{ + srwlock = SRWLOCK_INIT; + if (e != NULL) + evp = *e; + else + { + evp.sigev_notify = SIGEV_SIGNAL; + evp.sigev_signo = SIGALRM; + evp.sigev_value.sival_ptr = this; + } +} + +timer_tracker::~timer_tracker () +{ + AcquireSRWLockExclusive (&srwlock); + cancel (); + NtClose (cancel_evt); + NtClose (sync_thr); + NtClose (timer); + magic = 0; + ReleaseSRWLockExclusive (&srwlock); +} + +static inline int64_t +timespec_to_us (const timespec& ts) +{ + int64_t res = ts.tv_sec; + res *= USPERSEC; + res += (ts.tv_nsec + (NSPERSEC/USPERSEC) - 1) / (NSPERSEC/USPERSEC); + return res; +} + +/* Returns 0 if arming successful, -1 if a signal is already queued. + If so, it also increments overrun_count. Only call under lock! */ +LONG +timer_tracker::arm_overrun_event () +{ + LONG ret; + + ret = InterlockedExchange (&overrun_event_running, OVR_EVENT_ARMED); + if (ret == OVR_EVENT_ARMED) + ret = InterlockedIncrement64 (&overrun_count); + return ret; +} + +LONG +timer_tracker::disarm_overrun_event () +{ + LONG ret; + + AcquireSRWLockExclusive (&srwlock); + ret = InterlockedExchange (&overrun_event_running, OVR_EVENT_DISARMED); + if (ret == OVR_EVENT_ARMED) + { + LONG64 ov_cnt; + + InterlockedExchange64 (&ov_cnt, overrun_count); + if (ov_cnt > DELAYTIMER_MAX || ov_cnt < 0) + overrun_count_curr = DELAYTIMER_MAX; + else + overrun_count_curr = ov_cnt; + ret = overrun_count_curr; + InterlockedExchange64 (&overrun_count, 0); + } + ReleaseSRWLockExclusive (&srwlock); + return ret; +} + +static void * +notify_thread_wrapper (void *arg) +{ + timer_tracker *tt = (timer_tracker *) arg; + sigevent_t *evt = tt->sigevt (); + void * (*notify_func) (void *) = (void * (*) (void *)) + evt->sigev_notify_function; + + tt->disarm_overrun_event (); + return notify_func (evt->sigev_value.sival_ptr); +} + +DWORD +timer_tracker::thread_func () +{ + HANDLE w4[2] = { timer, cancel_evt }; + + debug_printf ("%p timer armed", this); + while (1) + { + switch (WaitForMultipleObjects (2, w4, FALSE, INFINITE)) + { + case WAIT_OBJECT_0: + debug_printf ("%p timer expired", this); + break; + case WAIT_OBJECT_0 + 1: + debug_printf ("%p timer disarmed, %E", this); + goto out; + default: + debug_printf ("%p wait failed, %E", this); + continue; + } + AcquireSRWLockExclusive (&srwlock); + /* Make sure we haven't been abandoned and/or disarmed in the meantime */ + if (exp_ts == 0 && interval == 0) + { + ReleaseSRWLockExclusive (&srwlock); + goto out; + } + if (interval) + { + /* Compute expiration count. */ + LONG64 now = get_clock_now (); + LONG64 ts = get_exp_ts (); + LONG64 exp_cnt; + + /* Make concessions for unexact realtime clock */ + if (ts > now) + ts = now - 1; + exp_cnt = (now - ts) / interval; + InterlockedAdd64 (&overrun_count, exp_cnt); + ts += interval * exp_cnt; + set_exp_ts (ts); + /* NtSetTimer allows periods of up to 24 days only. If the time + is longer, we set the timer up as one-shot timer for each + interval. Restart timer here with new due time. */ + if (interval > INT_MAX * (NS100PERSEC / MSPERSEC)) + { + /* TODO: CLOCK_REALTIME_ALARM / CLOCK_BOOTTIME_ALARM + See comment in arm_timer */ + BOOL Resume = FALSE; + LARGE_INTEGER DueTime = { QuadPart: -interval }; + + NtSetTimer (timer, &DueTime, NULL, NULL, Resume, 0, NULL); + } + } + switch (evp.sigev_notify) + { + case SIGEV_SIGNAL: + { + if (arm_overrun_event ()) + { + debug_printf ("%p timer signal already queued", this); + break; + } + siginfo_t si = {0}; + si.si_signo = evp.sigev_signo; + si.si_code = SI_TIMER; + si.si_tid = (timer_t) this; + si.si_sigval.sival_ptr = evp.sigev_value.sival_ptr; + debug_printf ("%p sending signal %d", this, evp.sigev_signo); + sig_send (myself_nowait, si); + break; + } + case SIGEV_THREAD: + { + if (arm_overrun_event ()) + { + debug_printf ("%p timer thread already queued", this); + break; + } + pthread_t notify_thread; + debug_printf ("%p starting thread", this); + pthread_attr_t *attr; + pthread_attr_t default_attr; + if (evp.sigev_notify_attributes) + attr = evp.sigev_notify_attributes; + else + { + pthread_attr_init(attr = &default_attr); + pthread_attr_setdetachstate (attr, PTHREAD_CREATE_DETACHED); + } + int rc = pthread_create (¬ify_thread, attr, + notify_thread_wrapper, this); + if (rc) + { + debug_printf ("thread creation failed, %E"); + return 0; + } + break; + } + } + /* one-shot timer? */ + if (!interval) + { + memset (&time_spec, 0, sizeof time_spec); + exp_ts = 0; + overrun_event_running = OVR_EVENT_DISARMED; + ReleaseSRWLockExclusive (&srwlock); + goto out; + } + ReleaseSRWLockExclusive (&srwlock); + debug_printf ("looping"); + } + +out: + _my_tls._ctinfo->auto_release (); /* automatically return the cygthread to the cygthread pool */ + return 0; +} + +static DWORD WINAPI +timer_thread (VOID *x) +{ + timer_tracker *tt = ((timer_tracker *) x); + return tt->thread_func (); +} + +int +timer_tracker::gettime (itimerspec *curr_value, bool lock) +{ + if (lock) + { + AcquireSRWLockExclusive (&srwlock); + if (!is_timer_tracker ()) + { + ReleaseSRWLockExclusive (&srwlock); + return -EINVAL; + } + } + if (!cancel_evt) + memset (curr_value, 0, sizeof (*curr_value)); + else + { + LONG64 next_relative_exp = get_exp_ts () - get_clock_now (); + curr_value->it_value.tv_sec = next_relative_exp / NS100PERSEC; + next_relative_exp -= curr_value->it_value.tv_sec * NS100PERSEC; + curr_value->it_value.tv_nsec = next_relative_exp + * (NSPERSEC / NS100PERSEC); + curr_value->it_interval = time_spec.it_interval; + } + if (lock) + ReleaseSRWLockExclusive (&srwlock); + return 0; +} + +int +timer_tracker::settime (int flags, const itimerspec *new_value, + itimerspec *old_value) +{ + int ret = -1; + + __try + { + if (!new_value || !valid_timespec (new_value->it_value) + || !valid_timespec (new_value->it_interval)) + { + ret = -EINVAL; + __leave; + } + + AcquireSRWLockExclusive (&srwlock); + if (!is_timer_tracker ()) + { + ReleaseSRWLockExclusive (&srwlock); + ret = -EINVAL; + __leave; + } + + if (old_value) + gettime (old_value, false); + + if (!new_value->it_value.tv_sec && !new_value->it_value.tv_nsec) + { + cancel (); + memset (&time_spec, 0, sizeof time_spec); + interval = 0; + exp_ts = 0; + } + else + { + LONG64 ts; + LARGE_INTEGER DueTime; + BOOLEAN Resume; + LONG Period; + NTSTATUS status; + + if (!timer) + { + OBJECT_ATTRIBUTES attr; + + InitializeObjectAttributes (&attr, NULL, 0, NULL, + sec_none_nih.lpSecurityDescriptor); + status = NtCreateEvent (&cancel_evt, EVENT_ALL_ACCESS, &attr, + NotificationEvent, FALSE); + if (!NT_SUCCESS (status)) + { + ret = -geterrno_from_nt_status (status); + __leave; + } + status = NtCreateEvent (&sync_thr, EVENT_ALL_ACCESS, &attr, + NotificationEvent, FALSE); + if (!NT_SUCCESS (status)) + { + NtClose (cancel_evt); + cancel_evt = NULL; + ret = -geterrno_from_nt_status (status); + __leave; + } + status = NtCreateTimer (&timer, TIMER_ALL_ACCESS, &attr, + SynchronizationTimer); + if (!NT_SUCCESS (status)) + { + NtClose (cancel_evt); + NtClose (sync_thr); + cancel_evt = sync_thr = NULL; + ret = -geterrno_from_nt_status (status); + __leave; + } + } + ResetEvent (cancel_evt); + ResetEvent (sync_thr); + NtCancelTimer (timer, NULL); + /* Convert incoming itimerspec into 100ns interval and timestamp */ + interval = new_value->it_interval.tv_sec * NS100PERSEC + + (new_value->it_interval.tv_nsec + + (NSPERSEC / NS100PERSEC) - 1) + / (NSPERSEC / NS100PERSEC); + ts = new_value->it_value.tv_sec * NS100PERSEC + + (new_value->it_value.tv_nsec + (NSPERSEC / NS100PERSEC) - 1) + / (NSPERSEC / NS100PERSEC); + if (flags & TIMER_ABSTIME) + { + if (clock_id == CLOCK_REALTIME) + DueTime.QuadPart = ts + FACTOR; + else /* non-REALTIME clocks require relative DueTime. */ + { + DueTime.QuadPart = get_clock_now () - ts; + /* If the timestamp was earlier than now, compute number + of expirations and offset DueTime to expire immediately. */ + if (DueTime.QuadPart >= 0) + DueTime.QuadPart = -1LL; + } + } + else + { + /* Keep relative timestamps relative for the timer, but store the + expiry timestamp absolute for the timer thread. */ + DueTime.QuadPart = -ts; + ts += get_clock_now (); + } + set_exp_ts (ts); + time_spec = *new_value; + overrun_count_curr = 0; + overrun_count = 0; + overrun_event_running = OVR_EVENT_DISARMED; + /* TODO: CLOCK_REALTIME_ALARM / CLOCK_BOOTTIME_ALARM + Note: Advanced Power Settings -> Sleep -> Allow Wake Timers + since W10 1709 */ + Resume = FALSE; + if (interval > INT_MAX * (NS100PERSEC / MSPERSEC)) + Period = 0; + else + Period = (interval + (NS100PERSEC / MSPERSEC) - 1) + / (NS100PERSEC / MSPERSEC); + status = NtSetTimer (timer, &DueTime, NULL, NULL, Resume, Period, + NULL); + if (!NT_SUCCESS (status)) + { + memset (&time_spec, 0, sizeof time_spec); + interval = 0; + exp_ts = 0; + ret = -geterrno_from_nt_status (status); + __leave; + } + new cygthread (timer_thread, this, "itimer", sync_thr); + } + ReleaseSRWLockExclusive (&srwlock); + ret = 0; + } + __except (EFAULT) {} + __endtry + return ret; +} + +/* The timers are stored on the cygheap. */ +#define cnew(name, ...) \ + ({ \ + void* ptr = (void*) ccalloc (HEAP_3_TIMER, 1, sizeof (name)); \ + ptr ? new (ptr) name (__VA_ARGS__) : NULL; \ + }) + +extern "C" int +timer_create (clockid_t clock_id, struct sigevent *__restrict evp, + timer_t *__restrict timerid) +{ + int ret = -1; + + __try + { + if (CLOCKID_IS_PROCESS (clock_id) || CLOCKID_IS_THREAD (clock_id)) + { + set_errno (ENOTSUP); + return -1; + } + + if (clock_id >= MAX_CLOCKS) + { + set_errno (EINVAL); + return -1; + } + + *timerid = (timer_t) cnew (timer_tracker, clock_id, evp); + ret = 0; + } + __except (EFAULT) {} + __endtry + return ret; +} + +extern "C" int +timer_gettime (timer_t timerid, struct itimerspec *ovalue) +{ + int ret = -1; + + __try + { + timer_tracker *tt = (timer_tracker *) timerid; + if (!tt->is_timer_tracker ()) + { + set_errno (EINVAL); + return -1; + } + + ret = tt->gettime (ovalue, true); + if (ret < 0) + set_errno (-ret); + } + __except (EFAULT) {} + __endtry + return ret; +} + +extern "C" int +timer_settime (timer_t timerid, int flags, + const struct itimerspec *__restrict value, + struct itimerspec *__restrict ovalue) +{ + int ret = -1; + + __try + { + timer_tracker *tt = (timer_tracker *) timerid; + if (!tt->is_timer_tracker ()) + { + set_errno (EINVAL); + __leave; + } + ret = tt->settime (flags, value, ovalue); + if (ret < 0) + set_errno (-ret); + } + __except (EFAULT) {} + __endtry + return ret; +} + +extern "C" int +timer_getoverrun (timer_t timerid) +{ + int ret = -1; + + __try + { + timer_tracker *tt = (timer_tracker *) timerid; + if (!tt->is_timer_tracker ()) + { + set_errno (EINVAL); + __leave; + } + LONG64 ov_cnt = tt->getoverrun (); + if (ov_cnt > DELAYTIMER_MAX || ov_cnt < 0) + ret = DELAYTIMER_MAX; + else + ret = ov_cnt; + } + __except (EFAULT) {} + __endtry + return ret; +} + +extern "C" int +timer_delete (timer_t timerid) +{ + int ret = -1; + + __try + { + timer_tracker *in_tt = (timer_tracker *) timerid; + if (!in_tt->is_timer_tracker () || in_tt == &itimer_tracker) + { + set_errno (EINVAL); + __leave; + } + delete in_tt; + } + __except (EFAULT) {} + __endtry + return ret; +} + +extern "C" int +setitimer (int which, const struct itimerval *__restrict value, + struct itimerval *__restrict ovalue) +{ + int ret; + if (which != ITIMER_REAL) + { + set_errno (EINVAL); + ret = -1; + } + else + { + struct itimerspec spec_value, spec_ovalue; + spec_value.it_interval.tv_sec = value->it_interval.tv_sec; + spec_value.it_interval.tv_nsec = value->it_interval.tv_usec * 1000; + spec_value.it_value.tv_sec = value->it_value.tv_sec; + spec_value.it_value.tv_nsec = value->it_value.tv_usec * 1000; + ret = timer_settime ((timer_t) &itimer_tracker, 0, + &spec_value, &spec_ovalue); + if (ret) + ret = -1; + else if (ovalue) + { + ovalue->it_interval.tv_sec = spec_ovalue.it_interval.tv_sec; + ovalue->it_interval.tv_usec = spec_ovalue.it_interval.tv_nsec / 1000; + ovalue->it_value.tv_sec = spec_ovalue.it_value.tv_sec; + ovalue->it_value.tv_usec = spec_ovalue.it_value.tv_nsec / 1000; + } + } + syscall_printf ("%R = setitimer()", ret); + return ret; +} + + +extern "C" int +getitimer (int which, struct itimerval *ovalue) +{ + int ret = -1; + + if (which != ITIMER_REAL) + set_errno (EINVAL); + else + { + __try + { + struct itimerspec spec_ovalue; + ret = timer_gettime ((timer_t) &itimer_tracker, &spec_ovalue); + if (!ret) + { + ovalue->it_interval.tv_sec = spec_ovalue.it_interval.tv_sec; + ovalue->it_interval.tv_usec = spec_ovalue.it_interval.tv_nsec / 1000; + ovalue->it_value.tv_sec = spec_ovalue.it_value.tv_sec; + ovalue->it_value.tv_usec = spec_ovalue.it_value.tv_nsec / 1000; + } + } + __except (EFAULT) {} + __endtry + } + syscall_printf ("%R = getitimer()", ret); + return ret; +} + +/* FIXME: POSIX - alarm survives exec */ +extern "C" unsigned int +alarm (unsigned int seconds) +{ + struct itimerspec newt = {}, oldt; + /* alarm cannot fail, but only needs not be + correct for arguments < 64k. Truncate */ + if (seconds > (CLOCK_DELAY_MAX / 1000 - 1)) + seconds = (CLOCK_DELAY_MAX / 1000 - 1); + newt.it_value.tv_sec = seconds; + timer_settime ((timer_t) &itimer_tracker, 0, &newt, &oldt); + int ret = oldt.it_value.tv_sec + (oldt.it_value.tv_nsec > 0); + syscall_printf ("%d = alarm(%u)", ret, seconds); + return ret; +} + +extern "C" useconds_t +ualarm (useconds_t value, useconds_t interval) +{ + struct itimerspec timer = {}, otimer; + /* ualarm cannot fail. + Interpret negative arguments as zero */ + if (value > 0) + { + timer.it_value.tv_sec = value / USPERSEC; + timer.it_value.tv_nsec = (value % USPERSEC) * (NSPERSEC/USPERSEC); + } + if (interval > 0) + { + timer.it_interval.tv_sec = interval / USPERSEC; + timer.it_interval.tv_nsec = (interval % USPERSEC) * (NSPERSEC/USPERSEC); + } + timer_settime ((timer_t) &itimer_tracker, 0, &timer, &otimer); + useconds_t ret = otimer.it_value.tv_sec * USPERSEC + + (otimer.it_value.tv_nsec + (NSPERSEC/USPERSEC) - 1) + / (NSPERSEC/USPERSEC); + syscall_printf ("%d = ualarm(%ld , %ld)", ret, value, interval); + return ret; +} diff --git a/winsup/cygwin/posix_timer.h b/winsup/cygwin/posix_timer.h new file mode 100644 index 000000000..04a383fd6 --- /dev/null +++ b/winsup/cygwin/posix_timer.h @@ -0,0 +1,55 @@ +/* timer.h: Define class timer_tracker, base class for posix timers + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#ifndef __TIMER_H__ +#define __TIMER_H__ + +#define TT_MAGIC 0x513e4a1c +class timer_tracker +{ + unsigned magic; + SRWLOCK srwlock; + clockid_t clock_id; + sigevent evp; + struct itimerspec time_spec; + HANDLE timer; + HANDLE cancel_evt; + HANDLE sync_thr; + LONG64 interval; + LONG64 exp_ts; + LONG overrun_event_running; + LONG overrun_count_curr; + LONG64 overrun_count; + + bool cancel (); + + public: + void *operator new (size_t, void *p) __attribute__ ((nothrow)) {return p;} + void operator delete (void *p) { cfree (p); } + timer_tracker (clockid_t, const sigevent *); + ~timer_tracker (); + inline bool is_timer_tracker () const { return magic == TT_MAGIC; } + inline sigevent_t *sigevt () { return &evp; } + inline int getoverrun () const { return overrun_count_curr; } + + LONG64 get_clock_now () const { return get_clock (clock_id)->n100secs (); } + LONG64 get_exp_ts () const { return exp_ts; } + LONG64 get_interval () const { return interval; } + void set_exp_ts (LONG64 ts) { exp_ts = ts; } + + LONG arm_overrun_event (); + LONG disarm_overrun_event (); + + int gettime (itimerspec *, bool); + int settime (int, const itimerspec *, itimerspec *); + + DWORD thread_func (); + static void fixup_after_fork (); +}; + +#endif /* __TIMER_H__ */ diff --git a/winsup/cygwin/signal.cc b/winsup/cygwin/signal.cc index fdde26058..abefedd7b 100644 --- a/winsup/cygwin/signal.cc +++ b/winsup/cygwin/signal.cc @@ -21,7 +21,7 @@ details. */ #include "dtable.h" #include "cygheap.h" #include "cygwait.h" -#include "timer.h" +#include "posix_timer.h" #define _SA_NORESTART 0x8000 @@ -619,7 +619,7 @@ sigwait_common (const sigset_t *set, siginfo_t *info, PLARGE_INTEGER waittime) { timer_tracker *tt = (timer_tracker *) _my_tls.infodata.si_tid; - _my_tls.infodata.si_overrun = tt->disarm_event (); + _my_tls.infodata.si_overrun = tt->disarm_overrun_event (); } if (info) *info = _my_tls.infodata; diff --git a/winsup/cygwin/timer.cc b/winsup/cygwin/timer.cc deleted file mode 100644 index e24edd5e9..000000000 --- a/winsup/cygwin/timer.cc +++ /dev/null @@ -1,598 +0,0 @@ -/* timer.cc: posix timers - -This file is part of Cygwin. - -This software is a copyrighted work licensed under the terms of the -Cygwin license. Please consult the file "CYGWIN_LICENSE" for -details. */ - -#include "winsup.h" -#include "thread.h" -#include "cygtls.h" -#include "sigproc.h" -#include "path.h" -#include "fhandler.h" -#include "dtable.h" -#include "cygheap.h" -#include "timer.h" -#include - -#define EVENT_DISARMED 0 -#define EVENT_ARMED -1 -#define EVENT_LOCK 1 - -/* Must not be NO_COPY to avoid memory leak after fork. */ -timer_tracker ttstart (CLOCK_REALTIME, NULL); - -class lock_timer_tracker -{ - static muto protect; -public: - lock_timer_tracker (); - ~lock_timer_tracker (); -}; - -muto NO_COPY lock_timer_tracker::protect; - -lock_timer_tracker::lock_timer_tracker () -{ - protect.init ("timer_protect")->acquire (); -} - -lock_timer_tracker::~lock_timer_tracker () -{ - protect.release (); -} - -bool -timer_tracker::cancel () -{ - if (!hcancel) - return false; - - SetEvent (hcancel); - DWORD res = WaitForSingleObject (syncthread, INFINITE); - if (res != WAIT_OBJECT_0) - system_printf ("WFSO returned unexpected value %u, %E", res); - return true; -} - -timer_tracker::~timer_tracker () -{ - if (cancel ()) - { - CloseHandle (hcancel); -#ifdef DEBUGGING - hcancel = NULL; -#endif - } - if (syncthread) - CloseHandle (syncthread); - magic = 0; -} - -timer_tracker::timer_tracker (clockid_t c, const sigevent *e) -{ - if (e != NULL) - evp = *e; - else - { - evp.sigev_notify = SIGEV_SIGNAL; - evp.sigev_signo = SIGALRM; - evp.sigev_value.sival_ptr = this; - } - clock_id = c; - magic = TT_MAGIC; - hcancel = NULL; - event_running = EVENT_DISARMED; - overrun_count_curr = 0; - overrun_count = 0; - if (this != &ttstart) - { - lock_timer_tracker here; - next = ttstart.next; - ttstart.next = this; - } -} - -static inline int64_t -timespec_to_us (const timespec& ts) -{ - int64_t res = ts.tv_sec; - res *= USPERSEC; - res += (ts.tv_nsec + (NSPERSEC/USPERSEC) - 1) / (NSPERSEC/USPERSEC); - return res; -} - -/* Returns 0 if arming successful, -1 if a signal is already queued. - If so, it also increments overrun_count. */ -LONG -timer_tracker::arm_event () -{ - LONG ret; - - while ((ret = InterlockedCompareExchange (&event_running, EVENT_ARMED, - EVENT_DISARMED)) == EVENT_LOCK) - yield (); - if (ret == EVENT_ARMED) - InterlockedIncrement64 (&overrun_count); - return ret; -} - -LONG -timer_tracker::disarm_event () -{ - LONG ret; - - while ((ret = InterlockedCompareExchange (&event_running, EVENT_LOCK, - EVENT_ARMED)) == EVENT_LOCK) - yield (); - if (ret == EVENT_ARMED) - { - LONG64 ov_cnt; - - InterlockedExchange64 (&ov_cnt, overrun_count); - if (ov_cnt > DELAYTIMER_MAX || ov_cnt < 0) - overrun_count_curr = DELAYTIMER_MAX; - else - overrun_count_curr = ov_cnt; - ret = overrun_count_curr; - InterlockedExchange64 (&overrun_count, 0); - InterlockedExchange (&event_running, EVENT_DISARMED); - } - return ret; -} - -static void * -notify_thread_wrapper (void *arg) -{ - timer_tracker *tt = (timer_tracker *) arg; - sigevent_t *evt = tt->sigevt (); - void * (*notify_func) (void *) = (void * (*) (void *)) - evt->sigev_notify_function; - - tt->disarm_event (); - return notify_func (evt->sigev_value.sival_ptr); -} - -DWORD -timer_tracker::thread_func () -{ - int64_t now; - int64_t cur_sleepto_us = sleepto_us; - while (1) - { - int64_t sleep_us; - LONG sleep_ms; - /* Account for delays in starting thread - and sending the signal */ - now = get_clock (clock_id)->usecs (); - sleep_us = cur_sleepto_us - now; - if (sleep_us > 0) - { - sleepto_us = cur_sleepto_us; - sleep_ms = (sleep_us + (USPERSEC / MSPERSEC) - 1) - / (USPERSEC / MSPERSEC); - } - else - { - int64_t num_intervals = (now - cur_sleepto_us) / interval_us; - InterlockedAdd64 (&overrun_count, num_intervals); - cur_sleepto_us += num_intervals * interval_us; - sleepto_us = cur_sleepto_us; - sleep_ms = 0; - } - - debug_printf ("%p waiting for %u ms", this, sleep_ms); - switch (WaitForSingleObject (hcancel, sleep_ms)) - { - case WAIT_TIMEOUT: - debug_printf ("timed out"); - break; - case WAIT_OBJECT_0: - debug_printf ("%p cancelled", this); - goto out; - default: - debug_printf ("%p wait failed, %E", this); - goto out; - } - - switch (evp.sigev_notify) - { - case SIGEV_SIGNAL: - { - if (arm_event ()) - { - debug_printf ("%p timer signal already queued", this); - break; - } - siginfo_t si = {0}; - si.si_signo = evp.sigev_signo; - si.si_code = SI_TIMER; - si.si_tid = (timer_t) this; - si.si_sigval.sival_ptr = evp.sigev_value.sival_ptr; - debug_printf ("%p sending signal %d", this, evp.sigev_signo); - sig_send (myself_nowait, si); - break; - } - case SIGEV_THREAD: - { - if (arm_event ()) - { - debug_printf ("%p timer thread already queued", this); - break; - } - pthread_t notify_thread; - debug_printf ("%p starting thread", this); - pthread_attr_t *attr; - pthread_attr_t default_attr; - if (evp.sigev_notify_attributes) - attr = evp.sigev_notify_attributes; - else - { - pthread_attr_init(attr = &default_attr); - pthread_attr_setdetachstate (attr, PTHREAD_CREATE_DETACHED); - } - int rc = pthread_create (¬ify_thread, attr, - notify_thread_wrapper, this); - if (rc) - { - debug_printf ("thread creation failed, %E"); - return 0; - } - break; - } - } - if (!interval_us) - break; - - cur_sleepto_us = sleepto_us + interval_us; - debug_printf ("looping"); - } - -out: - _my_tls._ctinfo->auto_release (); /* automatically return the cygthread to the cygthread pool */ - return 0; -} - -static DWORD WINAPI -timer_thread (VOID *x) -{ - timer_tracker *tt = ((timer_tracker *) x); - return tt->thread_func (); -} - -static inline bool -timespec_bad (const timespec& t) -{ - if (t.tv_nsec < 0 || t.tv_nsec >= NSPERSEC || t.tv_sec < 0) - { - set_errno (EINVAL); - return true; - } - return false; -} - -int -timer_tracker::settime (int in_flags, const itimerspec *value, itimerspec *ovalue) -{ - int ret = -1; - - __try - { - if (!value) - { - set_errno (EINVAL); - __leave; - } - - if (timespec_bad (value->it_value) || timespec_bad (value->it_interval)) - __leave; - - lock_timer_tracker here; - cancel (); - - if (ovalue) - gettime (ovalue); - - if (!value->it_value.tv_sec && !value->it_value.tv_nsec) - interval_us = sleepto_us = 0; - else - { - interval_us = timespec_to_us (value->it_interval); - sleepto_us = timespec_to_us (value->it_value); - if (!(in_flags & TIMER_ABSTIME)) - sleepto_us += get_clock (clock_id)->usecs (); - it_interval = value->it_interval; - if (!hcancel) - hcancel = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL); - else - ResetEvent (hcancel); - if (!syncthread) - syncthread = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL); - else - ResetEvent (syncthread); - new cygthread (timer_thread, this, "itimer", syncthread); - } - ret = 0; - } - __except (EFAULT) {} - __endtry - return ret; -} - -void -timer_tracker::gettime (itimerspec *ovalue) -{ - if (!hcancel) - memset (ovalue, 0, sizeof (*ovalue)); - else - { - ovalue->it_interval = it_interval; - int64_t now = get_clock (clock_id)->usecs (); - int64_t left_us = sleepto_us - now; - if (left_us < 0) - left_us = 0; - ovalue->it_value.tv_sec = left_us / USPERSEC; - ovalue->it_value.tv_nsec = (left_us % USPERSEC) * (NSPERSEC/USPERSEC); - } -} - -int -timer_tracker::clean_and_unhook () -{ - for (timer_tracker *tt = &ttstart; tt->next != NULL; tt = tt->next) - if (tt->next == this) - { - tt->next = this->next; - return 0; - } - return -1; -} - -void -timer_tracker::fixup_after_fork () -{ - ttstart.hcancel = ttstart.syncthread = NULL; - ttstart.event_running = EVENT_DISARMED; - ttstart.overrun_count_curr = 0; - ttstart.overrun_count = 0; - for (timer_tracker *tt = &ttstart; tt->next != NULL; /* nothing */) - { - timer_tracker *deleteme = tt->next; - tt->next = deleteme->next; - deleteme->hcancel = deleteme->syncthread = NULL; - delete deleteme; - } -} - -void -fixup_timers_after_fork () -{ - timer_tracker::fixup_after_fork (); -} - -extern "C" int -timer_gettime (timer_t timerid, struct itimerspec *ovalue) -{ - int ret = -1; - - __try - { - timer_tracker *tt = (timer_tracker *) timerid; - if (!tt->is_timer_tracker ()) - { - set_errno (EINVAL); - return -1; - } - - tt->gettime (ovalue); - ret = 0; - } - __except (EFAULT) {} - __endtry - return ret; -} - -extern "C" int -timer_create (clockid_t clock_id, struct sigevent *__restrict evp, - timer_t *__restrict timerid) -{ - int ret = -1; - - __try - { - if (CLOCKID_IS_PROCESS (clock_id) || CLOCKID_IS_THREAD (clock_id)) - { - set_errno (ENOTSUP); - return -1; - } - - if (clock_id >= MAX_CLOCKS) - { - set_errno (EINVAL); - return -1; - } - - *timerid = (timer_t) new timer_tracker (clock_id, evp); - ret = 0; - } - __except (EFAULT) {} - __endtry - return ret; -} - -extern "C" int -timer_settime (timer_t timerid, int flags, - const struct itimerspec *__restrict value, - struct itimerspec *__restrict ovalue) -{ - int ret = -1; - - __try - { - timer_tracker *tt = (timer_tracker *) timerid; - if (!tt->is_timer_tracker ()) - { - set_errno (EINVAL); - __leave; - } - ret = tt->settime (flags, value, ovalue); - } - __except (EFAULT) {} - __endtry - return ret; -} - -extern "C" int -timer_getoverrun (timer_t timerid) -{ - int ret = -1; - - __try - { - timer_tracker *tt = (timer_tracker *) timerid; - if (!tt->is_timer_tracker ()) - { - set_errno (EINVAL); - __leave; - } - LONG64 ov_cnt = tt->getoverrun (); - if (ov_cnt > DELAYTIMER_MAX || ov_cnt < 0) - ret = DELAYTIMER_MAX; - else - ret = ov_cnt; - } - __except (EFAULT) {} - __endtry - return ret; -} - -extern "C" int -timer_delete (timer_t timerid) -{ - int ret = -1; - - __try - { - timer_tracker *in_tt = (timer_tracker *) timerid; - if (!in_tt->is_timer_tracker ()) - { - set_errno (EINVAL); - __leave; - } - - lock_timer_tracker here; - if (in_tt->clean_and_unhook () == 0) - { - delete in_tt; - ret = 0; - } - else - set_errno (EINVAL); - } - __except (EFAULT) {} - __endtry - return ret; -} - -extern "C" int -setitimer (int which, const struct itimerval *__restrict value, - struct itimerval *__restrict ovalue) -{ - int ret; - if (which != ITIMER_REAL) - { - set_errno (EINVAL); - ret = -1; - } - else - { - struct itimerspec spec_value, spec_ovalue; - spec_value.it_interval.tv_sec = value->it_interval.tv_sec; - spec_value.it_interval.tv_nsec = value->it_interval.tv_usec * 1000; - spec_value.it_value.tv_sec = value->it_value.tv_sec; - spec_value.it_value.tv_nsec = value->it_value.tv_usec * 1000; - ret = timer_settime ((timer_t) &ttstart, 0, &spec_value, &spec_ovalue); - if (ret) - ret = -1; - else if (ovalue) - { - ovalue->it_interval.tv_sec = spec_ovalue.it_interval.tv_sec; - ovalue->it_interval.tv_usec = spec_ovalue.it_interval.tv_nsec / 1000; - ovalue->it_value.tv_sec = spec_ovalue.it_value.tv_sec; - ovalue->it_value.tv_usec = spec_ovalue.it_value.tv_nsec / 1000; - } - } - syscall_printf ("%R = setitimer()", ret); - return ret; -} - - -extern "C" int -getitimer (int which, struct itimerval *ovalue) -{ - int ret = -1; - - if (which != ITIMER_REAL) - set_errno (EINVAL); - else - { - __try - { - struct itimerspec spec_ovalue; - ret = timer_gettime ((timer_t) &ttstart, &spec_ovalue); - if (!ret) - { - ovalue->it_interval.tv_sec = spec_ovalue.it_interval.tv_sec; - ovalue->it_interval.tv_usec = spec_ovalue.it_interval.tv_nsec / 1000; - ovalue->it_value.tv_sec = spec_ovalue.it_value.tv_sec; - ovalue->it_value.tv_usec = spec_ovalue.it_value.tv_nsec / 1000; - } - } - __except (EFAULT) {} - __endtry - } - syscall_printf ("%R = getitimer()", ret); - return ret; -} - -/* FIXME: POSIX - alarm survives exec */ -extern "C" unsigned int -alarm (unsigned int seconds) -{ - struct itimerspec newt = {}, oldt; - /* alarm cannot fail, but only needs not be - correct for arguments < 64k. Truncate */ - if (seconds > (CLOCK_DELAY_MAX / 1000 - 1)) - seconds = (CLOCK_DELAY_MAX / 1000 - 1); - newt.it_value.tv_sec = seconds; - timer_settime ((timer_t) &ttstart, 0, &newt, &oldt); - int ret = oldt.it_value.tv_sec + (oldt.it_value.tv_nsec > 0); - syscall_printf ("%d = alarm(%u)", ret, seconds); - return ret; -} - -extern "C" useconds_t -ualarm (useconds_t value, useconds_t interval) -{ - struct itimerspec timer = {}, otimer; - /* ualarm cannot fail. - Interpret negative arguments as zero */ - if (value > 0) - { - timer.it_value.tv_sec = value / USPERSEC; - timer.it_value.tv_nsec = (value % USPERSEC) * (NSPERSEC/USPERSEC); - } - if (interval > 0) - { - timer.it_interval.tv_sec = interval / USPERSEC; - timer.it_interval.tv_nsec = (interval % USPERSEC) * (NSPERSEC/USPERSEC); - } - timer_settime ((timer_t) &ttstart, 0, &timer, &otimer); - useconds_t ret = otimer.it_value.tv_sec * USPERSEC - + (otimer.it_value.tv_nsec + (NSPERSEC/USPERSEC) - 1) - / (NSPERSEC/USPERSEC); - syscall_printf ("%d = ualarm(%ld , %ld)", ret, value, interval); - return ret; -} diff --git a/winsup/cygwin/timer.h b/winsup/cygwin/timer.h deleted file mode 100644 index dd5b165c7..000000000 --- a/winsup/cygwin/timer.h +++ /dev/null @@ -1,50 +0,0 @@ -/* timer.h: Define class timer_tracker, base class for posix timers - -This file is part of Cygwin. - -This software is a copyrighted work licensed under the terms of the -Cygwin license. Please consult the file "CYGWIN_LICENSE" for -details. */ - -#ifndef __TIMER_H__ -#define __TIMER_H__ - -#define TT_MAGIC 0x513e4a1c -class timer_tracker -{ - unsigned magic; - timer_tracker *next; - - clockid_t clock_id; - sigevent evp; - timespec it_interval; - HANDLE hcancel; - HANDLE syncthread; - int64_t interval_us; - int64_t sleepto_us; - LONG event_running; - LONG overrun_count_curr; - LONG64 overrun_count; - - bool cancel (); - - public: - timer_tracker (clockid_t, const sigevent *); - ~timer_tracker (); - inline bool is_timer_tracker () const { return magic == TT_MAGIC; } - inline sigevent_t *sigevt () { return &evp; } - inline int getoverrun () const { return overrun_count_curr; } - - void gettime (itimerspec *); - int settime (int, const itimerspec *, itimerspec *); - int clean_and_unhook (); - LONG arm_event (); - LONG disarm_event (); - - DWORD thread_func (); - static void fixup_after_fork (); -}; - -extern void fixup_timers_after_fork (); - -#endif /* __TIMER_H__ */ -- cgit v1.2.3