1 /*
2 Copyright (c) 2005-2022 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 nullptr. 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
82 //! Call entry notifications on observers added after last was notified.
83 /** Updates last to become the last notified observer proxy (in the global list)
84 or leaves it to be nullptr. The proxy has its refcount incremented. **/
85 inline void notify_entry_observers( observer_proxy*& last, bool worker );
86
87 //! Call exit notifications on last and observers added before it.
88 inline void notify_exit_observers( observer_proxy*& last, bool worker );
89 }; // class observer_list
90
91 //! Wrapper for an observer object
92 /** To maintain shared lists of observers the scheduler first wraps each observer
93 object into a proxy so that a list item remained valid even after the corresponding
94 proxy object is destroyed by the user code. **/
95 class observer_proxy {
96 friend class d1::task_scheduler_observer;
97 friend class observer_list;
98 friend void observe(d1::task_scheduler_observer&, bool);
99 //! Reference count used for garbage collection.
100 /** 1 for reference from my task_scheduler_observer.
101 1 for each task dispatcher's last observer pointer.
102 No accounting for neighbors in the shared list. */
103 std::atomic<std::uintptr_t> my_ref_count;
104 //! Reference to the list this observer belongs to.
105 observer_list* my_list;
106 //! Pointer to next observer in the list specified by my_head.
107 /** nullptr for the last item in the list. **/
108 observer_proxy* my_next;
109 //! Pointer to the previous observer in the list specified by my_head.
110 /** For the head of the list points to the last item. **/
111 observer_proxy* my_prev;
112 //! Associated observer
113 d1::task_scheduler_observer* my_observer;
114
115 //! Constructs proxy for the given observer and adds it to the specified list.
116 observer_proxy( d1::task_scheduler_observer& );
117
118 ~observer_proxy();
119 }; // class observer_proxy
120
remove_ref_fast(observer_proxy * & p)121 void observer_list::remove_ref_fast( observer_proxy*& p ) {
122 if( p->my_observer ) {
123 // Can decrement refcount quickly, as it cannot drop to zero while under the lock.
124 std::uintptr_t r = --p->my_ref_count;
125 __TBB_ASSERT_EX( r, nullptr);
126 p = nullptr;
127 } else {
128 // Use slow form of refcount decrementing, after the lock is released.
129 }
130 }
131
notify_entry_observers(observer_proxy * & last,bool worker)132 void observer_list::notify_entry_observers(observer_proxy*& last, bool worker) {
133 if (last == my_tail.load(std::memory_order_relaxed))
134 return;
135 do_notify_entry_observers(last, worker);
136 }
137
notify_exit_observers(observer_proxy * & last,bool worker)138 void observer_list::notify_exit_observers( observer_proxy*& last, bool worker ) {
139 if (last == nullptr) {
140 return;
141 }
142 __TBB_ASSERT(!is_poisoned(last), nullptr);
143 do_notify_exit_observers( last, worker );
144 __TBB_ASSERT(last != nullptr, nullptr);
145 poison_pointer(last);
146 }
147
148 } // namespace r1
149 } // namespace detail
150 } // namespace tbb
151
152 #endif /* __TBB_observer_proxy_H */
153