libjmmcg  release_579_6_g8cffd
A C++ library containing an eclectic mix of useful, advanced components.
thread_base.hpp
Go to the documentation of this file.
1 #ifndef LIBJMMCG_CORE_PRIVATE_THREAD_BASE_HPP
2 #define LIBJMMCG_CORE_PRIVATE_THREAD_BASE_HPP
3 
4 /******************************************************************************
5 ** Copyright © 2004 by J.M.McGuiness, coder@hussar.me.uk
6 **
7 ** This library is free software; you can redistribute it and/or
8 ** modify it under the terms of the GNU Lesser General Public
9 ** License as published by the Free Software Foundation; either
10 ** version 2.1 of the License, or (at your option) any later version.
11 **
12 ** This library is distributed in the hope that it will be useful,
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 ** Lesser General Public License for more details.
16 **
17 ** You should have received a copy of the GNU Lesser General Public
18 ** License along with this library; if not, write to the Free Software
19 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21 
22 #include "../../core/exception.hpp"
23 #include "../../core/thread_os_traits.hpp"
24 
25 namespace jmmcg { namespace LIBJMMCG_VER_NAMESPACE { namespace ppd { namespace private_ {
26 
27  /// This class is used to implement a raw class that wraps using a raw thread.
28  /**
29  This provides the thread-as-an-active-class threading model.
30  */
31  template<generic_traits::api_type API, typename Mdl>
32  class thread_base {
33  public:
34  typedef thread_os_traits<API, Mdl> os_traits;
36  typedef typename os_traits::lock_traits lock_traits;
38 
39  /**
40  To assist in allowing compile-time computation of the algorithmic order of the threading model.
41  */
42  static constexpr ppd::generic_traits::memory_access_modes memory_access_mode=lock_traits::critical_section_type::memory_access_mode;
43 
45 
46  /// Start the underlying kernel thread.
47  virtual void __fastcall create_running() noexcept(false);
48  const typename thread_traits::api_params_type & __fastcall params() const noexcept(true) FORCE_INLINE {
49  return thread_params;
50  }
51 
52  const typename thread_traits::api_params_type::suspend_count __fastcall suspend() noexcept(true);
53  const typename thread_traits::api_params_type::suspend_count __fastcall resume() noexcept(true);
54  const typename thread_traits::api_params_type::states __fastcall state() const noexcept(true);
55  bool __fastcall is_running() const noexcept(true);
56 
57  typename thread_traits::api_params_type::priority_type __fastcall kernel_priority() const noexcept(false);
58  void __fastcall kernel_priority(const typename thread_traits::api_params_type::priority_type priority) noexcept(false);
59  typename thread_traits::api_params_type::processor_mask_type __fastcall kernel_affinity() const noexcept(false);
60  void __fastcall kernel_affinity(const typename thread_traits::api_params_type::processor_mask_type &mask) noexcept(false);
61  void __fastcall set_name(char const *name) noexcept(false);
62 
63  virtual const tstring __fastcall to_string() const noexcept(false);
64 
65  /**
66  We don't gobble exceptions, because thread_pools might want to know if a member fails...
67  TODO Is this true?: Note that if this method throws an exception, wait_thread_exit() may lock up unless the situation is managed further.
68  */
69  virtual void __fastcall request_exit() const noexcept(false);
70 
71  protected:
72  /// This is to allow exceptions thrown in the contained thread to be (hopefully) propagated back to another thread on which that exception can be caught & handled.
74  // Ensure that assignment to the thread params is atomic.
77 
78  /// Create the wrapper object. Note that the underlying kernel thread will not have been started.
79  /**
80  \param exit_wait_p The length of time, in ms, that the dtor will wait for the thread to exit before terminating the thread. Such termination may cause undefined behaviour to result, depending upon the details of how the underlying OS manages thread termination.
81  */
82  explicit __stdcall thread_base(const typename thread_traits::api_params_type::suspend_period_ms exit_wait_p) noexcept(false);
83  explicit __stdcall thread_base(const thread_base &tb) noexcept(true);
84 
85  /// Thread base dtor.
86  /**
87  Don't throw any registered "exception_thrown_in_thread" here, because evil leaks will happen in the thread pool. YUCK.
88  Ignore any bad exit states from the thread too. Tough.
89  Ensure that wait_thread_exit() is called from the most-derived dtor, otherwise UB will happen.
90 
91  \see wait_thread_exit()
92  */
93  virtual __stdcall ~thread_base() noexcept(false);
94 
95  /**
96  This must be called from the most-derived class, otherwise you could get "pure virtual call" errors, because process() could be called, or other dependent class members could be deleted before the thread exists.
97  */
98  void __fastcall wait_thread_exit() noexcept(false);
99  /// This is the main "work" function. Implement it to do some work in the thread.
100  /**
101  Any exceptions thrown will be caught in the core_work_fn() and reported to the user (or calling thread) by re-throwing, at the latest, in the destructor.
102  */
103  virtual typename thread_traits::api_params_type::states __fastcall process() noexcept(false)=0;
104 
105  private:
106  static const typename thread_traits::api_params_type::states __fastcall state_nolk(typename thread_traits::api_params_type const &params) noexcept(true);
107 
108  /**
109  Perform some sanity checks then forwards to the process() function which actually does the work.
110  */
111  typename thread_traits::api_params_type::states __fastcall process_chk() noexcept(false) {
112  assert(this->thread_params.id==thread_traits::get_current_thread_id());
113  const auto state=this->process();
114  // Can't lock here, because we'll deadlock in wait_thread_exit(), also can't set thread.id to zero, otherwise we'll race with wait_thread_exit(), and possibly crash in it.
115  thread_params.state=state;
116  return state;
117  }
118 
119 
120 #pragma GCC diagnostic push
121 #pragma GCC diagnostic ignored "-Wattributes"
122 
123  /// This is the core function that is called by the the os-level thread to eventually do the work.
124  /**
125  It simply forwards to the execute_chk() function which actually does the work.
126  */
127  static typename thread_traits::api_params_type::core_work_fn_ret_t __cdecl core_work_fn(typename thread_traits::api_params_type::core_work_fn_arg_t) noexcept(false);
128 
129 #pragma GCC diagnostic pop
130 
131  };
132 
133  /**
134  \todo Implement using the advice given in "Standard C++ IOStreams and Locales" by A.Langer & K.Kreft, page 170.
135  */
136  template<generic_traits::api_type API, typename Mdl> inline tostream &__fastcall FORCE_INLINE
137  operator<<(tostream &os, thread_base<API, Mdl> const &th) {
138  return os<<th.to_string();
139  }
140 
141  /// Ensure that the deleter is not called for work that is stack-allocated.
142  /**
143  Determine if the work should be deleted at construction time, so that if the object goes out of scope by the time the dtor is run, we already know it should not be deleted, and therefore do not do anything.
144  */
145  template<class E>
147  public:
148  using element_type=E;
149  using atomic_ptr_t=typename element_type::atomic_ptr_t;
150  using work_complete_t=typename element_type::value_type::work_complete_t;
151 
152  /**
153  To assist in allowing compile-time computation of the algorithmic order of the threading model.
154  */
156  atomic_ptr_t::memory_access_mode==ppd::generic_traits::memory_access_modes::crew_memory_access
157  && work_complete_t::memory_access_mode==ppd::generic_traits::memory_access_modes::crew_memory_access
160  );
161 
162  explicit eval_shared_deleter_t(element_type &e) noexcept(true) FORCE_INLINE
163  : elem(), stack_based(e->sp_noop_ctr()) {
164  // Ensure we don't re-try processing work that threw an exception.
165  e.get().swap(elem);
166  assert(!dynamic_cast<typename atomic_ptr_t::value_type>(e.get().get()));
167  assert(stack_based || dynamic_cast<typename atomic_ptr_t::value_type>(elem.get()));
168  assert(stack_based || !dynamic_cast<typename element_type::no_ref_counting *>(elem.get()));
169  assert(stack_based || dynamic_cast<typename element_type::ctr_type *>(elem.get()));
170  assert(stack_based || *dynamic_cast<typename element_type::ctr_type *>(elem.get())>=1);
171  }
173  // execution_contexts (which are stack-based) may be already deleted, so the RTTI object-type may be long gone, good job we pre-computed it...
174  if (!stack_based) {
175  // Ok, on the heap, so we own it, so de-ref the f*cker.
176  element_type del;
177  del.get().swap(elem);
178 // TODO: There is a race condition with the nonjoinable_buff(...) transfers in subdivide_n_gen_wk, in which the heap::memory_buff may be deleted before this should occur...
179  assert(!elem.get());
180  assert(!dynamic_cast<typename element_type::no_ref_counting *>(del.get().get()));
181  assert(dynamic_cast<typename element_type::ctr_type *>(del.get().get()));
182  assert(*dynamic_cast<typename element_type::ctr_type *>(del.get().get())>=1);
183  }
184  }
185 
186  template<class UpdStats, class EdgeAnn> void FORCE_INLINE
187  process_the_work(UpdStats &&update_stats, EdgeAnn const e_details) noexcept(false) {
188  // Ensure we don't re-try processing work that threw an exception.
189  if (elem.get()) {
190 // TODO Set start & end times of processing op, so that time-order of nodes in DFG can be seen.
191  if (elem->result_traits()==generic_traits::return_data::joinable) {
192  // Make sure that we signal that the work has been completed before the related execution_context might be deleted from the stack, .i.e well before the dtor of this object.
193  const set_work_complete work_done(*elem->work_complete());
194  elem->process_joinable(e_details);
195  } else {
196  elem->process_nonjoinable(e_details);
197  }
198  update_stats();
199  }
200  }
201 
202  private:
203  class set_work_complete {
204  public:
205  explicit __stdcall set_work_complete(work_complete_t &wc) noexcept(true) FORCE_INLINE
206  : work_complete(wc) {
207  }
208  set_work_complete(set_work_complete const &)=delete;
209  __stdcall ~set_work_complete() noexcept(true) FORCE_INLINE {
210  work_complete.set();
211  }
212 
213  private:
214  work_complete_t& work_complete;
215  };
216 
217  atomic_ptr_t elem;
218  const bool stack_based; ///< We need to evaluate this at object construction time, because when the dtor is called, the object no longer may be valid (another thread may have let the containing execution_context go out of scope), so we can't determine them, at run-time, if it should be deleted or not.
219  };
220 
221 } } } }
222 
224 
225 #ifdef WIN32
226 # include "../experimental/NT-based/NTSpecific/thread_base_impl.hpp"
227 #else
228 #endif
229 
230 #endif