Support deallocating kernel timers in timer handlers.
This commit is contained in:
parent
7a8a71674e
commit
a53dd5d29d
4 changed files with 58 additions and 4 deletions
|
@ -235,6 +235,17 @@ void Clock::Cancel(Timer* timer)
|
||||||
UnlockClock();
|
UnlockClock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Clock::TryCancel(Timer* timer)
|
||||||
|
{
|
||||||
|
LockClock();
|
||||||
|
bool active = timer->flags & TIMER_ACTIVE;
|
||||||
|
if ( active )
|
||||||
|
Unlink(timer);
|
||||||
|
UnlockClock();
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: We need some method for threads to sleep for real but still be
|
// TODO: We need some method for threads to sleep for real but still be
|
||||||
// interrupted by signals.
|
// interrupted by signals.
|
||||||
struct timespec Clock::SleepDelay(struct timespec duration)
|
struct timespec Clock::SleepDelay(struct timespec duration)
|
||||||
|
@ -350,10 +361,16 @@ static void Clock__FireTimer(void* timer_ptr)
|
||||||
timer->clock->LockClock();
|
timer->clock->LockClock();
|
||||||
timer->num_overrun_events = timer->num_firings_scheduled;
|
timer->num_overrun_events = timer->num_firings_scheduled;
|
||||||
timer->num_firings_scheduled = 0;
|
timer->num_firings_scheduled = 0;
|
||||||
|
bool may_deallocate = timer->flags & TIMER_FUNC_MAY_DEALLOCATE_TIMER;
|
||||||
timer->clock->UnlockClock();
|
timer->clock->UnlockClock();
|
||||||
|
|
||||||
Clock__DoFireTimer(timer);
|
Clock__DoFireTimer(timer);
|
||||||
|
|
||||||
|
// The handler may have deallocated the storage for the timer, don't touch
|
||||||
|
// it again.
|
||||||
|
if ( may_deallocate )
|
||||||
|
return;
|
||||||
|
|
||||||
// If additional events happened during the time of the event handler, we'll
|
// If additional events happened during the time of the event handler, we'll
|
||||||
// have to handle them because the firing bit is set. We'll schedule another
|
// have to handle them because the firing bit is set. We'll schedule another
|
||||||
// worker thread job and resume there, so this worker thread can continue to
|
// worker thread job and resume there, so this worker thread can continue to
|
||||||
|
@ -376,6 +393,7 @@ static void Clock__FireTimer_InterruptWorker(void* timer_ptr, void*, size_t)
|
||||||
void Clock::FireTimer(Timer* timer)
|
void Clock::FireTimer(Timer* timer)
|
||||||
{
|
{
|
||||||
timer->flags &= ~TIMER_ACTIVE;
|
timer->flags &= ~TIMER_ACTIVE;
|
||||||
|
bool may_deallocate = timer->flags & TIMER_FUNC_MAY_DEALLOCATE_TIMER;
|
||||||
|
|
||||||
// If the CPU is currently interrupted, we call the timer callback directly
|
// If the CPU is currently interrupted, we call the timer callback directly
|
||||||
// only if it is known to work when the interrupts are disabled on this CPU.
|
// only if it is known to work when the interrupts are disabled on this CPU.
|
||||||
|
@ -389,7 +407,8 @@ void Clock::FireTimer(Timer* timer)
|
||||||
timer->num_firings_scheduled++;
|
timer->num_firings_scheduled++;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
timer->flags |= TIMER_FIRING;
|
if ( !may_deallocate )
|
||||||
|
timer->flags |= TIMER_FIRING;
|
||||||
Interrupt::ScheduleWork(Clock__FireTimer_InterruptWorker, timer, NULL, 0);
|
Interrupt::ScheduleWork(Clock__FireTimer_InterruptWorker, timer, NULL, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -405,13 +424,15 @@ void Clock::FireTimer(Timer* timer)
|
||||||
timer->num_firings_scheduled++;
|
timer->num_firings_scheduled++;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
timer->flags |= TIMER_FIRING;
|
if ( !may_deallocate )
|
||||||
|
timer->flags |= TIMER_FIRING;
|
||||||
Worker::Schedule(Clock__FireTimer, timer);
|
Worker::Schedule(Clock__FireTimer, timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rearm the timer only if it is periodic.
|
// Rearm the timer only if it is periodic.
|
||||||
if ( timespec_le(timer->value.it_interval, timespec_nul()) )
|
if ( may_deallocate ||
|
||||||
|
timespec_le(timer->value.it_interval, timespec_nul()) )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// TODO: If the period is too short (such a single nanosecond) on a delay
|
// TODO: If the period is too short (such a single nanosecond) on a delay
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2013 Jonas 'Sortie' Termansen.
|
* Copyright (c) 2013, 2016 Jonas 'Sortie' Termansen.
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
@ -55,6 +55,7 @@ public:
|
||||||
void Register(Timer* timer);
|
void Register(Timer* timer);
|
||||||
void Unlink(Timer* timer);
|
void Unlink(Timer* timer);
|
||||||
void Cancel(Timer* timer);
|
void Cancel(Timer* timer);
|
||||||
|
bool TryCancel(Timer* timer);
|
||||||
void LockClock();
|
void LockClock();
|
||||||
void UnlockClock();
|
void UnlockClock();
|
||||||
struct timespec SleepDelay(struct timespec duration);
|
struct timespec SleepDelay(struct timespec duration);
|
||||||
|
|
|
@ -35,6 +35,28 @@ static const int TIMER_ACTIVE = 1 << 1;
|
||||||
static const int TIMER_FIRING = 1 << 2;
|
static const int TIMER_FIRING = 1 << 2;
|
||||||
static const int TIMER_FUNC_INTERRUPT_HANDLER = 1 << 3;
|
static const int TIMER_FUNC_INTERRUPT_HANDLER = 1 << 3;
|
||||||
static const int TIMER_FUNC_ADVANCE_THREAD = 1 << 4;
|
static const int TIMER_FUNC_ADVANCE_THREAD = 1 << 4;
|
||||||
|
// The timer callback may deallocate the timer itself. The timer data structure
|
||||||
|
// will not be touched by the timer clock after running the callback and the
|
||||||
|
// clock and timer implementation will not have any issues with deallocating it.
|
||||||
|
// This feature cannot be combined with periodic timers. The Cancel method may
|
||||||
|
// not be called, as it ensures consistency (the timer is cancelled if pending,
|
||||||
|
// and if it's firing, then waiting for it to complete). Instead, use TryCancel
|
||||||
|
// which will return false if the timer wasn't pending. If the timer has been
|
||||||
|
// armed, and the handler has not yet run, that means the handler is scheduled
|
||||||
|
// to run and it's not safe to deallocate until the handler runs. It is not
|
||||||
|
// possible call the Set method on an armed timer, unless the timer has been
|
||||||
|
// successfully TryCancelled, or the handler has run. It's the user's
|
||||||
|
// responsibility to ensure deallocation of the timer only happens if no other
|
||||||
|
// threads will use the timer data structure. I.e. if some code wants to
|
||||||
|
// TryCancel a timer, it must synchronize with the timer handler, so the timer
|
||||||
|
// handler doesn't deallocate the timer and then the other thread calls
|
||||||
|
// TryCancel on a freed pointer.
|
||||||
|
// The object containing the timer could contain a mutex and a bool of whether
|
||||||
|
// the timer is armed and the handler has not run. If there's a need to destroy
|
||||||
|
// the object, attempt to TryCancel and timer and do so if it succeeds,
|
||||||
|
// otherwise delay the destruction until the timer handler, which also grabs the
|
||||||
|
// mutex and checks whether object destruction is supposed to happen.
|
||||||
|
static const int TIMER_FUNC_MAY_DEALLOCATE_TIMER = 1 << 5;
|
||||||
|
|
||||||
class Timer
|
class Timer
|
||||||
{
|
{
|
||||||
|
@ -62,6 +84,7 @@ public:
|
||||||
void Detach();
|
void Detach();
|
||||||
bool IsAttached() const { return clock; }
|
bool IsAttached() const { return clock; }
|
||||||
void Cancel();
|
void Cancel();
|
||||||
|
bool TryCancel();
|
||||||
Clock* GetClock() const { return clock; }
|
Clock* GetClock() const { return clock; }
|
||||||
void Get(struct itimerspec* current);
|
void Get(struct itimerspec* current);
|
||||||
void Set(struct itimerspec* value, struct itimerspec* ovalue, int flags,
|
void Set(struct itimerspec* value, struct itimerspec* ovalue, int flags,
|
||||||
|
|
|
@ -77,6 +77,13 @@ void Timer::Cancel()
|
||||||
clock->Cancel(this);
|
clock->Cancel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Timer::TryCancel()
|
||||||
|
{
|
||||||
|
if ( clock )
|
||||||
|
return clock->TryCancel(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Timer::GetInternal(struct itimerspec* current)
|
void Timer::GetInternal(struct itimerspec* current)
|
||||||
{
|
{
|
||||||
if ( !(this->flags & TIMER_ACTIVE ) )
|
if ( !(this->flags & TIMER_ACTIVE ) )
|
||||||
|
@ -103,6 +110,8 @@ void Timer::Set(struct itimerspec* new_value, struct itimerspec* old_value,
|
||||||
void* new_user)
|
void* new_user)
|
||||||
{
|
{
|
||||||
assert(clock);
|
assert(clock);
|
||||||
|
assert(!(flags & TIMER_FUNC_MAY_DEALLOCATE_TIMER) ||
|
||||||
|
timespec_le(new_value->it_interval, timespec_nul()));
|
||||||
|
|
||||||
clock->LockClock();
|
clock->LockClock();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue