diff --git a/winsup/cygwin/autoload.cc b/winsup/cygwin/autoload.cc index 7978287cb..b4dc77339 100644 --- a/winsup/cygwin/autoload.cc +++ b/winsup/cygwin/autoload.cc @@ -663,6 +663,7 @@ LoadDLLfunc (CreateDesktopW, 24, user32) LoadDLLfunc (CreateWindowExW, 48, user32) LoadDLLfunc (CreateWindowStationW, 16, user32) LoadDLLfunc (DefWindowProcW, 16, user32) +LoadDLLfunc (DestroyWindow, 4, user32) LoadDLLfunc (DispatchMessageW, 4, user32) LoadDLLfunc (EmptyClipboard, 0, user32) LoadDLLfunc (EnumWindows, 8, user32) @@ -690,6 +691,7 @@ LoadDLLfunc (SetClipboardData, 8, user32) LoadDLLfunc (SetParent, 8, user32) LoadDLLfunc (SetProcessWindowStation, 4, user32) LoadDLLfunc (SetThreadDesktop, 4, user32) +LoadDLLfunc (UnregisterClassW, 8, user32) LoadDLLfuncEx (CreateEnvironmentBlock, 12, userenv, 1) LoadDLLfuncEx2 (CreateProfile, 16, userenv, 1, 1) diff --git a/winsup/cygwin/timerfd.cc b/winsup/cygwin/timerfd.cc index dbb63e676..cebb002ed 100644 --- a/winsup/cygwin/timerfd.cc +++ b/winsup/cygwin/timerfd.cc @@ -16,6 +16,70 @@ details. */ #include #include "timerfd.h" +#define TFD_CANCEL_FLAGS (TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET) + +/* Unfortunately MsgWaitForMultipleObjectsEx does not receive WM_TIMECHANGED + messages without a window defined in this process. Create a hidden window + for that purpose. */ + +void +timerfd_tracker::create_timechange_window () +{ + WNDCLASSW wclass = { 0 }; + WCHAR cname[NAME_MAX]; + + __small_swprintf (cname, L"Cygwin.timerfd.%u", winpid); + wclass.lpfnWndProc = DefWindowProcW; + wclass.hInstance = GetModuleHandle (NULL); + wclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); + wclass.lpszClassName = cname; + atom = RegisterClassW (&wclass); + if (!atom) + debug_printf ("RegisterClass %E"); + else + { + window = CreateWindowExW (0, cname, cname, WS_POPUP, 0, 0, 0, 0, + NULL, NULL, NULL, NULL); + if (!window) + debug_printf ("RegisterClass %E"); + } +} + +void +timerfd_tracker::delete_timechange_window () +{ + if (window) + DestroyWindow (window); + if (atom) + UnregisterClassW ((LPWSTR) (uintptr_t) atom, GetModuleHandle (NULL)); +} + +void +timerfd_tracker::handle_timechange_window () +{ + MSG msg; + + if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE) && msg.message != WM_QUIT) + { + DispatchMessageW(&msg); + if (msg.message == WM_TIMECHANGE + && get_clockid () == CLOCK_REALTIME + && (flags () & TFD_CANCEL_FLAGS) == TFD_CANCEL_FLAGS + && enter_critical_section ()) + { + /* make sure to handle each WM_TIMECHANGE only once! */ + if (msg.time != tc_time ()) + { + set_overrun_count (-1LL); + disarm_timer (); + timer_expired (); + set_tc_time (msg.time); + } + leave_critical_section (); + } + } +} + DWORD timerfd_tracker::thread_func () { @@ -23,14 +87,19 @@ timerfd_tracker::thread_func () HANDLE armed[2] = { tfd_shared->arm_evt (), cancel_evt }; + create_timechange_window (); while (1) { - switch (WaitForMultipleObjects (2, armed, FALSE, INFINITE)) + switch (MsgWaitForMultipleObjectsEx (2, armed, INFINITE, QS_POSTMESSAGE, + MWMO_INPUTAVAILABLE)) { case WAIT_OBJECT_0: break; case WAIT_OBJECT_0 + 1: goto canceled; + case WAIT_OBJECT_0 + 2: + handle_timechange_window (); + continue; default: continue; } @@ -40,9 +109,12 @@ timerfd_tracker::thread_func () HANDLE expired[3] = { tfd_shared->timer (), tfd_shared->disarm_evt (), cancel_evt }; + while (1) { - switch (WaitForMultipleObjects (3, expired, FALSE, INFINITE)) + switch (MsgWaitForMultipleObjectsEx (3, expired, INFINITE, + QS_POSTMESSAGE, + MWMO_INPUTAVAILABLE)) { case WAIT_OBJECT_0: break; @@ -50,12 +122,23 @@ timerfd_tracker::thread_func () goto disarmed; case WAIT_OBJECT_0 + 2: goto canceled; + case WAIT_OBJECT_0 + 3: + handle_timechange_window (); + continue; default: continue; } if (!enter_critical_section ()) continue; + /* Make sure we haven't been abandoned and/or disarmed + in the meantime */ + if (overrun_count () == -1LL + || IsEventSignalled (tfd_shared->disarm_evt ())) + { + leave_critical_section (); + goto disarmed; + } /* One-shot timer? */ if (!get_interval ()) { @@ -93,7 +176,7 @@ timerfd_tracker::thread_func () NtSetTimer (tfd_shared->timer (), &DueTime, NULL, NULL, Resume, 0, NULL); } - } + } /* Arm the expiry object */ timer_expired (); leave_critical_section (); @@ -103,6 +186,7 @@ disarmed: } canceled: + delete_timechange_window (); _my_tls._ctinfo->auto_release (); /* automatically return the cygthread to the cygthread pool */ return 0; } diff --git a/winsup/cygwin/timerfd.h b/winsup/cygwin/timerfd.h index 543fad1d2..cebd1d9eb 100644 --- a/winsup/cygwin/timerfd.h +++ b/winsup/cygwin/timerfd.h @@ -31,6 +31,7 @@ class timerfd_shared LONG64 _interval; /* timer interval in 100ns */ LONG64 _overrun_count; /* expiry counter */ int _flags; /* settime flags */ + DWORD _tc_time; /* timestamp of the last WM_TIMECHANGE msg */ int create (clockid_t); bool dtor (); @@ -43,7 +44,8 @@ class timerfd_shared LONG64 get_clock_now () const { return get_clock (_clockid)->n100secs (); } struct itimerspec &time_spec () { return _time_spec; } int flags () const { return _flags; } - LONG64 overrun_count () const { return _overrun_count; } + + /* write access methods */ void increment_overrun_count (LONG64 add) { InterlockedAdd64 (&_overrun_count, add); } void set_overrun_count (LONG64 newval) @@ -55,8 +57,6 @@ class timerfd_shared ResetEvent (_expired_evt); return ret; } - - /* write access methods */ bool enter_cs () { return (WaitForSingleObject (_access_mtx, INFINITE) & ~WAIT_ABANDONED_0) @@ -73,7 +73,7 @@ class timerfd_shared memset (&_time_spec, 0, sizeof _time_spec); _exp_ts = 0; _interval = 0; - _flags = 0; + /* _flags = 0; DON'T DO THAT. Required for TFD_TIMER_CANCEL_ON_SET */ NtCancelTimer (timer (), NULL); SetEvent (_disarm_evt); return 0; @@ -86,8 +86,6 @@ class timerfd_shared class timerfd_tracker /* cygheap! */ { - DWORD winpid; /* This is used @ fork/exec time to know if - this tracker already has been fixed up. */ HANDLE tfd_shared_hdl; /* handle auf shared mem */ timerfd_shared *tfd_shared; /* pointer auf shared mem, needs NtMapViewOfSection in each new process. */ @@ -96,6 +94,14 @@ class timerfd_tracker /* cygheap! */ HANDLE sync_thr; /* cygthread sync object. */ LONG local_instance_count; /* each open fd increments this. If 0 -> cancel thread. */ + DWORD winpid; /* This is used @ fork/exec time to know if + this tracker already has been fixed up. */ + HWND window; /* window handle */ + ATOM atom; /* window class */ + + void create_timechange_window (); + void delete_timechange_window (); + void handle_timechange_window (); bool dtor (); @@ -107,9 +113,10 @@ class timerfd_tracker /* cygheap! */ int disarm_timer () const { return tfd_shared->disarm_timer (); } void timer_expired () const { tfd_shared->timer_expired (); } + LONG64 overrun_count () const { return tfd_shared->_overrun_count; } void increment_overrun_count (LONG64 add) const { tfd_shared->increment_overrun_count (add); } - void set_overrun_count (uint64_t ov_cnt) const + void set_overrun_count (LONG64 ov_cnt) const { tfd_shared->set_overrun_count ((LONG64) ov_cnt); } LONG64 read_and_reset_overrun_count () const { return tfd_shared->read_and_reset_overrun_count (); } @@ -119,9 +126,13 @@ class timerfd_tracker /* cygheap! */ struct timespec it_interval () const { return tfd_shared->time_spec ().it_interval; } + clock_t get_clockid () const { return tfd_shared->_clockid; } LONG64 get_clock_now () const { return tfd_shared->get_clock_now (); } LONG64 get_exp_ts () const { return tfd_shared->_exp_ts; } LONG64 get_interval () const { return tfd_shared->_interval; } + int flags () const { return tfd_shared->flags (); } + DWORD tc_time () const { return tfd_shared->_tc_time; } + void set_tc_time (DWORD new_time) { tfd_shared->_tc_time = new_time; } void set_exp_ts (LONG64 ts) const { tfd_shared->set_exp_ts (ts); } @@ -129,7 +140,8 @@ class timerfd_tracker /* cygheap! */ void *operator new (size_t, void *p) __attribute__ ((nothrow)) {return p;} timerfd_tracker () : tfd_shared_hdl (NULL), tfd_shared (NULL), cancel_evt (NULL), - sync_thr (NULL), local_instance_count (1) {} + sync_thr (NULL), local_instance_count (1), winpid (0), window (NULL), + atom (0) {} int create (clockid_t); int gettime (struct itimerspec *); int settime (int, const struct itimerspec *, struct itimerspec *);