// -*- c-basic-offset: 4; related-file-name: "../../lib/task.cc" -*- #ifndef CLICK_TASK_HH #define CLICK_TASK_HH #include #include #if HAVE_MULTITHREAD # include # include #endif CLICK_DECLS #if CLICK_BSDMODULE # include CLICK_CXX_PROTECT # include # include CLICK_CXX_UNPROTECT # include #else #define GIANT_REQUIRED #endif #define PASS_GT(a, b) ((int)(a - b) > 0) typedef bool (*TaskCallback)(Task *, void *); typedef TaskCallback TaskHook CLICK_DEPRECATED; class RouterThread; class TaskList; class Master; struct TaskLink { #if !HAVE_TASK_HEAP TaskLink *_prev; TaskLink *_next; #endif #if HAVE_STRIDE_SCHED unsigned _pass; #endif TaskLink() { #if !HAVE_TASK_HEAP _prev = _next = 0; #endif #if HAVE_STRIDE_SCHED _pass = 0; #endif } }; class Task : private TaskLink { public: #if HAVE_STRIDE_SCHED enum { STRIDE1 = 1U<<16, MAX_STRIDE = 1U<<31 }; enum { MAX_TICKETS = 1<<15, DEFAULT_TICKETS = 1<<10 }; #endif #if HAVE_ADAPTIVE_SCHEDULER enum { MAX_UTILIZATION = 1000 }; #endif /** @brief Construct a task that calls @a f with @a user_data argument. * * @param f callback function * @param user_data argument for callback function * * Constructs a task that, when fired, calls @a f like so: * * @code * bool work_done = f(task, user_data); * @endcode * * where @a task is a pointer to this task. @a f should return true if * the task accomplished some meaningful work, and false if it did not. * For example, a task that polls a network driver for packets should * return true if it emits at least one packet, and false if no packets * were available. */ inline Task(TaskCallback f, void *user_data); /** @brief Construct a task that calls @a e ->@link Element::run_task(Task*) run_task()@endlink. * * @param e element to call * * Constructs a task that, when fired, calls the element @a e's @link * Element::run_task(Task *) run_task()@endlink method, passing this Task * as an argument. * * @sa Task(TaskCallback, void *) */ inline Task(Element *e); /** @brief Destroy a task. * * Unschedules the task if necessary. */ ~Task(); /** @brief Return the task's callback function. * * Returns null if the task was constructed with the Task(Element *) * constructor. */ inline TaskCallback callback() const { return _hook; } /** @brief Return the task callback function's user data. */ inline void *user_data() const { return _thunk; } /** @brief Return the task's owning element. */ inline Element *element() const { return _owner; } /** @brief Return true iff the task has been initialize()d. */ inline bool initialized() const; /** @brief Return the task's home thread ID. * * This is the @link RouterThread::thread_id() thread_id()@endlink of the * thread on which this Task would run if it were scheduled. This need * not equal the ID of the current thread(), since changes in * home_thread_id() aren't always implemented immediately (because of * locking issues). */ inline int home_thread_id() const; /** @brief Return the thread on which this task is currently scheduled, * or would be scheduled. * * Usually, task->thread()->@link RouterThread::thread_id() * thread_id()@endlink == task->home_thread_id(). They can differ, * however, if move_thread() was called but the task hasn't yet been moved * to the new thread. */ inline RouterThread *thread() const; /** @brief Return the router to which this task belongs. */ inline Router *router() const { return _owner->router(); } /** @brief Return the master where this task will be scheduled. */ Master *master() const; /** @brief Initialize the Task, and optionally schedule it. * @param owner specifies the element owning the Task * @param schedule if true, the Task will be scheduled immediately * * This function must be called on every Task before it is used. The * corresponding router's ThreadSched, if any, is used to determine the * task's initial thread assignment. The task initially has the default * number of tickets, and is scheduled iff @a schedule is true. * * An assertion will fail if a Task is initialized twice. * * Most elements call ScheduleInfo::initialize_task() to initialize a Task * object. The ScheduleInfo method additionally sets the task's * scheduling parameters, such as ticket count and thread preference, * based on a router's ScheduleInfo. ScheduleInfo::initialize_task() * calls Task::initialize(). */ void initialize(Element *owner, bool schedule); /** @brief Initialize the Task, and optionally schedule it. * @param router specifies the router owning the Task * @param schedule if true, the Task will be scheduled immediately * * This function is shorthand for @link Task::initialize(Element *, bool) * Task::initialize@endlink(@a router ->@link Router::root_element * root_element@endlink(), @a scheduled). However, it is better to * explicitly associate tasks with real elements. */ void initialize(Router *router, bool schedule); /** @brief Return true iff the task is currently scheduled to run. * * @note A scheduled task will usually run very soon, but not * always; due to locking issues, the effects of some reschedule() * requests may be delayed. Although a task unscheduled with * strong_unschedule() may appear scheduled(), it will not run * until strong_reschedule() is called. */ inline bool scheduled() const { return _status.is_scheduled; } /** @brief Unschedule the task. * * After unschedule() returns, the task will not run until it is * rescheduled with reschedule(). * * @sa reschedule, strong_unschedule */ inline void unschedule() { _status.is_scheduled = false; } /** @brief Reschedule the task. * * The task is rescheduled on its home thread. It will eventually run, * unless its home thread is quiescent or it has been * strong_unschedule()d. * * @sa unschedule, strong_reschedule */ inline void reschedule() { _status.is_scheduled = true; click_fence(); if (_pending_nextptr.x < 2) complete_schedule(0); } /** @brief Reschedule a task from the task's callback function. * * @warning Only call @a task.fast_reschedule() while @a task is being * fired, i.e., in its callback function. It is an error to call * @task.fast_reschedule() at other times -- the task may not actually be * rescheduled. * * Here's a typical, correct use of fast_reschedule(): * * @code * class MyElement : public Element { * ... Task _task; ... bool run_task(Task *t); ... * }; * bool MyElement::run_task(Task *) { * do_some_work(); * _task.fast_reschedule(); * return true; * } * @endcode * * This assumes, however, that run_task() is only called directly by the * driver. If you call run_task() from another context, _task may not * actually be scheduled. * * @code * void MyElement::run_timer(Timer *) { * run_task(); // XXX might not reschedule _task! * } * @endcode */ inline void fast_reschedule() { _status.is_scheduled = true; } /** @brief Unschedule the Task until strong_reschedule(). * * Like unschedule(), but in addition, future reschedule() calls * will not actually schedule the task. Only after strong_reschedule() * will the task run again. * @sa strong_reschedule, unschedule */ inline void strong_unschedule() { _status.is_scheduled = false; _status.is_strong_unscheduled = true; } /** @brief Reschedule the Task, undoing a prior strong_unschedule(). * * This function undoes any previous strong_unschedule() and * reschedules the task. * @sa reschedule, strong_unschedule */ inline void strong_reschedule() { _status.is_strong_unscheduled = false; reschedule(); } /** @brief Move the Task to a new home thread. * * The home thread ID is set to @a new_thread_id. The task, if it is * currently scheduled, is rescheduled on thread @a new_thread_id * (which generally takes some time to take effect). If @a new_thread_id * is less than zero or greater than the number of threads on the router, * the task is scheduled on a quiescent thread that never actually runs. */ void move_thread(int new_thread_id); #if HAVE_STRIDE_SCHED inline int tickets() const; inline void set_tickets(int n); inline void adjust_tickets(int delta); #endif inline bool fire(); #if HAVE_ADAPTIVE_SCHEDULER inline unsigned runs() const; inline unsigned work_done() const; inline unsigned utilization() const; inline void clear_runs(); #endif #if HAVE_MULTITHREAD inline int cycles() const; inline unsigned cycle_runs() const; inline void update_cycles(unsigned c); #endif /** @cond never */ inline TaskCallback hook() const CLICK_DEPRECATED; inline void *thunk() const CLICK_DEPRECATED; /** @endcond never */ private: #if HAVE_TASK_HEAP int _schedpos; #endif #if HAVE_STRIDE_SCHED unsigned _stride; int _tickets; #endif union Status { struct { int16_t home_thread_id; uint8_t is_scheduled; uint8_t is_strong_unscheduled; }; uint32_t status; } _status; TaskCallback _hook; void *_thunk; #if HAVE_ADAPTIVE_SCHEDULER unsigned _runs; unsigned _work_done; #endif #if HAVE_MULTITHREAD DirectEWMA _cycles; unsigned _cycle_runs; #endif RouterThread *_thread; Element *_owner; union Pending { Task *t; uintptr_t x; }; Pending _pending_nextptr; Task(const Task &x); Task &operator=(const Task &x); void cleanup(); #if CLICK_DEBUG_SCHEDULING public: #endif inline bool on_scheduled_list() const; inline bool on_pending_list() const { return _pending_nextptr.x != 0; } inline bool needs_cleanup() const; #if CLICK_DEBUG_SCHEDULING private: #endif void add_pending(bool always); void process_pending(RouterThread* thread); void complete_schedule(RouterThread* process_pending_thread); inline void remove_from_scheduled_list(); static bool error_hook(Task *task, void *user_data); friend class RouterThread; friend class Master; }; // need RouterThread's definition for inline functions CLICK_ENDDECLS #include CLICK_DECLS inline Task::Task(TaskCallback f, void *user_data) : #if HAVE_TASK_HEAP _schedpos(-1), #endif #if HAVE_STRIDE_SCHED _stride(0), _tickets(-1), #endif _hook(f), _thunk(user_data), #if HAVE_ADAPTIVE_SCHEDULER _runs(0), _work_done(0), #endif #if HAVE_MULTITHREAD _cycle_runs(0), #endif _thread(0), _owner(0) { _status.home_thread_id = -2; _status.is_scheduled = _status.is_strong_unscheduled = false; _pending_nextptr.x = 0; } inline Task::Task(Element* e) : #if HAVE_TASK_HEAP _schedpos(-1), #endif #if HAVE_STRIDE_SCHED _stride(0), _tickets(-1), #endif _hook(0), _thunk(e), #if HAVE_ADAPTIVE_SCHEDULER _runs(0), _work_done(0), #endif #if HAVE_MULTITHREAD _cycle_runs(0), #endif _thread(0), _owner(0) { _status.home_thread_id = -2; _status.is_scheduled = _status.is_strong_unscheduled = false; _pending_nextptr.x = 0; } inline bool Task::initialized() const { return _owner != 0; } inline bool Task::on_scheduled_list() const { #if HAVE_TASK_HEAP return _schedpos >= 0; #else return _prev != 0; #endif } inline bool Task::needs_cleanup() const { #if HAVE_TASK_HEAP int a; uintptr_t b; do { a = _schedpos; b = _pending_nextptr.x; click_fence(); } while (a != _schedpos || b != _pending_nextptr.x); return a >= 0 || b != 0; #else TaskLink* a; uintptr_t b; do { a = _prev; b = _pending_nextptr.x; click_fence(); } while (a != _prev || b != _pending_nextptr.x); return a != 0 || b != 0; #endif } /** @cond never */ /** @brief Return the task's callback function. * @deprecated Use callback() instead. */ inline TaskCallback Task::hook() const { return _hook; } /** @brief Return the task's callback data. * @deprecated Use user_data() instead. */ inline void * Task::thunk() const { return _thunk; } /** @endcond never */ inline int Task::home_thread_id() const { return _status.home_thread_id; } inline RouterThread * Task::thread() const { return _thread; } inline void Task::remove_from_scheduled_list() { if (on_scheduled_list()) { #if HAVE_TASK_HEAP Task *back = _thread->_task_heap.back().t; _thread->_task_heap.pop_back(); if (_thread->_task_heap.size() > 0) _thread->task_reheapify_from(_schedpos, back); click_fence(); _schedpos = -1; #else _next->_prev = _prev; _prev->_next = _next; _next = 0; click_fence(); _prev = 0; #endif } } #if HAVE_STRIDE_SCHED /** @brief Return the task's number of tickets. * * Tasks with larger numbers of tickets are scheduled more often. Tasks are * initialized with tickets() == DEFAULT_TICKETS. * * @sa set_tickets, adjust_tickets */ inline int Task::tickets() const { return _tickets; } /** @brief Set the task's ticket count. * @param n the ticket count * * The ticket count @a n is pinned to the range [1, MAX_TICKETS]. * * @sa tickets, adjust_tickets */ inline void Task::set_tickets(int n) { if (n > MAX_TICKETS) n = MAX_TICKETS; else if (n < 1) n = 1; _tickets = n; _stride = STRIDE1 / n; assert(_stride < MAX_STRIDE); } /** @brief Add @a delta to the Task's ticket count. * @param delta adjustment to the ticket count * * The ticket count cannot be adjusted below 1 or above MAX_TICKETS. * * @sa set_tickets */ inline void Task::adjust_tickets(int delta) { set_tickets(_tickets + delta); } #endif /* HAVE_STRIDE_SCHED */ /** @brief Fire the task by calling its callback function. * * This function is generally called by the RouterThread implementation; there * should be no need to call it yourself. */ inline bool Task::fire() { #if CLICK_STATS >= 2 click_cycles_t start_cycles = click_get_cycles(), start_child_cycles = _owner->_child_cycles; #endif #if HAVE_MULTITHREAD _cycle_runs++; #endif bool work_done; if (!_hook) work_done = ((Element*)_thunk)->run_task(this); else work_done = _hook(this, _thunk); #if HAVE_ADAPTIVE_SCHEDULER ++_runs; _work_done += work_done; #endif #if CLICK_STATS >= 2 click_cycles_t all_delta = click_get_cycles() - start_cycles, own_delta = all_delta - (_owner->_child_cycles - start_child_cycles); _owner->_task_calls += 1; _owner->_task_own_cycles += own_delta; #endif return work_done; } #if HAVE_ADAPTIVE_SCHEDULER inline unsigned Task::runs() const { return _runs; } inline unsigned Task::work_done() const { return _work_done; } inline unsigned Task::utilization() const { return (_runs ? (MAX_UTILIZATION * _work_done) / _runs : 0); } inline void Task::clear_runs() { _runs = _work_done = 0; } #endif #if HAVE_MULTITHREAD inline int Task::cycles() const { return _cycles.unscaled_average(); } inline unsigned Task::cycle_runs() const { return _cycle_runs; } inline void Task::update_cycles(unsigned c) { _cycles.update(c); _cycle_runs = 0; } #endif CLICK_ENDDECLS #endif