40481dbabb
Using posix timers "timer_tracker" as base class for timerfd was flawed. Posix timers are not inherited by child processes and don't survive execve. The method used by posix timers didn't allow to share timers between processes. The timers were still per-process timers and worked entirely separate from each other. Reading from these timers via different descriptors was only synchronized within the same process. This does not reflect the timerfd semantics in Linux: The per-file timers can be dup'ed and survive fork and execve. They are still just descriptors pointing to the same timer object originally created by timerfd_create. Synchronization is performed between all descriptor instances of the same timer, system-wide. Thus, reimplement timerfd using a timer instance in shared memory, a kernel timer, and a handful of sync objects. Every process maintains a per-process timerfd struct on the cygheap maintaining a per-process thread. Every process sharing the same timerfd will run this thread checking the state of the timer, similar to the posix timer thread, just working on the shared objects and synchronizing its job with each other thread. Drop the timerfd implementation in the posix timer code and move the public API to fhandler_timerfd.c. Keep the ttstart timer_tracker anchor out of "NO_COPY" since the fixup_after_fork code should run to avoid memory leakage. Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
516 lines
13 KiB
C++
516 lines
13 KiB
C++
/* timerfd.cc: timerfd helper classes
|
|
|
|
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 "path.h"
|
|
#include "fhandler.h"
|
|
#include "pinfo.h"
|
|
#include "dtable.h"
|
|
#include "cygheap.h"
|
|
#include "cygerrno.h"
|
|
#include <sys/timerfd.h>
|
|
#include "timerfd.h"
|
|
|
|
DWORD
|
|
timerfd_tracker::thread_func ()
|
|
{
|
|
/* Outer loop: Is the timer armed? If not, wait for it. */
|
|
HANDLE armed[2] = { tfd_shared->arm_evt (),
|
|
cancel_evt };
|
|
|
|
while (1)
|
|
{
|
|
switch (WaitForMultipleObjects (2, armed, FALSE, INFINITE))
|
|
{
|
|
case WAIT_OBJECT_0:
|
|
break;
|
|
case WAIT_OBJECT_0 + 1:
|
|
goto canceled;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
/* Inner loop: Timer expired? If not, wait for it. */
|
|
HANDLE expired[3] = { tfd_shared->timer (),
|
|
tfd_shared->disarm_evt (),
|
|
cancel_evt };
|
|
while (1)
|
|
{
|
|
switch (WaitForMultipleObjects (3, expired, FALSE, INFINITE))
|
|
{
|
|
case WAIT_OBJECT_0:
|
|
break;
|
|
case WAIT_OBJECT_0 + 1:
|
|
goto disarmed;
|
|
case WAIT_OBJECT_0 + 2:
|
|
goto canceled;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
if (!enter_critical_section ())
|
|
continue;
|
|
/* One-shot timer? */
|
|
if (!get_interval ())
|
|
{
|
|
/* Set overrun count, disarm timer */
|
|
increment_overrun_count (1);
|
|
disarm_timer ();
|
|
}
|
|
else
|
|
{
|
|
/* Compute overrun count. */
|
|
LONG64 now = get_clock_now ();
|
|
LONG64 ts = get_exp_ts ();
|
|
|
|
increment_overrun_count ((now - ts + get_interval () - 1)
|
|
/ get_interval ());
|
|
/* Set exp_ts to current timestamp. Make sure exp_ts ends up
|
|
bigger than "now" and fix overrun count as required */
|
|
while ((ts += get_interval ()) <= (now = get_clock_now ()))
|
|
increment_overrun_count ((now - ts + get_interval () - 1)
|
|
/ get_interval ());
|
|
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 (get_interval () > INT_MAX * (NS100PERSEC / MSPERSEC))
|
|
{
|
|
/* TODO: CLOCK_REALTIME_ALARM / CLOCK_BOOTTIME_ALARM
|
|
See comment in arm_timer */
|
|
BOOL Resume = FALSE;
|
|
LARGE_INTEGER DueTime = { QuadPart: -get_interval () };
|
|
|
|
NtSetTimer (tfd_shared->timer (), &DueTime, NULL, NULL,
|
|
Resume, 0, NULL);
|
|
}
|
|
}
|
|
/* Arm the expiry object */
|
|
timer_expired ();
|
|
leave_critical_section ();
|
|
}
|
|
disarmed:
|
|
;
|
|
}
|
|
|
|
canceled:
|
|
_my_tls._ctinfo->auto_release (); /* automatically return the cygthread to the cygthread pool */
|
|
return 0;
|
|
}
|
|
|
|
static DWORD WINAPI
|
|
timerfd_thread (VOID *arg)
|
|
{
|
|
timerfd_tracker *tt = ((timerfd_tracker *) arg);
|
|
return tt->thread_func ();
|
|
}
|
|
|
|
int
|
|
timerfd_shared::create (clockid_t clock_id)
|
|
{
|
|
int ret;
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES attr;
|
|
|
|
/* Create access mutex */
|
|
InitializeObjectAttributes (&attr, NULL, OBJ_INHERIT, NULL,
|
|
sec_none.lpSecurityDescriptor);
|
|
status = NtCreateMutant (&_access_mtx, MUTEX_ALL_ACCESS, &attr, FALSE);
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
ret = -geterrno_from_nt_status (status);
|
|
goto err;
|
|
}
|
|
/* Create "timer is armed" event, set to "Unsignaled" at creation time */
|
|
status = NtCreateEvent (&_arm_evt, EVENT_ALL_ACCESS, &attr,
|
|
NotificationEvent, FALSE);
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
ret = -geterrno_from_nt_status (status);
|
|
goto err_close_access_mtx;
|
|
}
|
|
/* Create "timer is disarmed" event, set to "Signaled" at creation time */
|
|
status = NtCreateEvent (&_disarm_evt, EVENT_ALL_ACCESS, &attr,
|
|
NotificationEvent, TRUE);
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
ret = -geterrno_from_nt_status (status);
|
|
goto err_close_arm_evt;
|
|
}
|
|
/* Create timer */
|
|
status = NtCreateTimer (&_timer, TIMER_ALL_ACCESS, &attr,
|
|
SynchronizationTimer);
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
ret = -geterrno_from_nt_status (status);
|
|
goto err_close_disarm_evt;
|
|
}
|
|
/* Create "timer expired" semaphore */
|
|
status = NtCreateEvent (&_expired_evt, EVENT_ALL_ACCESS, &attr,
|
|
NotificationEvent, FALSE);
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
ret = -geterrno_from_nt_status (status);
|
|
goto err_close_timer;
|
|
}
|
|
instance_count = 1;
|
|
_clockid = clock_id;
|
|
return 0;
|
|
|
|
err_close_timer:
|
|
NtClose (_timer);
|
|
err_close_disarm_evt:
|
|
NtClose (_disarm_evt);
|
|
err_close_arm_evt:
|
|
NtClose (_arm_evt);
|
|
err_close_access_mtx:
|
|
NtClose (_access_mtx);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
timerfd_tracker::create (clockid_t clock_id)
|
|
{
|
|
int ret;
|
|
clk_t *clock;
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES attr;
|
|
|
|
const ACCESS_MASK access = STANDARD_RIGHTS_REQUIRED
|
|
| SECTION_MAP_READ | SECTION_MAP_WRITE;
|
|
SIZE_T vsize = PAGE_SIZE;
|
|
LARGE_INTEGER sectionsize = { QuadPart: PAGE_SIZE };
|
|
|
|
/* Valid clock? */
|
|
clock = get_clock (clock_id);
|
|
if (!clock)
|
|
{
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
/* Create shared section. */
|
|
InitializeObjectAttributes (&attr, NULL, OBJ_INHERIT, NULL,
|
|
sec_none.lpSecurityDescriptor);
|
|
status = NtCreateSection (&tfd_shared_hdl, access, &attr,
|
|
§ionsize, PAGE_READWRITE,
|
|
SEC_COMMIT, NULL);
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
ret = -geterrno_from_nt_status (status);
|
|
goto err;
|
|
}
|
|
/* Create section mapping (has to be repeated after fork/exec */
|
|
status = NtMapViewOfSection (tfd_shared_hdl, NtCurrentProcess (),
|
|
(void **) &tfd_shared, 0, PAGE_SIZE, NULL,
|
|
&vsize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
ret = -geterrno_from_nt_status (status);
|
|
goto err_close_tfd_shared_hdl;
|
|
}
|
|
/* Create cancel even for this processes timer thread */
|
|
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);
|
|
goto err_unmap_tfd_shared;
|
|
}
|
|
ret = tfd_shared->create (clock_id);
|
|
if (ret < 0)
|
|
goto err_close_cancel_evt;
|
|
winpid = GetCurrentProcessId ();
|
|
new cygthread (timerfd_thread, this, "timerfd", sync_thr);
|
|
return 0;
|
|
|
|
err_close_cancel_evt:
|
|
NtClose (cancel_evt);
|
|
err_unmap_tfd_shared:
|
|
NtUnmapViewOfSection (NtCurrentProcess (), tfd_shared);
|
|
err_close_tfd_shared_hdl:
|
|
NtClose (tfd_shared_hdl);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
/* Return true if this was the last instance of a timerfd, session-wide,
|
|
false otherwise */
|
|
bool
|
|
timerfd_shared::dtor ()
|
|
{
|
|
if (instance_count > 0)
|
|
{
|
|
return false;
|
|
}
|
|
timer_expired ();
|
|
disarm_timer ();
|
|
NtClose (_timer);
|
|
NtClose (_arm_evt);
|
|
NtClose (_disarm_evt);
|
|
NtClose (_expired_evt);
|
|
NtClose (_access_mtx);
|
|
return true;
|
|
}
|
|
|
|
/* Return true if this was the last instance of a timerfd, session-wide,
|
|
false otherwise. Basically this is a destructor, but one which may
|
|
notify the caller NOT to deleted the object. */
|
|
bool
|
|
timerfd_tracker::dtor ()
|
|
{
|
|
if (enter_critical_section ())
|
|
{
|
|
if (local_instance_count > 0)
|
|
{
|
|
leave_critical_section ();
|
|
return false;
|
|
}
|
|
SetEvent (cancel_evt);
|
|
WaitForSingleObject (sync_thr, INFINITE);
|
|
if (tfd_shared->dtor ())
|
|
{
|
|
NtUnmapViewOfSection (NtCurrentProcess (), tfd_shared);
|
|
NtClose (tfd_shared_hdl);
|
|
}
|
|
else
|
|
leave_critical_section ();
|
|
}
|
|
NtClose (cancel_evt);
|
|
NtClose (sync_thr);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
timerfd_tracker::dtor (timerfd_tracker *tfd)
|
|
{
|
|
if (tfd->dtor ())
|
|
cfree (tfd);
|
|
}
|
|
|
|
void
|
|
timerfd_tracker::close ()
|
|
{
|
|
InterlockedDecrement (&local_instance_count);
|
|
InterlockedDecrement (&tfd_shared->instance_count);
|
|
}
|
|
|
|
void
|
|
timerfd_tracker::ioctl_set_ticks (uint64_t ov_cnt)
|
|
{
|
|
set_overrun_count (ov_cnt);
|
|
timer_expired ();
|
|
}
|
|
|
|
void
|
|
timerfd_tracker::fixup_after_fork_exec (bool execing)
|
|
{
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES attr;
|
|
SIZE_T vsize = PAGE_SIZE;
|
|
|
|
/* Run this only once per process */
|
|
if (winpid == GetCurrentProcessId ())
|
|
return;
|
|
/* Recreate shared section mapping */
|
|
status = NtMapViewOfSection (tfd_shared_hdl, NtCurrentProcess (),
|
|
(void **) &tfd_shared, 0, PAGE_SIZE, NULL,
|
|
&vsize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
|
|
if (!NT_SUCCESS (status))
|
|
api_fatal ("Can't recreate shared timerfd section during %s!",
|
|
execing ? "execve" : "fork");
|
|
/* Increment global instance count by the number of instances in this
|
|
process */
|
|
InterlockedAdd (&tfd_shared->instance_count, local_instance_count);
|
|
/* Create cancel even for this processes timer thread */
|
|
InitializeObjectAttributes (&attr, NULL, 0, NULL,
|
|
sec_none_nih.lpSecurityDescriptor);
|
|
status = NtCreateEvent (&cancel_evt, EVENT_ALL_ACCESS, &attr,
|
|
NotificationEvent, FALSE);
|
|
if (!NT_SUCCESS (status))
|
|
api_fatal ("Can't recreate timerfd cancel event during %s!",
|
|
execing ? "execve" : "fork");
|
|
/* Set winpid so we don't run this twice */
|
|
winpid = GetCurrentProcessId ();
|
|
new cygthread (timerfd_thread, this, "timerfd", sync_thr);
|
|
}
|
|
|
|
LONG64
|
|
timerfd_tracker::wait (bool nonblocking)
|
|
{
|
|
HANDLE w4[2] = { get_timerfd_handle (), NULL };
|
|
LONG64 ret;
|
|
|
|
wait_signal_arrived here (w4[1]);
|
|
repeat:
|
|
switch (WaitForMultipleObjects (2, w4, FALSE, nonblocking ? 0 : INFINITE))
|
|
{
|
|
case WAIT_OBJECT_0: /* timer event */
|
|
if (!enter_critical_section ())
|
|
ret = -EIO;
|
|
else
|
|
{
|
|
ret = read_and_reset_overrun_count ();
|
|
leave_critical_section ();
|
|
if (ret)
|
|
break;
|
|
/* A 0 overrun count indicates another read was quicker.
|
|
Continue as if we didn't catch the expiry. */
|
|
if (!nonblocking)
|
|
{
|
|
Sleep (100L);
|
|
goto repeat;
|
|
}
|
|
ret = -EAGAIN;
|
|
}
|
|
break;
|
|
case WAIT_OBJECT_0 + 1: /* signal */
|
|
if (_my_tls.call_signal_handler ())
|
|
goto repeat;
|
|
ret = -EINTR;
|
|
break;
|
|
case WAIT_TIMEOUT:
|
|
ret = -EAGAIN;
|
|
break;
|
|
default:
|
|
ret = -geterrno_from_win_error ();
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
timerfd_tracker::gettime (struct itimerspec *curr_value)
|
|
{
|
|
int ret = 0;
|
|
|
|
__try
|
|
{
|
|
if (!enter_critical_section ())
|
|
{
|
|
ret = -EBADF;
|
|
__leave;
|
|
}
|
|
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);
|
|
leave_critical_section ();
|
|
ret = 0;
|
|
}
|
|
__except (NO_ERROR)
|
|
{
|
|
ret = -EFAULT;
|
|
}
|
|
__endtry
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
timerfd_shared::arm_timer (int flags, const struct itimerspec *new_value)
|
|
{
|
|
LONG64 ts;
|
|
NTSTATUS status;
|
|
LARGE_INTEGER DueTime;
|
|
BOOLEAN Resume;
|
|
LONG Period;
|
|
|
|
ResetEvent (_disarm_evt);
|
|
|
|
/* 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);
|
|
_flags = flags;
|
|
if (flags & TFD_TIMER_ABSTIME)
|
|
{
|
|
if (_clockid == CLOCK_REALTIME)
|
|
DueTime.QuadPart = ts + FACTOR;
|
|
else /* non-REALTIME clocks require relative DueTime. */
|
|
{
|
|
DueTime.QuadPart = ts - get_clock_now ();
|
|
/* If the timestamp was earlier than now, compute number
|
|
of overruns and offset DueTime to expire immediately. */
|
|
if (DueTime.QuadPart >= 0)
|
|
{
|
|
LONG64 num_intervals = DueTime.QuadPart / _interval;
|
|
increment_overrun_count (num_intervals);
|
|
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);
|
|
/* 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))
|
|
{
|
|
disarm_timer ();
|
|
return -geterrno_from_nt_status (status);
|
|
}
|
|
|
|
SetEvent (_arm_evt);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
timerfd_tracker::settime (int flags, const struct itimerspec *new_value,
|
|
struct itimerspec *old_value)
|
|
{
|
|
int ret = 0;
|
|
|
|
__try
|
|
{
|
|
if (!valid_timespec (new_value->it_value)
|
|
|| !valid_timespec (new_value->it_interval))
|
|
{
|
|
ret = -EINVAL;
|
|
__leave;
|
|
}
|
|
if (!enter_critical_section ())
|
|
{
|
|
ret = -EBADF;
|
|
__leave;
|
|
}
|
|
if (old_value)
|
|
gettime (old_value);
|
|
if (new_value->it_value.tv_sec == 0 && new_value->it_value.tv_nsec == 0)
|
|
ret = disarm_timer ();
|
|
else
|
|
ret = arm_timer (flags, new_value);
|
|
leave_critical_section ();
|
|
ret = 0;
|
|
}
|
|
__except (NO_ERROR)
|
|
{
|
|
ret = -EFAULT;
|
|
}
|
|
__endtry
|
|
return ret;
|
|
}
|