/*
    Copyright 2005-2014 Intel Corporation.  All Rights Reserved.

    This file is part of Threading Building Blocks. Threading Building Blocks is free software;
    you can redistribute it and/or modify it under the terms of the GNU General Public License
    version 2  as  published  by  the  Free Software Foundation.  Threading Building Blocks is
    distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    See  the GNU General Public License for more details.   You should have received a copy of
    the  GNU General Public License along with Threading Building Blocks; if not, write to the
    Free Software Foundation, Inc.,  51 Franklin St,  Fifth Floor,  Boston,  MA 02110-1301 USA

    As a special exception,  you may use this file  as part of a free software library without
    restriction.  Specifically,  if other files instantiate templates  or use macros or inline
    functions from this file, or you compile this file and link it with other files to produce
    an executable,  this file does not by itself cause the resulting executable to be covered
    by the GNU General Public License. This exception does not however invalidate any other
    reasons why the executable file might be covered by the GNU General Public License.
*/

#ifndef _TBB_observer_proxy_H
#define _TBB_observer_proxy_H

#if __TBB_SCHEDULER_OBSERVER

#include "scheduler_common.h" // to include task.h
#include "tbb/task_scheduler_observer.h"
#include "tbb/spin_rw_mutex.h"
#include "tbb/aligned_space.h"

namespace tbb {
namespace internal {

class observer_list {
    friend class arena;

    // Mutex is wrapped with aligned_space to shut up warnings when its destructor
    // is called while threads are still using it.
    typedef aligned_space<spin_rw_mutex>  my_mutex_type;

    //! Pointer to the head of this list.
    observer_proxy* my_head;

    //! Pointer to the tail of this list.
    observer_proxy* my_tail;

    //! Mutex protecting this list.
    my_mutex_type my_mutex;

    //! Back-pointer to the arena this list belongs to.
    arena* my_arena;

    //! Decrement refcount of the proxy p if there are other outstanding references.
    /** In case of success sets p to NULL. Must be invoked from under the list lock. **/
    inline static void remove_ref_fast( observer_proxy*& p );

    //! Implements notify_entry_observers functionality.
    void do_notify_entry_observers( observer_proxy*& last, bool worker );

    //! Implements notify_exit_observers functionality.
    void do_notify_exit_observers( observer_proxy* last, bool worker );

public:
    observer_list () : my_head(NULL), my_tail(NULL) {}

    //! Removes and destroys all observer proxies from the list.
    /** Cannot be used concurrently with other methods. **/
    void clear ();

    //! Add observer proxy to the tail of the list.
    void insert ( observer_proxy* p );

    //! Remove observer proxy from the list.
    void remove ( observer_proxy* p );

    //! Decrement refcount of the proxy and destroy it if necessary.
    /** When refcount reaches zero removes the proxy from the list and destructs it. **/
    void remove_ref( observer_proxy* p );

    //! Type of the scoped lock for the reader-writer mutex associated with the list.
    typedef spin_rw_mutex::scoped_lock scoped_lock;

    //! Accessor to the reader-writer mutex associated with the list.
    spin_rw_mutex& mutex () { return my_mutex.begin()[0]; }

    bool empty () const { return my_head == NULL; }

    //! Call entry notifications on observers added after last was notified.
    /** Updates last to become the last notified observer proxy (in the global list)
        or leaves it to be NULL. The proxy has its refcount incremented. **/
    inline void notify_entry_observers( observer_proxy*& last, bool worker );

    //! Call exit notifications on last and observers added before it.
    inline void notify_exit_observers( observer_proxy*& last, bool worker );

    //! Call may_sleep callbacks to ask for permission for a worker thread to leave market
    bool ask_permission_to_leave();
}; // class observer_list

//! Wrapper for an observer object
/** To maintain shared lists of observers the scheduler first wraps each observer
    object into a proxy so that a list item remained valid even after the corresponding
    proxy object is destroyed by the user code. **/
class observer_proxy {
    friend class task_scheduler_observer_v3;
    friend class observer_list;
    //! Reference count used for garbage collection.
    /** 1 for reference from my task_scheduler_observer.
        1 for each task dispatcher's last observer pointer. 
        No accounting for neighbors in the shared list. */
    atomic<int> my_ref_count;
    //! Reference to the list this observer belongs to.
    observer_list* my_list;
    //! Pointer to next observer in the list specified by my_head.
    /** NULL for the last item in the list. **/
    observer_proxy* my_next;
    //! Pointer to the previous observer in the list specified by my_head.
    /** For the head of the list points to the last item. **/
    observer_proxy* my_prev;
    //! Associated observer
    task_scheduler_observer_v3* my_observer;
    //! Version
    char my_version;

    interface6::task_scheduler_observer* get_v6_observer();
    bool is_global(); //TODO: move them back inline when un-CPF'ing

    //! Constructs proxy for the given observer and adds it to the specified list.
    observer_proxy( task_scheduler_observer_v3& );

#if TBB_USE_ASSERT
    ~observer_proxy();
#endif /* TBB_USE_ASSERT */

    //! Shut up the warning
    observer_proxy& operator = ( const observer_proxy& );
}; // class observer_proxy

inline void observer_list::remove_ref_fast( observer_proxy*& p ) {
    if( p->my_observer ) {
        // Can decrement refcount quickly, as it cannot drop to zero while under the lock.
        int r = --p->my_ref_count;
        __TBB_ASSERT_EX( r, NULL );
        p = NULL;
    } else {
        // Use slow form of refcount decrementing, after the lock is released.
    }
}

inline void observer_list::notify_entry_observers( observer_proxy*& last, bool worker ) {
    if ( last == my_tail )
        return;
    do_notify_entry_observers( last, worker );
}

inline void observer_list::notify_exit_observers( observer_proxy*& last, bool worker ) {
    if ( !last )
        return;
    __TBB_ASSERT(is_alive((uintptr_t)last), NULL);
    do_notify_exit_observers( last, worker );
    __TBB_ASSERT(last, NULL);
    poison_value(last);
}

extern padded<observer_list> the_global_observer_list;

} // namespace internal
} // namespace tbb

#endif /* __TBB_SCHEDULER_OBSERVER */

#endif /* _TBB_observer_proxy_H */
