Cygwin: (mostly) drop NT4 and Samba < 3.0 support
[newlib-cygwin.git] / winsup / cygwin / timerfd.cc
blobc692e1bd17906c6e9d5271fc61dbccafe950cd0e
1 /* timerfd.cc: timerfd helper classes
3 This file is part of Cygwin.
5 This software is a copyrighted work licensed under the terms of the
6 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
7 details. */
9 #include "winsup.h"
10 #include "path.h"
11 #include "fhandler.h"
12 #include "pinfo.h"
13 #include "dtable.h"
14 #include "cygheap.h"
15 #include "cygerrno.h"
16 #include <sys/timerfd.h>
17 #include "timerfd.h"
19 #define TFD_CANCEL_FLAGS (TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET)
21 /* Unfortunately MsgWaitForMultipleObjectsEx does not receive WM_TIMECHANGED
22 messages without a window defined in this process. Create a hidden window
23 for that purpose. */
25 void
26 timerfd_tracker::create_timechange_window ()
28 WNDCLASSW wclass = { 0 };
29 WCHAR cname[NAME_MAX];
31 __small_swprintf (cname, L"Cygwin.timerfd.%p", this);
32 wclass.lpfnWndProc = DefWindowProcW;
33 wclass.hInstance = user_data->hmodule;
34 wclass.lpszClassName = cname;
35 /* This sleep is required on Windows 10 64 bit only, and only when running
36 under strace. One of the child processes inheriting the timerfd
37 descriptor will get a STATUS_FLOAT_INEXACT_RESULT exception inside of
38 msvcrt.dll. While this is completely crazy in itself, it's apparently
39 some timing problem. It occurs in 4 out of 5 runs under strace only.
40 The sleep is required before calling RegisterClassW. Moving it before
41 CreateWindowExW does not work. What the heck? */
42 if (being_debugged ())
43 Sleep (1L);
44 atom = RegisterClassW (&wclass);
45 if (!atom)
46 debug_printf ("RegisterClass %E");
47 else
49 window = CreateWindowExW (0, cname, cname, WS_POPUP, 0, 0, 0, 0,
50 NULL, NULL, user_data->hmodule, NULL);
51 if (!window)
52 debug_printf ("CreateWindowEx %E");
56 void
57 timerfd_tracker::delete_timechange_window ()
59 if (window)
60 DestroyWindow (window);
61 if (atom)
62 UnregisterClassW ((LPWSTR) (uintptr_t) atom, user_data->hmodule);
65 void
66 timerfd_tracker::handle_timechange_window ()
68 MSG msg;
70 while (PeekMessageW (&msg, NULL, 0, 0, PM_REMOVE | PM_QS_POSTMESSAGE)
71 && msg.message != WM_QUIT)
73 DispatchMessageW (&msg);
74 if (msg.message == WM_TIMECHANGE
75 && get_clockid () == CLOCK_REALTIME
76 && (get_flags () & TFD_CANCEL_FLAGS) == TFD_CANCEL_FLAGS
77 && enter_critical_section ())
79 /* make sure to handle each WM_TIMECHANGE only once! */
80 if (msg.time != tc_time ())
82 set_expiration_count (-1LL);
83 disarm_timer ();
84 timer_expired ();
85 set_tc_time (msg.time);
87 leave_critical_section ();
92 /* Like enter_critical_section, but returns -1 on a cancel event. */
93 int
94 timerfd_tracker::enter_critical_section_cancelable ()
96 HANDLE w[2] = { cancel_evt, _access_mtx };
97 DWORD waitret = WaitForMultipleObjects (2, w, FALSE, INFINITE);
99 switch (waitret)
101 case WAIT_OBJECT_0:
102 return -1;
103 case WAIT_OBJECT_0 + 1:
104 case WAIT_ABANDONED_0 + 1:
105 return 1;
106 default:
107 return 0;
111 DWORD
112 timerfd_tracker::thread_func ()
114 /* Outer loop: Is the timer armed? If not, wait for it. */
115 HANDLE armed[2] = { arm_evt (),
116 cancel_evt };
118 create_timechange_window ();
119 while (1)
121 switch (MsgWaitForMultipleObjectsEx (2, armed, INFINITE, QS_POSTMESSAGE,
122 MWMO_INPUTAVAILABLE))
124 case WAIT_OBJECT_0:
125 break;
126 case WAIT_OBJECT_0 + 1:
127 goto canceled;
128 case WAIT_OBJECT_0 + 2:
129 handle_timechange_window ();
130 continue;
131 default:
132 continue;
135 /* Inner loop: Timer expired? If not, wait for it. */
136 HANDLE expired[3] = { timer (),
137 disarm_evt (),
138 cancel_evt };
140 while (1)
142 switch (MsgWaitForMultipleObjectsEx (3, expired, INFINITE,
143 QS_POSTMESSAGE,
144 MWMO_INPUTAVAILABLE))
146 case WAIT_OBJECT_0:
147 break;
148 case WAIT_OBJECT_0 + 1:
149 goto disarmed;
150 case WAIT_OBJECT_0 + 2:
151 goto canceled;
152 case WAIT_OBJECT_0 + 3:
153 handle_timechange_window ();
154 continue;
155 default:
156 continue;
159 int ec = enter_critical_section_cancelable ();
160 if (ec < 0)
161 goto canceled;
162 else if (!ec)
163 continue;
164 /* Make sure we haven't been abandoned and/or disarmed
165 in the meantime */
166 if (expiration_count () == -1LL
167 || IsEventSignalled (disarm_evt ()))
169 leave_critical_section ();
170 goto disarmed;
172 /* One-shot timer? */
173 if (!get_interval ())
175 /* Set expiration count, disarm timer */
176 increment_expiration_count (1);
177 disarm_timer ();
179 else
181 /* Compute expiration count. */
182 LONG64 now = get_clock_now ();
183 LONG64 ts = get_exp_ts ();
184 LONG64 exp_cnt;
186 /* Make concessions for unexact realtime clock */
187 if (ts > now)
188 ts = now - 1;
189 exp_cnt = (now - ts + get_interval () - 1) / get_interval ();
190 increment_expiration_count (exp_cnt);
191 ts += get_interval () * exp_cnt;
192 /* Set exp_ts to current timestamp. Make sure exp_ts ends up
193 bigger than "now" and fix expiration count as required */
194 while (ts <= (now = get_clock_now ()))
196 exp_cnt = (now - ts + get_interval () - 1) / get_interval ();
197 increment_expiration_count (exp_cnt);
198 ts += get_interval () * exp_cnt;
200 set_exp_ts (ts);
201 /* NtSetTimer allows periods of up to 24 days only. If the time
202 is longer, we set the timer up as one-shot timer for each
203 interval. Restart timer here with new due time. */
204 if (get_interval () > INT_MAX * (NS100PERSEC / MSPERSEC))
206 BOOLEAN Resume = (get_clockid () == CLOCK_REALTIME_ALARM
207 || get_clockid () == CLOCK_BOOTTIME_ALARM);
208 LARGE_INTEGER DueTime = { QuadPart: -get_interval () };
210 NtSetTimer (timer (), &DueTime, NULL, NULL, Resume, 0, NULL);
213 /* Arm the expiry object */
214 timer_expired ();
215 leave_critical_section ();
217 disarmed:
221 canceled:
222 delete_timechange_window ();
223 /* automatically return the cygthread to the cygthread pool */
224 _my_tls._ctinfo->auto_release ();
225 return 0;
228 static DWORD
229 timerfd_thread (VOID *arg)
231 timerfd_tracker *tt = ((timerfd_tracker *) arg);
232 return tt->thread_func ();
236 timerfd_tracker::create (clockid_t clock_id)
238 int ret;
239 NTSTATUS status;
240 OBJECT_ATTRIBUTES attr;
242 const ACCESS_MASK access = STANDARD_RIGHTS_REQUIRED
243 | SECTION_MAP_READ | SECTION_MAP_WRITE;
244 SIZE_T vsize = wincap.page_size ();
245 LARGE_INTEGER sectionsize = { QuadPart: (LONGLONG) wincap.page_size () };
247 /* Valid clock? */
248 if (!get_clock (clock_id))
250 ret = -EINVAL;
251 goto err;
254 /* Create shared objects */
255 InitializeObjectAttributes (&attr, NULL, OBJ_INHERIT, NULL, NULL);
256 /* Create shared section */
257 status = NtCreateSection (&tfd_shared_hdl, access, &attr, &sectionsize,
258 PAGE_READWRITE, SEC_COMMIT, NULL);
259 if (!NT_SUCCESS (status))
261 ret = -geterrno_from_nt_status (status);
262 goto err;
264 /* Create access mutex */
265 status = NtCreateMutant (&_access_mtx, MUTEX_ALL_ACCESS, &attr, FALSE);
266 if (!NT_SUCCESS (status))
268 ret = -geterrno_from_nt_status (status);
269 goto err_close_tfd_shared_hdl;
271 /* Create "timer is armed" event, set to "Unsignaled" at creation time */
272 status = NtCreateEvent (&_arm_evt, EVENT_ALL_ACCESS, &attr,
273 NotificationEvent, FALSE);
274 if (!NT_SUCCESS (status))
276 ret = -geterrno_from_nt_status (status);
277 goto err_close_access_mtx;
279 /* Create "timer is disarmed" event, set to "Signaled" at creation time */
280 status = NtCreateEvent (&_disarm_evt, EVENT_ALL_ACCESS, &attr,
281 NotificationEvent, TRUE);
282 if (!NT_SUCCESS (status))
284 ret = -geterrno_from_nt_status (status);
285 goto err_close_arm_evt;
287 /* Create timer */
288 status = NtCreateTimer (&_timer, TIMER_ALL_ACCESS, &attr,
289 SynchronizationTimer);
290 if (!NT_SUCCESS (status))
292 ret = -geterrno_from_nt_status (status);
293 goto err_close_disarm_evt;
295 /* Create "timer expired" semaphore */
296 status = NtCreateEvent (&_expired_evt, EVENT_ALL_ACCESS, &attr,
297 NotificationEvent, FALSE);
298 if (!NT_SUCCESS (status))
300 ret = -geterrno_from_nt_status (status);
301 goto err_close_timer;
303 /* Create process-local cancel event for this processes timer thread
304 (has to be recreated after fork/exec)*/
305 InitializeObjectAttributes (&attr, NULL, 0, NULL, NULL);
306 status = NtCreateEvent (&cancel_evt, EVENT_ALL_ACCESS, &attr,
307 NotificationEvent, FALSE);
308 if (!NT_SUCCESS (status))
310 ret = -geterrno_from_nt_status (status);
311 goto err_close_expired_evt;
313 /* Create sync event for this processes timer thread */
314 status = NtCreateEvent (&sync_thr, EVENT_ALL_ACCESS, &attr,
315 NotificationEvent, FALSE);
316 if (!NT_SUCCESS (status))
318 ret = -geterrno_from_nt_status (status);
319 goto err_close_cancel_evt;
321 /* Create section mapping (has to be recreated after fork/exec) */
322 tfd_shared = NULL;
323 status = NtMapViewOfSection (tfd_shared_hdl, NtCurrentProcess (),
324 (void **) &tfd_shared, 0, vsize, NULL,
325 &vsize, ViewShare, 0, PAGE_READWRITE);
326 if (!NT_SUCCESS (status))
328 ret = -geterrno_from_nt_status (status);
329 goto err_close_sync_thr;
331 /* Initialize clock id */
332 set_clockid (clock_id);
333 /* Set our winpid for fixup_after_fork_exec */
334 winpid = GetCurrentProcessId ();
335 /* Start timerfd thread */
336 new cygthread (timerfd_thread, this, "timerfd", sync_thr);
337 return 0;
339 err_close_sync_thr:
340 NtClose (sync_thr);
341 err_close_cancel_evt:
342 NtClose (cancel_evt);
343 err_close_expired_evt:
344 NtClose (_expired_evt);
345 err_close_timer:
346 NtClose (_timer);
347 err_close_disarm_evt:
348 NtClose (_disarm_evt);
349 err_close_arm_evt:
350 NtClose (_arm_evt);
351 err_close_access_mtx:
352 NtClose (_access_mtx);
353 err_close_tfd_shared_hdl:
354 NtClose (tfd_shared_hdl);
355 err:
356 return ret;
359 /* Return true if this was the last instance of a timerfd, process-wide,
360 false otherwise. Basically this is a destructor, but one which may
361 notify the caller NOT to deleted the object. */
362 bool
363 timerfd_tracker::dtor ()
365 if (!enter_critical_section ())
366 return false;
367 if (decrement_instances () > 0)
369 leave_critical_section ();
370 return false;
372 if (cancel_evt)
373 SetEvent (cancel_evt);
374 if (sync_thr)
376 WaitForSingleObject (sync_thr, INFINITE);
377 NtClose (sync_thr);
379 leave_critical_section ();
380 if (tfd_shared)
381 NtUnmapViewOfSection (NtCurrentProcess (), tfd_shared);
382 if (cancel_evt)
383 NtClose (cancel_evt);
384 NtClose (tfd_shared_hdl);
385 NtClose (_expired_evt);
386 NtClose (_timer);
387 NtClose (_disarm_evt);
388 NtClose (_arm_evt);
389 NtClose (_access_mtx);
390 return true;
393 void
394 timerfd_tracker::dtor (timerfd_tracker *tfd)
396 if (tfd->dtor ())
397 cfree (tfd);
401 timerfd_tracker::ioctl_set_ticks (uint64_t new_exp_cnt)
403 LONG64 exp_cnt = (LONG64) new_exp_cnt;
404 if (exp_cnt == 0 || exp_cnt == -1LL)
405 return -EINVAL;
406 if (!enter_critical_section ())
407 return -EBADF;
408 set_expiration_count (exp_cnt);
409 timer_expired ();
410 leave_critical_section ();
411 return 0;
414 void
415 timerfd_tracker::init_fixup_after_fork_exec ()
417 /* Run this only if this is the first call, or all previous calls
418 came from close_on_exec descriptors */
419 if (winpid == GetCurrentProcessId ())
420 return;
421 tfd_shared = NULL;
422 cancel_evt = NULL;
423 sync_thr = NULL;
426 void
427 timerfd_tracker::fixup_after_fork_exec (bool execing)
429 NTSTATUS status;
430 OBJECT_ATTRIBUTES attr;
431 SIZE_T vsize = wincap.page_size ();
433 /* Run this only once per process */
434 if (winpid == GetCurrentProcessId ())
435 return;
436 /* Recreate shared section mapping */
437 tfd_shared = NULL;
438 status = NtMapViewOfSection (tfd_shared_hdl, NtCurrentProcess (),
439 (PVOID *) &tfd_shared, 0, vsize, NULL,
440 &vsize, ViewShare, 0, PAGE_READWRITE);
441 if (!NT_SUCCESS (status))
442 api_fatal ("Can't recreate shared timerfd section during %s, status %y!",
443 execing ? "execve" : "fork", status);
444 /* Create cancel event for this processes timer thread */
445 InitializeObjectAttributes (&attr, NULL, 0, NULL, NULL);
446 status = NtCreateEvent (&cancel_evt, EVENT_ALL_ACCESS, &attr,
447 NotificationEvent, FALSE);
448 if (!NT_SUCCESS (status))
449 api_fatal ("Can't recreate timerfd cancel event during %s, status %y!",
450 execing ? "execve" : "fork", status);
451 /* Create sync event for this processes timer thread */
452 InitializeObjectAttributes (&attr, NULL, 0, NULL, NULL);
453 status = NtCreateEvent (&sync_thr, EVENT_ALL_ACCESS, &attr,
454 NotificationEvent, FALSE);
455 if (!NT_SUCCESS (status))
456 api_fatal ("Can't recreate timerfd sync event during %s, status %y!",
457 execing ? "execve" : "fork", status);
458 /* Set winpid so we don't run this twice */
459 winpid = GetCurrentProcessId ();
460 new cygthread (timerfd_thread, this, "timerfd", sync_thr);
463 LONG64
464 timerfd_tracker::wait (bool nonblocking)
466 HANDLE w4[2] = { get_timerfd_handle (), NULL };
467 LONG64 ret;
469 wait_signal_arrived here (w4[1]);
470 repeat:
471 switch (WaitForMultipleObjects (2, w4, FALSE, nonblocking ? 0 : INFINITE))
473 case WAIT_OBJECT_0: /* timer event */
474 if (!enter_critical_section ())
475 ret = -EIO;
476 else
478 ret = read_and_reset_expiration_count ();
479 leave_critical_section ();
480 switch (ret)
482 case -1: /* TFD_TIMER_CANCEL_ON_SET */
483 ret = -ECANCELED;
484 break;
485 case 0: /* Another read was quicker. */
486 if (!nonblocking)
487 goto repeat;
488 ret = -EAGAIN;
489 break;
490 default: /* Return (positive) expiration count. */
491 if (ret < 0)
492 ret = INT64_MAX;
493 break;
496 break;
497 case WAIT_OBJECT_0 + 1: /* signal */
498 if (_my_tls.call_signal_handler ())
499 goto repeat;
500 ret = -EINTR;
501 break;
502 case WAIT_TIMEOUT:
503 ret = -EAGAIN;
504 break;
505 default:
506 ret = -geterrno_from_win_error ();
507 break;
509 return ret;
513 timerfd_tracker::gettime (struct itimerspec *curr_value)
515 int ret = 0;
517 __try
519 if (!enter_critical_section ())
521 ret = -EBADF;
522 __leave;
525 __except (NO_ERROR)
527 return -EFAULT;
529 __endtry
531 __try
533 if (IsEventSignalled (disarm_evt ()))
534 *curr_value = time_spec ();
535 else
537 LONG64 next_relative_exp = get_exp_ts () - get_clock_now ();
538 curr_value->it_value.tv_sec = next_relative_exp / NS100PERSEC;
539 next_relative_exp -= curr_value->it_value.tv_sec * NS100PERSEC;
540 curr_value->it_value.tv_nsec = next_relative_exp
541 * (NSPERSEC / NS100PERSEC);
542 curr_value->it_interval = time_spec ().it_interval;
544 ret = 0;
546 __except (NO_ERROR)
548 ret = -EFAULT;
550 __endtry
551 leave_critical_section ();
552 return ret;
556 timerfd_tracker::arm_timer (int flags, const struct itimerspec *new_value)
558 LONG64 interval;
559 LONG64 ts;
560 NTSTATUS status;
561 LARGE_INTEGER DueTime;
562 BOOLEAN Resume;
563 LONG Period;
565 ResetEvent (disarm_evt ());
567 /* Convert incoming itimerspec into 100ns interval and timestamp */
568 interval = new_value->it_interval.tv_sec * NS100PERSEC
569 + (new_value->it_interval.tv_nsec + (NSPERSEC / NS100PERSEC) - 1)
570 / (NSPERSEC / NS100PERSEC);
571 ts = new_value->it_value.tv_sec * NS100PERSEC
572 + (new_value->it_value.tv_nsec + (NSPERSEC / NS100PERSEC) - 1)
573 / (NSPERSEC / NS100PERSEC);
574 set_flags (flags);
575 if (flags & TFD_TIMER_ABSTIME)
577 if (get_clockid () == CLOCK_REALTIME)
578 DueTime.QuadPart = ts + FACTOR;
579 else /* non-REALTIME clocks require relative DueTime. */
581 DueTime.QuadPart = get_clock_now () - ts;
582 /* If the timestamp was earlier than now, compute number
583 of expirations and offset DueTime to expire immediately. */
584 if (DueTime.QuadPart >= 0)
585 DueTime.QuadPart = -1LL;
588 else
590 /* Keep relative timestamps relative for the timer, but store the
591 expiry timestamp absolute for the timer thread. */
592 DueTime.QuadPart = -ts;
593 ts += get_clock_now ();
595 time_spec () = *new_value;
596 set_exp_ts (ts);
597 set_interval (interval);
598 read_and_reset_expiration_count ();
599 /* Note: Advanced Power Settings -> Sleep -> Allow Wake Timers
600 since W10 1709 */
601 Resume = (get_clockid () == CLOCK_REALTIME_ALARM
602 || get_clockid () == CLOCK_BOOTTIME_ALARM);
603 if (interval > INT_MAX * (NS100PERSEC / MSPERSEC))
604 Period = 0;
605 else
606 Period = (interval + (NS100PERSEC / MSPERSEC) - 1)
607 / (NS100PERSEC / MSPERSEC);
608 status = NtSetTimer (timer (), &DueTime, NULL, NULL, Resume, Period, NULL);
609 if (!NT_SUCCESS (status))
611 disarm_timer ();
612 return -geterrno_from_nt_status (status);
615 SetEvent (arm_evt ());
616 return 0;
620 timerfd_tracker::settime (int flags, const struct itimerspec *new_value,
621 struct itimerspec *old_value)
623 int ret = 0;
625 __try
627 if (!valid_timespec (new_value->it_value)
628 || !valid_timespec (new_value->it_interval))
630 ret = -EINVAL;
631 __leave;
633 if (!enter_critical_section ())
635 ret = -EBADF;
636 __leave;
638 if (old_value && (ret = gettime (old_value)) < 0)
639 __leave;
640 if (new_value->it_value.tv_sec == 0 && new_value->it_value.tv_nsec == 0)
641 ret = disarm_timer ();
642 else
643 ret = arm_timer (flags, new_value);
644 leave_critical_section ();
645 ret = 0;
647 __except (NO_ERROR)
649 ret = -EFAULT;
651 __endtry
652 return ret;