// -*- c-basic-offset: 4; related-file-name: "../include/click/task.hh" -*-
/*
 * task.{cc,hh} -- a linked list of schedulable entities
 * Eddie Kohler, Benjie Chen
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 * Copyright (c) 2002 International Computer Science Institute
 * Copyright (c) 2004-2005 Regents of the University of California
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include <click/task.hh>
#include <click/router.hh>
#include <click/routerthread.hh>
#include <click/master.hh>
CLICK_DECLS

/** @class Task
 * @brief Represents a frequently-scheduled computational task.
 *
 * Click schedules a router's CPU or CPUs with one or more <em>task
 * queues</em>.  These queues are simply lists of @e tasks, which represent
 * functions that would like unconditional access to the CPU.  Tasks are
 * generally associated with elements.  When scheduled, most tasks call some
 * element's @link Element::run_task() run_task()@endlink method.
 *
 * Click tasks are represented by Task objects.  An element that would like
 * special access to a router's CPU should include and initialize a Task
 * instance variable.
 *
 * Tasks are called very frequently, up to tens of thousands of times per
 * second.  Unlike Timer objects and selections, tasks are called
 * unconditionally.  Elements generally use Tasks for frequent tasks, and
 * implement their own algorithms for scheduling and unscheduling the tasks
 * when there's work to be done.  For infrequent events, it is far more
 * efficient to use Timer objects.
 *
 * Since Click tasks are cooperatively scheduled, executing a task should not
 * take a long time.  Very long tasks can inappropriately delay timers and
 * other periodic events.  We may address this problem in a future release,
 * but for now, keep tasks short.
 */

// - Changes to _thread are protected by _thread->lock.
// - Changes to _home_thread_id are protected by
//   _router->master()->task_lock.
// - If _pending is nonzero, then _pending_next is nonnull.
// - Either _home_thread_id == _thread->thread_id(), or
//   _thread->thread_id() == -1.

bool
Task::error_hook(Task *, void *)
{
    assert(0);
    return false;
}

void
Task::make_list()
{
    _hook = error_hook;
    _pending_next = this;
}

Task::~Task()
{
#if HAVE_TASK_HEAP
    if (scheduled() || _pending)
	cleanup();
#else
    if ((scheduled() || _pending) && _thread != this)
	cleanup();
#endif
}

/** @brief Return the master where this task will be scheduled.
 */
Master *
Task::master() const
{
    assert(_thread);
    return _thread->master();
}

/** @brief Initialize the Task, and optionally schedule it.
 * @param router the router containing this Task
 * @param schedule if true, the Task will be scheduled immediately
 *
 * This function must be called on every Task before it is used.  The @a
 * 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.
 */
void
Task::initialize(Router *router, bool schedule)
{
    assert(!initialized() && !scheduled());

    _router = router;
    
    _home_thread_id = router->initial_home_thread_id(this, schedule);
    if (_home_thread_id == ThreadSched::THREAD_UNKNOWN)
	_home_thread_id = 0;
    // Master::thread() returns the quiescent thread if its argument is out of
    // range
    _thread = router->master()->thread(_home_thread_id);
    
#ifdef HAVE_STRIDE_SCHED
    set_tickets(DEFAULT_TICKETS);
#endif

    if (schedule)
	add_pending(RESCHEDULE);
}

/** @brief Initialize the Task, and optionally schedule it.
 * @param e specifies the router containing the Task
 * @param schedule if true, the Task will be scheduled immediately
 *
 * This method is shorthand for @link Task::initialize(Router *, bool) initialize@endlink(@a e ->@link Element::router router@endlink(), bool).
 */
void
Task::initialize(Element *e, bool schedule)
{
    initialize(e->router(), schedule);
}

void
Task::cleanup()
{
    if (initialized()) {
	strong_unschedule();

	if (_pending) {
	    Master *m = _router->master();
	    SpinlockIRQ::flags_t flags = m->_task_lock.acquire();
	    Task *prev = &m->_task_list;
	    for (Task *t = prev->_pending_next; t != &m->_task_list; prev = t, t = t->_pending_next)
		if (t == this) {
		    prev->_pending_next = t->_pending_next;
		    break;
		}
	    _pending = 0;
	    _pending_next = 0;
	    m->_task_lock.release(flags);
	}
	
	_router = 0;
	_thread = 0;
    }
}

inline void
Task::lock_tasks()
{
    while (1) {
	RouterThread *t = _thread;
	t->lock_tasks();
	if (t == _thread)
	    return;
	t->unlock_tasks();
    }
}

inline bool
Task::attempt_lock_tasks()
{
    RouterThread *t = _thread;
    if (t->attempt_lock_tasks()) {
	if (t == _thread)
	    return true;
	t->unlock_tasks();
    }
    return false;
}

void
Task::add_pending(int p)
{
    Master *m = _router->master();
    SpinlockIRQ::flags_t flags = m->_task_lock.acquire();
    if (_router->_running >= Router::RUNNING_PREPARING) {
	_pending |= p;
	if (!_pending_next && _pending) {
	    _pending_next = m->_task_list._pending_next;
	    m->_task_list._pending_next = this;
	}
	if (_pending)
	    _thread->add_pending();
    }
    m->_task_lock.release(flags);
}

/** @brief Unschedules the task.
 *
 * When unschedule() returns, the task will not be scheduled on any thread.
 * @sa reschedule, strong_unschedule, fast_unschedule
 */
void
Task::unschedule()
{
    // Thanksgiving 2001: unschedule() will always unschedule the task. This
    // seems more reliable, since some people depend on unschedule() ensuring
    // that the task is not scheduled any more, no way, no how. Possible
    // problem: calling unschedule() from run_task() will hang!
    // 2005: It shouldn't hang.
#if CLICK_LINUXMODULE
    assert(!in_interrupt());
#endif
#if CLICK_BSDMODULE
    assert(!intr_nesting_level);
    SPLCHECK;
#endif
    if (_thread) {
	lock_tasks();
	fast_unschedule();
	_pending &= ~RESCHEDULE;
	_thread->unlock_tasks();
    }
}

#if HAVE_TASK_HEAP
void
Task::fast_reschedule()
{
    // should not be scheduled at this point
    assert(_thread);
#if CLICK_LINUXMODULE
    // tasks never run at interrupt time
    assert(!in_interrupt());
#endif
#if CLICK_BSDMODULE
    // assert(!intr_nesting_level); it happens all the time from fromdevice!
    SPLCHECK;
#endif

    if (!scheduled()) {
	// increase pass
	_pass += _stride;

	if (_thread->_task_heap_hole) {
	    _schedpos = 0;
	    _thread->_task_heap_hole = 0;
	} else {
	    _schedpos = _thread->_task_heap.size();
	    _thread->_task_heap.push_back(0);
	}
	_thread->task_reheapify_from(_schedpos, this);
    }
}
#endif

void
Task::true_reschedule()
{
    assert(_thread);
    bool done = false;
#if CLICK_LINUXMODULE
    if (in_interrupt())
	goto skip_lock;
#endif
#if CLICK_BSDMODULE
    SPLCHECK;
#endif
    if (attempt_lock_tasks()) {
	if (_router->_running >= Router::RUNNING_BACKGROUND) {
	    if (!scheduled()) {
		fast_schedule();
		_thread->wake();
	    }
	    done = true;
	}
	_thread->unlock_tasks();
    }
#if CLICK_LINUXMODULE
  skip_lock:
#endif
    if (!done)
	add_pending(RESCHEDULE);
}

/** @brief Unschedules the Task and moves it to a quiescent thread.
 *
 * When strong_unschedule() returns, the task will not be scheduled on any
 * thread.  Furthermore, the task has been moved to a temporary "dead" thread.
 * Future reschedule() calls will not schedule the task, since the dead thread
 * never runs; future move_thread() calls change the home thread ID,
 * but leave the thread on the dead thread.  Only strong_reschedule() can make
 * the task run again.
 * @sa strong_reschedule, unschedule
 */
void
Task::strong_unschedule()
{
#if CLICK_LINUXMODULE
    assert(!in_interrupt());
#endif
#if CLICK_BSDMODULE
    assert(!intr_nesting_level);
    SPLCHECK;
#endif
    // unschedule() and move to a quiescent thread, so that subsequent
    // reschedule()s won't have any effect
    if (_thread) {
	lock_tasks();
	fast_unschedule();
	RouterThread *old_thread = _thread;
	_pending &= ~(RESCHEDULE | CHANGE_THREAD);
	_thread = _router->master()->thread(RouterThread::THREAD_STRONG_UNSCHEDULE);
	old_thread->unlock_tasks();
    }
}

/** @brief Reschedules the Task, moving it from the "dead" thread to its home
 * thread if appropriate.
 *
 * This function undoes any previous strong_unschedule().  If the task is on
 * the "dead" thread, then it is moved to its home thread.  The task is also
 * rescheduled.  Due to locking issues, the task may not be scheduled right
 * away -- scheduled() may not immediately return true.
 *
 * @sa reschedule, strong_unschedule
 */
void
Task::strong_reschedule()
{
#if CLICK_LINUXMODULE
    assert(!in_interrupt());
#endif
#if CLICK_BSDMODULE
    assert(!intr_nesting_level);
    SPLCHECK;
#endif
    assert(_thread);
    lock_tasks();
    RouterThread *old_thread = _thread;
    if (old_thread->thread_id() == RouterThread::THREAD_STRONG_UNSCHEDULE) {
	fast_unschedule();
	_thread = _router->master()->thread(_home_thread_id);
	add_pending(RESCHEDULE);
    } else if (old_thread->thread_id() == _home_thread_id)
	fast_reschedule();
    old_thread->unlock_tasks();
}

/** @brief Move the Task to a new home thread.
 *
 * The home thread ID is set to @a thread_id.  The task, if it is currently
 * scheduled, is rescheduled on thread @a thread_id; this generally takes some
 * time to take effect.  @a thread_id can be less than zero, in which case the
 * thread is scheduled on a quiescent thread: it will never be run.
 */
void
Task::move_thread(int thread_id)
{
#if CLICK_LINUXMODULE
    assert(!in_interrupt());
#endif
#if CLICK_BSDMODULE
    assert(!intr_nesting_level);
    SPLCHECK;
#endif
    if (thread_id < RouterThread::THREAD_QUIESCENT)
	thread_id = RouterThread::THREAD_QUIESCENT;
    _home_thread_id = thread_id;

    if (attempt_lock_tasks()) {
	RouterThread *old_thread = _thread;
	if (old_thread->thread_id() != _home_thread_id
	    && old_thread->thread_id() != RouterThread::THREAD_STRONG_UNSCHEDULE) {
	    if (scheduled()) {
		fast_unschedule();
		_pending |= RESCHEDULE;
	    }
	    _thread = _router->master()->thread(_home_thread_id);
	    old_thread->unlock_tasks();
	    add_pending(0);
	} else
	    old_thread->unlock_tasks();
    } else
	add_pending(CHANGE_THREAD);
}

void
Task::process_pending(RouterThread *thread)
{
    // must be called with thread->lock held

#if CLICK_BSDMODULE
    int s = splimp();
#endif

    if (_thread == thread) {
	if (_pending & CHANGE_THREAD) {
	    // see also move_thread() above
	    _pending &= ~CHANGE_THREAD;
	    if (_thread->thread_id() != _home_thread_id
		&& _thread->thread_id() != RouterThread::THREAD_STRONG_UNSCHEDULE) {
		if (scheduled()) {
		    fast_unschedule();
		    _pending |= RESCHEDULE;
		}
		_thread = _router->master()->thread(_home_thread_id);
	    }
	} else if (_pending & RESCHEDULE) {
	    _pending &= ~RESCHEDULE;
	    if (!scheduled())
		fast_schedule();
	}
    }

    if (_pending)
	add_pending(0);

#if CLICK_BSDMODULE
    splx(s);
#endif
}

CLICK_ENDDECLS


syntax highlighted by Code2HTML, v. 0.9.1