/* * Copyright (c) 2013, 2016, 2017, 2021 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * timer.cpp * Clock and timer facility. */ #include #include #include #include #include #include #include namespace Sortix { // The caller should protect the timer using a lock if multiple threads can // access it. Timer::Timer() { value = { timespec_nul(), timespec_nul() }; clock = NULL; prev_timer = NULL; next_timer = NULL; next_interrupt_timer = NULL; callback = NULL; user = NULL; num_firings_scheduled = 0; num_overrun_events = 0; flags = 0; } Timer::~Timer() { // TODO: Is this the right thing? // The user of this object should cancel all pending triggers before calling // the destructor. We could try to cancel the timer, but if we fail the race // the handler function will be called, and that function may access the // timer object. We'd then have to wait for the function to finish and then // continue the destuction. This is a bit complex and fragile, so we'll just // make it the rule that all outstanding requests must be cancelled before // our destruction. The caller should know better than we how to cancel. assert(!(flags & TIMER_ACTIVE)); } void Timer::Attach(Clock* the_clock) { assert(!clock); assert(the_clock); clock = the_clock; } void Timer::Detach() { assert(!(flags & TIMER_ACTIVE)); assert(clock); clock = NULL; } void Timer::Cancel() { if ( clock ) clock->Cancel(this); } bool Timer::TryCancel() { if ( clock ) return clock->TryCancel(this); return true; } void Timer::GetInternal(struct itimerspec* current) { if ( !(this->flags & TIMER_ACTIVE ) ) current->it_value = timespec_nul(), current->it_interval = timespec_nul(); else if ( flags & TIMER_ABSOLUTE ) current->it_value = timespec_sub(value.it_value, clock->current_time), current->it_interval = value.it_interval; else *current = value; } void Timer::Get(struct itimerspec* current) { assert(clock); clock->LockClock(); GetInternal(current); clock->UnlockClock(); } void Timer::Set(struct itimerspec* new_value, struct itimerspec* old_value, int new_flags, void (*new_callback)(Clock*, Timer*, void*), void* new_user) { assert(clock); assert(!(flags & TIMER_FUNC_MAY_DEALLOCATE_TIMER) || timespec_le(new_value->it_interval, timespec_nul())); clock->LockClock(); // Dequeue this timer if it is already armed. if ( flags & TIMER_ACTIVE ) { // TODO: How does this interplay with concurrently running timer // handlers? Maybe the timer should be cancelled instead? clock->Unlink(this); } // Let the caller know how much time was left on the timer. if ( old_value ) GetInternal(old_value); // Arm the timer if a value was specified. if ( !(new_flags & TIMER_DISARM) ) { value = *new_value; flags = new_flags; callback = new_callback; user = new_user; if ( !(flags & TIMER_ABSOLUTE) && value.it_value.tv_sec < 0 ) value.it_value = timespec_nul(); clock->Register(this); } else { value.it_value = timespec_nul(); value.it_interval = timespec_nul(); } clock->UnlockClock(); } } // namespace Sortix