1 /*
2     Copyright (c) 2005-2021 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 #ifndef __TBB_observer_proxy_H
18 #define __TBB_observer_proxy_H
19 
20 #include "oneapi/tbb/detail/_config.h"
21 #include "oneapi/tbb/detail/_aligned_space.h"
22 
23 #include "oneapi/tbb/task_scheduler_observer.h"
24 #include "oneapi/tbb/spin_rw_mutex.h"
25 
26 namespace tbb {
27 namespace detail {
28 namespace r1 {
29 
30 class observer_list {
31     friend class arena;
32 
33     // Mutex is wrapped with aligned_space to shut up warnings when its destructor
34     // is called while threads are still using it.
35     typedef aligned_space<spin_rw_mutex>  my_mutex_type;
36 
37     //! Pointer to the head of this list.
38     std::atomic<observer_proxy*> my_head{nullptr};
39 
40     //! Pointer to the tail of this list.
41     std::atomic<observer_proxy*> my_tail{nullptr};
42 
43     //! Mutex protecting this list.
44     my_mutex_type my_mutex;
45 
46     //! Back-pointer to the arena this list belongs to.
47     arena* my_arena;
48 
49     //! Decrement refcount of the proxy p if there are other outstanding references.
50     /** In case of success sets p to NULL. Must be invoked from under the list lock. **/
51     inline static void remove_ref_fast( observer_proxy*& p );
52 
53     //! Implements notify_entry_observers functionality.
54     void do_notify_entry_observers( observer_proxy*& last, bool worker );
55 
56     //! Implements notify_exit_observers functionality.
57     void do_notify_exit_observers( observer_proxy* last, bool worker );
58 
59 public:
60     observer_list () = default;
61 
62     //! Removes and destroys all observer proxies from the list.
63     /** Cannot be used concurrently with other methods. **/
64     void clear ();
65 
66     //! Add observer proxy to the tail of the list.
67     void insert ( observer_proxy* p );
68 
69     //! Remove observer proxy from the list.
70     void remove ( observer_proxy* p );
71 
72     //! Decrement refcount of the proxy and destroy it if necessary.
73     /** When refcount reaches zero removes the proxy from the list and destructs it. **/
74     void remove_ref( observer_proxy* p );
75 
76     //! Type of the scoped lock for the reader-writer mutex associated with the list.
77     typedef spin_rw_mutex::scoped_lock scoped_lock;
78 
79     //! Accessor to the reader-writer mutex associated with the list.
mutex()80     spin_rw_mutex& mutex () { return my_mutex.begin()[0]; }
81 
empty()82     bool empty () const { return my_head.load(std::memory_order_relaxed) == nullptr; }
83 
84     //! Call entry notifications on observers added after last was notified.
85     /** Updates last to become the last notified observer proxy (in the global list)
86         or leaves it to be nullptr. The proxy has its refcount incremented. **/
87     inline void notify_entry_observers( observer_proxy*& last, bool worker );
88 
89     //! Call exit notifications on last and observers added before it.
90     inline void notify_exit_observers( observer_proxy*& last, bool worker );
91 }; // class observer_list
92 
93 //! Wrapper for an observer object
94 /** To maintain shared lists of observers the scheduler first wraps each observer
95     object into a proxy so that a list item remained valid even after the corresponding
96     proxy object is destroyed by the user code. **/
97 class observer_proxy {
98     friend class d1::task_scheduler_observer;
99     friend class observer_list;
100     friend void observe(d1::task_scheduler_observer&, bool);
101     //! Reference count used for garbage collection.
102     /** 1 for reference from my task_scheduler_observer.
103         1 for each task dispatcher's last observer pointer.
104         No accounting for neighbors in the shared list. */
105     std::atomic<std::uintptr_t> my_ref_count;
106     //! Reference to the list this observer belongs to.
107     observer_list* my_list;
108     //! Pointer to next observer in the list specified by my_head.
109     /** NULL for the last item in the list. **/
110     observer_proxy* my_next;
111     //! Pointer to the previous observer in the list specified by my_head.
112     /** For the head of the list points to the last item. **/
113     observer_proxy* my_prev;
114     //! Associated observer
115     d1::task_scheduler_observer* my_observer;
116 
117     //! Constructs proxy for the given observer and adds it to the specified list.
118     observer_proxy( d1::task_scheduler_observer& );
119 
120     ~observer_proxy();
121 }; // class observer_proxy
122 
remove_ref_fast(observer_proxy * & p)123 void observer_list::remove_ref_fast( observer_proxy*& p ) {
124     if( p->my_observer ) {
125         // Can decrement refcount quickly, as it cannot drop to zero while under the lock.
126         std::uintptr_t r = --p->my_ref_count;
127         __TBB_ASSERT_EX( r, NULL );
128         p = NULL;
129     } else {
130         // Use slow form of refcount decrementing, after the lock is released.
131     }
132 }
133 
notify_entry_observers(observer_proxy * & last,bool worker)134 void observer_list::notify_entry_observers(observer_proxy*& last, bool worker) {
135     if (last == my_tail.load(std::memory_order_relaxed))
136         return;
137     do_notify_entry_observers(last, worker);
138 }
139 
notify_exit_observers(observer_proxy * & last,bool worker)140 void observer_list::notify_exit_observers( observer_proxy*& last, bool worker ) {
141     if (last == nullptr) {
142         return;
143     }
144     __TBB_ASSERT(!is_poisoned(last), NULL);
145     do_notify_exit_observers( last, worker );
146     __TBB_ASSERT(last != nullptr, NULL);
147     poison_pointer(last);
148 }
149 
150 } // namespace r1
151 } // namespace detail
152 } // namespace tbb
153 
154 #endif /* __TBB_observer_proxy_H */
155