HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
dispatcher.h
Go to the documentation of this file.
1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the terms set forth in the LICENSE.txt file available at
5 // https://openusd.org/license.
6 //
7 #ifndef PXR_BASE_WORK_DISPATCHER_H
8 #define PXR_BASE_WORK_DISPATCHER_H
9 
10 /// \file
11 
12 #include "pxr/pxr.h"
13 #include "pxr/base/work/api.h"
14 #include "pxr/base/work/impl.h"
16 
17 #include "pxr/base/tf/errorMark.h"
19 #include "pxr/base/tf/mallocTag.h"
20 
21 #include <functional>
22 #include <type_traits>
23 #include <utility>
24 
26 
27 // The Work_Dispatcher interface, specialized with a dispatcher impl template
28 // argument.
29 //
30 // Clients expected to use the WorkDispatcher type instead.
31 template <class Impl>
33 {
34 protected:
35  // Prevent construction of the work dispatcher base class.
37 
38 public:
39  /// Wait() for any pending tasks to complete, then destroy the dispatcher.
40  WORK_API ~Work_Dispatcher() noexcept;
41 
42  Work_Dispatcher(Work_Dispatcher const &) = delete;
43  Work_Dispatcher &operator=(Work_Dispatcher const &) = delete;
44 
45 #ifdef doxygen
46 
47  /// Add work for the dispatcher to run.
48  ///
49  /// Before a call to Wait() is made it is safe for any client to invoke
50  /// Run(). Once Wait() is invoked, it is \b only safe to invoke Run() from
51  /// within the execution of tasks already added via Run().
52  ///
53  /// This function does not block, in general. It may block if concurrency
54  /// is limited to 1. The added work may be not yet started, may be started
55  /// but not completed, or may be completed upon return. No guarantee is
56  /// made.
57  template <class Callable, class A1, class A2, ... class AN>
58  void Run(Callable &&c, A1 &&a1, A2 &&a2, ... AN &&aN);
59 
60 #else // doxygen
61 
62  template <class Callable>
63  inline void Run(Callable &&c) {
64  if (TfMallocTag::IsInitialized()) {
65  _dispatcher.Run(
66  _MallocTagsInvokerTask<
68  std::forward<Callable>(c), &_errors));
69  }
70  else {
71  _dispatcher.Run(
72  _InvokerTask<typename std::remove_reference<Callable>::type>(
73  std::forward<Callable>(c), &_errors));
74  }
75  }
76 
77  template <class Callable, class A0, class ... Args>
78  inline void Run(Callable &&c, A0 &&a0, Args&&... args) {
79  Run(std::bind(std::forward<Callable>(c),
80  std::forward<A0>(a0),
81  std::forward<Args>(args)...));
82  }
83 
84 #endif // doxygen
85 
86  /// Block until the work started by Run() completes.
87  WORK_API void Wait();
88 
89  /// Cancel remaining work and return immediately.
90  ///
91  /// Calling this function affects task that are being run directly
92  /// by this dispatcher. If any of these tasks are using their own
93  /// dispatchers to run tasks, these dispatchers will not be affected
94  /// and these tasks will run to completion, unless they are also
95  /// explicitly cancelled.
96  ///
97  /// This call does not block. Call Wait() after Cancel() to wait for
98  /// pending tasks to complete.
99  WORK_API void Cancel();
100 
101  /// Returns true if Cancel() has been called. Calling Wait() will reset the
102  /// cancel state.
103  WORK_API bool IsCancelled() const;
104 
105 private:
106  typedef tbb::concurrent_vector<TfErrorTransport> _ErrorTransports;
107 
108  // Function invoker helper that wraps the invocation with an ErrorMark so we
109  // can transmit errors that occur back to the thread that Wait() s for tasks
110  // to complete.
111  template <class Fn>
112  struct _InvokerTask {
113  explicit _InvokerTask(Fn &&fn, _ErrorTransports *err)
114  : _fn(std::move(fn))
115  , _errors(err) {}
116 
117  explicit _InvokerTask(Fn const &fn, _ErrorTransports *err)
118  : _fn(fn)
119  , _errors(err) {}
120 
121  // Ensure only moves happen, no copies.
122  _InvokerTask(_InvokerTask &&other) = default;
123  _InvokerTask(const _InvokerTask &other) = delete;
124  _InvokerTask &operator=(const _InvokerTask &other) = delete;
125 
126  void operator()() const {
127  TfErrorMark m;
128  _fn();
129  if (!m.IsClean())
130  Work_Dispatcher::_TransportErrors(m, _errors);
131  }
132  private:
133  Fn _fn;
134  _ErrorTransports *_errors;
135  };
136 
137  // Function invoker helper that wraps the invocation with an ErrorMark so we
138  // can transmit errors that occur back to the thread that Wait() s for tasks
139  // to complete. This version also duplicates the caller's malloc tag stack
140  // to the callee's thread.
141  template <class Fn>
142  struct _MallocTagsInvokerTask {
143  explicit _MallocTagsInvokerTask(Fn &&fn, _ErrorTransports *err)
144  : _fn(std::move(fn))
145  , _errors(err)
146  , _mallocTagStack(TfMallocTag::GetCurrentStackState())
147  {}
148 
149  explicit _MallocTagsInvokerTask(Fn const &fn, _ErrorTransports *err)
150  : _fn(fn)
151  , _errors(err)
152  , _mallocTagStack(TfMallocTag::GetCurrentStackState()) {}
153 
154  // Ensure only moves happen, no copies.
155  _MallocTagsInvokerTask(_MallocTagsInvokerTask &&other) = default;
156  _MallocTagsInvokerTask(const _MallocTagsInvokerTask &other) = delete;
157  _MallocTagsInvokerTask &
158  operator=(const _MallocTagsInvokerTask &other) = delete;
159 
160  void operator()() const {
161  TfErrorMark m;
162  TfMallocTag::StackOverride ovr(_mallocTagStack);
163  _fn();
164  if (!m.IsClean())
165  Work_Dispatcher::_TransportErrors(m, _errors);
166  }
167  private:
168  Fn _fn;
169  _ErrorTransports *_errors;
170  TfMallocTag::StackState _mallocTagStack;
171  };
172 
173  // Helper function that removes errors from \p m and stores them in a new
174  // entry in \p errors.
175  WORK_API static void
176  _TransportErrors(const TfErrorMark &m, _ErrorTransports *errors);
177 
178  // WorkDispatcher implementation
179  Impl _dispatcher;
180  std::atomic<bool> _isCancelled;
181 
182  // The error transports we use to transmit errors in other threads back to
183  // this thread.
184  _ErrorTransports _errors;
185 
186  // Concurrent calls to Wait() have to serialize certain cleanup operations.
187  std::atomic_flag _waitCleanupFlag;
188 };
189 
190 /// \class WorkDispatcher
191 /// \extends Work_Dispatcher
192 ///
193 /// A work dispatcher runs concurrent tasks. The dispatcher supports adding
194 /// new tasks from within running tasks. This suits problems that exhibit
195 /// hierarchical structured parallelism: tasks that discover additional tasks
196 /// during their execution.
197 ///
198 /// Typical use is to create a dispatcher and invoke Run() to begin doing
199 /// work, then Wait() for the work to complete. Tasks may invoke Run() during
200 /// their execution as they discover additional tasks to perform.
201 ///
202 /// For example,
203 ///
204 /// \code
205 /// WorkDispatcher dispatcher;
206 /// for (i = 0; i != N; ++i) {
207 /// dispatcher.Run(DoSomeWork, workItem[i]);
208 /// }
209 /// dispatcher.Wait();
210 /// \endcode
211 ///
212 /// Calls to Run() and Cancel() may be made concurrently. Calls to Wait() may
213 /// also be made concurrently. However, once any calls to Wait() are in-flight,
214 /// calls to Run() and Cancel() must only be made by tasks already added by
215 /// Run(). This means that users of this class are responsible to synchronize
216 /// concurrent calls to Wait() to ensure this requirement is met.
217 ///
218 /// Additionally, Wait() must never be called by a task added by Run(), since
219 /// that task could never complete.
220 ///
222  : public Work_Dispatcher<PXR_WORK_IMPL_NS::WorkImpl_Dispatcher>
223 {};
224 
225 // Wrapper class for non-const tasks.
226 template <class Fn>
228  explicit Work_DeprecatedMutableTask(Fn &&fn)
229  : _fn(std::move(fn)) {}
230 
231  explicit Work_DeprecatedMutableTask(Fn const &fn)
232  : _fn(fn) {}
233 
234  // Ensure only moves happen, no copies.
236  (Work_DeprecatedMutableTask &&other) = default;
238  (const Work_DeprecatedMutableTask &other) = delete;
240  &operator= (const Work_DeprecatedMutableTask &other) = delete;
241 
242  void operator()() const {
243  _fn();
244  }
245 private:
246  mutable Fn _fn;
247 };
248 
249 // Wrapper function to convert non-const tasks to a Work_DeprecatedMutableTask.
250 // When adding new tasks refrain from using this wrapper, instead ensure the
251 // call operator of the task is const such that it is compatible with oneTBB.
252 template <typename Fn>
256  (std::forward<Fn>(fn));
257 }
258 
259 ///////////////////////////////////////////////////////////////////////////////
260 
262 
263 #endif // PXR_BASE_WORK_DISPATCHER_H
Work_Dispatcher & operator=(Work_Dispatcher const &)=delete
type
Definition: core.h:556
Work_DeprecatedMutableTask(Fn const &fn)
Definition: dispatcher.h:231
#define PXR_NAMESPACE_OPEN_SCOPE
Definition: pxr.h:73
void Run(Callable &&c)
Definition: dispatcher.h:63
#define WORK_API
Definition: api.h:23
WORK_API Work_Dispatcher()
void operator()() const
Definition: dispatcher.h:242
WORK_API bool IsCancelled() const
bool IsClean() const
Definition: errorMark.h:82
WORK_API void Cancel()
void Run(Callable &&c, A0 &&a0, Args &&...args)
Definition: dispatcher.h:78
Work_DeprecatedMutableTask< typename std::remove_reference_t< Fn > > WorkMakeDeprecatedMutableTask(Fn &&fn)
Definition: dispatcher.h:254
Work_DeprecatedMutableTask(Fn &&fn)
Definition: dispatcher.h:228
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74
**If you just want to fire and args
Definition: thread.h:618
WORK_API void Wait()
Block until the work started by Run() completes.
Work_DeprecatedMutableTask & operator=(const Work_DeprecatedMutableTask &other)=delete
WORK_API ~Work_Dispatcher() noexcept
Wait() for any pending tasks to complete, then destroy the dispatcher.