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
16 #include <sys/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
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 ())
44 atom
= RegisterClassW (&wclass
);
46 debug_printf ("RegisterClass %E");
49 window
= CreateWindowExW (0, cname
, cname
, WS_POPUP
, 0, 0, 0, 0,
50 NULL
, NULL
, user_data
->hmodule
, NULL
);
52 debug_printf ("CreateWindowEx %E");
57 timerfd_tracker::delete_timechange_window ()
60 DestroyWindow (window
);
62 UnregisterClassW ((LPWSTR
) (uintptr_t) atom
, user_data
->hmodule
);
66 timerfd_tracker::handle_timechange_window ()
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);
85 set_tc_time (msg
.time
);
87 leave_critical_section ();
92 /* Like enter_critical_section, but returns -1 on a cancel event. */
94 timerfd_tracker::enter_critical_section_cancelable ()
96 HANDLE w
[2] = { cancel_evt
, _access_mtx
};
97 DWORD waitret
= WaitForMultipleObjects (2, w
, FALSE
, INFINITE
);
103 case WAIT_OBJECT_0
+ 1:
104 case WAIT_ABANDONED_0
+ 1:
112 timerfd_tracker::thread_func ()
114 /* Outer loop: Is the timer armed? If not, wait for it. */
115 HANDLE armed
[2] = { arm_evt (),
118 create_timechange_window ();
121 switch (MsgWaitForMultipleObjectsEx (2, armed
, INFINITE
, QS_POSTMESSAGE
,
122 MWMO_INPUTAVAILABLE
))
126 case WAIT_OBJECT_0
+ 1:
128 case WAIT_OBJECT_0
+ 2:
129 handle_timechange_window ();
135 /* Inner loop: Timer expired? If not, wait for it. */
136 HANDLE expired
[3] = { timer (),
142 switch (MsgWaitForMultipleObjectsEx (3, expired
, INFINITE
,
144 MWMO_INPUTAVAILABLE
))
148 case WAIT_OBJECT_0
+ 1:
150 case WAIT_OBJECT_0
+ 2:
152 case WAIT_OBJECT_0
+ 3:
153 handle_timechange_window ();
159 int ec
= enter_critical_section_cancelable ();
164 /* Make sure we haven't been abandoned and/or disarmed
166 if (expiration_count () == -1LL
167 || IsEventSignalled (disarm_evt ()))
169 leave_critical_section ();
172 /* One-shot timer? */
173 if (!get_interval ())
175 /* Set expiration count, disarm timer */
176 increment_expiration_count (1);
181 /* Compute expiration count. */
182 LONG64 now
= get_clock_now ();
183 LONG64 ts
= get_exp_ts ();
186 /* Make concessions for unexact realtime clock */
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
;
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 */
215 leave_critical_section ();
222 delete_timechange_window ();
223 /* automatically return the cygthread to the cygthread pool */
224 _my_tls
._ctinfo
->auto_release ();
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
)
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 () };
248 if (!get_clock (clock_id
))
254 /* Create shared objects */
255 InitializeObjectAttributes (&attr
, NULL
, OBJ_INHERIT
, NULL
, NULL
);
256 /* Create shared section */
257 status
= NtCreateSection (&tfd_shared_hdl
, access
, &attr
, §ionsize
,
258 PAGE_READWRITE
, SEC_COMMIT
, NULL
);
259 if (!NT_SUCCESS (status
))
261 ret
= -geterrno_from_nt_status (status
);
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
;
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) */
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
);
341 err_close_cancel_evt
:
342 NtClose (cancel_evt
);
343 err_close_expired_evt
:
344 NtClose (_expired_evt
);
347 err_close_disarm_evt
:
348 NtClose (_disarm_evt
);
351 err_close_access_mtx
:
352 NtClose (_access_mtx
);
353 err_close_tfd_shared_hdl
:
354 NtClose (tfd_shared_hdl
);
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. */
363 timerfd_tracker::dtor ()
365 if (!enter_critical_section ())
367 if (decrement_instances () > 0)
369 leave_critical_section ();
373 SetEvent (cancel_evt
);
376 WaitForSingleObject (sync_thr
, INFINITE
);
379 leave_critical_section ();
381 NtUnmapViewOfSection (NtCurrentProcess (), tfd_shared
);
383 NtClose (cancel_evt
);
384 NtClose (tfd_shared_hdl
);
385 NtClose (_expired_evt
);
387 NtClose (_disarm_evt
);
389 NtClose (_access_mtx
);
394 timerfd_tracker::dtor (timerfd_tracker
*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)
406 if (!enter_critical_section ())
408 set_expiration_count (exp_cnt
);
410 leave_critical_section ();
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 ())
427 timerfd_tracker::fixup_after_fork_exec (bool execing
)
430 OBJECT_ATTRIBUTES attr
;
431 SIZE_T vsize
= wincap
.page_size ();
433 /* Run this only once per process */
434 if (winpid
== GetCurrentProcessId ())
436 /* Recreate shared section mapping */
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
);
464 timerfd_tracker::wait (bool nonblocking
)
466 HANDLE w4
[2] = { get_timerfd_handle (), NULL
};
469 wait_signal_arrived
here (w4
[1]);
471 switch (WaitForMultipleObjects (2, w4
, FALSE
, nonblocking
? 0 : INFINITE
))
473 case WAIT_OBJECT_0
: /* timer event */
474 if (!enter_critical_section ())
478 ret
= read_and_reset_expiration_count ();
479 leave_critical_section ();
482 case -1: /* TFD_TIMER_CANCEL_ON_SET */
485 case 0: /* Another read was quicker. */
490 default: /* Return (positive) expiration count. */
497 case WAIT_OBJECT_0
+ 1: /* signal */
498 if (_my_tls
.call_signal_handler ())
506 ret
= -geterrno_from_win_error ();
513 timerfd_tracker::gettime (struct itimerspec
*curr_value
)
519 if (!enter_critical_section ())
533 if (IsEventSignalled (disarm_evt ()))
534 *curr_value
= time_spec ();
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
;
551 leave_critical_section ();
556 timerfd_tracker::arm_timer (int flags
, const struct itimerspec
*new_value
)
561 LARGE_INTEGER DueTime
;
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
);
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;
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
;
597 set_interval (interval
);
598 read_and_reset_expiration_count ();
599 /* Note: Advanced Power Settings -> Sleep -> Allow Wake Timers
601 Resume
= (get_clockid () == CLOCK_REALTIME_ALARM
602 || get_clockid () == CLOCK_BOOTTIME_ALARM
);
603 if (interval
> INT_MAX
* (NS100PERSEC
/ MSPERSEC
))
606 Period
= (interval
+ (NS100PERSEC
/ MSPERSEC
) - 1)
607 / (NS100PERSEC
/ MSPERSEC
);
608 status
= NtSetTimer (timer (), &DueTime
, NULL
, NULL
, Resume
, Period
, NULL
);
609 if (!NT_SUCCESS (status
))
612 return -geterrno_from_nt_status (status
);
615 SetEvent (arm_evt ());
620 timerfd_tracker::settime (int flags
, const struct itimerspec
*new_value
,
621 struct itimerspec
*old_value
)
627 if (!valid_timespec (new_value
->it_value
)
628 || !valid_timespec (new_value
->it_interval
))
633 if (!enter_critical_section ())
638 if (old_value
&& (ret
= gettime (old_value
)) < 0)
640 if (new_value
->it_value
.tv_sec
== 0 && new_value
->it_value
.tv_nsec
== 0)
641 ret
= disarm_timer ();
643 ret
= arm_timer (flags
, new_value
);
644 leave_critical_section ();