/*
* \brief Semaphore implementation with timeout facility
* \author Stefan Kalkowski
* \date 2010-03-05
*
* This semaphore implementation allows to block on a semaphore for a
* given time instead of blocking indefinetely.
*
* For the timeout functionality the alarm framework is used.
*/
/*
* Copyright (C) 2010-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _INCLUDE__OS__TIMED_SEMAPHORE_H_
#define _INCLUDE__OS__TIMED_SEMAPHORE_H_
#include
#include
#include
#include
namespace Genode {
class Timeout_thread;
class Timed_semaphore;
/**
* Exception types
*/
class Timeout_exception;
class Nonblocking_exception;
}
/**
* Alarm thread, which counts jiffies and triggers timeout events.
*/
class Genode::Timeout_thread : public Thread_deprecated<2048*sizeof(long)>,
private Alarm_scheduler
{
private:
enum { JIFFIES_STEP_MS = 10 };
static Genode::Env *_env;
Timer::Connection _timer { *_env };
Signal_context _context { };
Signal_receiver _receiver { };
void entry(void);
public:
using Alarm_scheduler::schedule_absolute;
using Alarm_scheduler::discard;
Timeout_thread() : Thread_deprecated("alarm-timer")
{
_timer.sigh(_receiver.manage(&_context));
_timer.trigger_periodic(JIFFIES_STEP_MS*1000);
start();
}
Genode::Alarm::Time time(void) { return _timer.elapsed_ms(); }
/*
* Returns the singleton timeout-thread used for all timeouts.
*/
static Timeout_thread *alarm_timer();
static void env(Genode::Env &env) { _env = &env; }
};
class Genode::Timeout_exception : public Exception { };
class Genode::Nonblocking_exception : public Exception { };
/**
* Semaphore with timeout on down operation.
*/
class Genode::Timed_semaphore : public Semaphore
{
private:
typedef Semaphore::Element Element;
/**
* Aborts blocking on the semaphore, raised when a timeout occured.
*
* \param element the waiting-queue element associated with a timeout.
* \return true if a thread was aborted/woken up
*/
bool _abort(Element &element)
{
Lock::Guard lock_guard(Semaphore::_meta_lock);
/* potentially, the queue is empty */
if (++Semaphore::_cnt <= 0) {
/*
* Iterate through the queue and find the thread,
* with the corresponding timeout.
*/
Element *first = Semaphore::_queue.dequeue();
Element *e = first;
while (true) {
/*
* Wakeup the thread.
*/
if (&element == e) {
e->wake_up();
return true;
}
/*
* Noninvolved threads are enqueued again.
*/
Semaphore::_queue.enqueue(e);
e = Semaphore::_queue.dequeue();
/*
* Maybe, the alarm was triggered just after the corresponding
* thread was already dequeued, that's why we have to track
* whether we processed the whole queue.
*/
if (e == first)
break;
}
}
/* The right element was not found, so decrease counter again */
--Semaphore::_cnt;
return false;
}
/**
* Represents a timeout associated with the blocking
* operation on a semaphore.
*/
class Timeout : public Alarm
{
private:
Timed_semaphore &_sem; /* semaphore we block on */
Element &_element; /* queue element timeout belongs to */
bool _triggered; /* timeout expired */
Time _start { };
public:
Timeout(Time duration, Timed_semaphore *s, Element *e)
: _sem(*s), _element(*e), _triggered(false)
{
Timeout_thread *tt = Timeout_thread::alarm_timer();
_start = tt->time();
tt->schedule_absolute(this, _start + duration);
}
void discard(void) { Timeout_thread::alarm_timer()->discard(this); }
bool triggered(void) { return _triggered; }
Time start() { return _start; }
protected:
bool on_alarm(unsigned) override
{
_triggered = _sem._abort(_element);
return false;
}
};
public:
/**
* Constructor
*
* \param n initial counter value of the semphore
*/
Timed_semaphore(int n = 0) : Semaphore(n) { }
/**
* Decrements semaphore and blocks when it's already zero.
*
* \param t after t milliseconds of blocking a Timeout_exception is thrown.
* if t is zero do not block, instead raise an
* Nonblocking_exception.
* \return milliseconds the caller was blocked
*/
Alarm::Time down(Alarm::Time t)
{
Semaphore::_meta_lock.lock();
if (--Semaphore::_cnt < 0) {
/* If t==0 we shall not block */
if (t == 0) {
++_cnt;
Semaphore::_meta_lock.unlock();
throw Genode::Nonblocking_exception();
}
/*
* Create semaphore queue element representing the thread
* in the wait queue.
*/
Element queue_element;
Semaphore::_queue.enqueue(&queue_element);
Semaphore::_meta_lock.unlock();
/* Create the timeout */
Timeout to(t, this, &queue_element);
/*
* The thread is going to block on a local lock now,
* waiting for getting waked from another thread
* calling 'up()'
* */
queue_element.block();
/* Deactivate timeout */
to.discard();
/*
* When we were only woken up, because of a timeout,
* throw an exception.
*/
if (to.triggered())
throw Genode::Timeout_exception();
/* return blocking time */
return Timeout_thread::alarm_timer()->time() - to.start();
} else {
Semaphore::_meta_lock.unlock();
}
return 0;
}
/********************************
** Base class implementations **
********************************/
void down() { Semaphore::down(); }
void up() { Semaphore::up(); }
};
#endif /* _INCLUDE__OS__TIMED_SEMAPHORE_H_ */