hmbdc
simplify-high-performance-messaging-programming
Client.hpp
1 #include "hmbdc/Copyright.hpp"
2 #pragma once
3 
4 #include "hmbdc/app/MessageDispacher.hpp"
5 #include "hmbdc/MetaUtils.hpp"
6 #include "hmbdc/Compile.hpp"
7 
8 #include <iostream>
9 #include <stdexcept>
10 #include <tuple>
11 #include <memory>
12 #include <type_traits>
13 
14 namespace hmbdc { namespace app {
15 
16 namespace client_detail {
17 template <size_t MAX_MEMORY_ATTACHMENT>
20  : att(new(underlyingMessage) hasMemoryAttachment){}
21  uint8_t underlyingMessage[MAX_MEMORY_ATTACHMENT];
22  template <MessageC Message>
23  bool cache(InBandHasMemoryAttachment<Message> const& ibma, uint16_t typeTagIn) {
24  static_assert(sizeof(Message) <= MAX_MEMORY_ATTACHMENT, "");
25  if (hmbdc_unlikely(att->attachment)) {
26  HMBDC_THROW(std::logic_error, "previous InBandMemoryAttachment not concluded");
27  }
28  typeTag = typeTagIn;
29 
30  if constexpr(Message::justBytes) {
31  memcpy(underlyingMessage, &ibma.underlyingMessage, sizeof(underlyingMessage));
32  } else {
33  memcpy(underlyingMessage, &ibma.underlyingMessage, sizeof(Message));
34  }
35 
36 
37  if (hmbdc_unlikely(!att->len)) {
38  att->attachment = nullptr;
39  att->afterConsumedCleanupFunc = nullptr;
40  accSize = 0;
41  return true;
42  } else if (att->holdShmHandle<Message>()) {
43  if constexpr (has_hmbdcShmRefCount<Message>::value) {
44  // auto addr = malloc(att->len);
45  auto shmAddr = hmbdcShmHandleToAddr(att->shmHandle);
46  // memcpy(addr, shmAddr, att->len);
47  att->attachment = shmAddr;
48  att->clientData[0] = (uint64_t)&hmbdcShmDeallocator;
49  static_assert(sizeof(ibma.underlyingMessage.hmbdcShmRefCount) == sizeof(size_t));
50  att->clientData[1] = (uint64_t)&ibma.underlyingMessage.hmbdcShmRefCount;
51  if constexpr (has_hmbdcShmRefCount<Message>::value) {
52  att->afterConsumedCleanupFunc = [](hasMemoryAttachment* h) {
53  auto hmbdcShmRefCount = (size_t*)h->clientData[1];
54  if (0 == __atomic_sub_fetch(hmbdcShmRefCount, 1, __ATOMIC_RELAXED)) {
55  auto& hmbdcShmDeallocator
56  = *(std::function<void (uint8_t*)>*)h->clientData[0];
57  hmbdcShmDeallocator((uint8_t*)h->attachment);
58  }
59  };
60  }
61  accSize = att->len;
62  } /// no else
63  return true;
64  } else {
65  att->attachment = malloc(att->len);
66  att->afterConsumedCleanupFunc = hasMemoryAttachment::free;
67  accSize = 0;
68  return false;
69  }
70  }
71 
72  bool accumulate(InBandMemorySeg const& ibms, size_t n) {
73  if (accSize < att->len) {
74  auto attBytes = ibms.seg;
75  auto copySize = std::min(n, att->len - accSize);
76  memcpy((char*)att->attachment + accSize, attBytes, copySize);
77  accSize += copySize;
78  return accSize == att->len;
79  }
80  return true;
81  }
82  hasMemoryAttachment * const att;
83  uint16_t typeTag = 0;
84  size_t accSize = 0;
85  std::function<void* (boost::interprocess::managed_shared_memory::handle_t)>
86  hmbdcShmHandleToAddr;
87  std::function<void (uint8_t*)> hmbdcShmDeallocator;
88 };
89 }
90 
91 /**
92  * @class single_thread_powered_client
93  * @brief a trait class, if a Client can only run on a single specific thread in Pool,
94  * derive the Client from it, hmbdc will check to ensure that is the case
95  */
97 
98 /**
99  * @class Client<>
100  * @brief A Client represents a thread of execution/a task. The execution is managed by
101  * a Context.
102  * a Client object could participate in message dispatching as the receiver of specifed
103  * message types
104  * @details message is dispatched thru callbacks in the loose
105  * form of void handleMessageCb(Message const& m) or void handleMessageCb(Message& m).
106  * some callbacks have default implementations.
107  * however, all callbacks are overridable to provide desired effects;
108  * When running in a Context, all callbacks (*Cb methods) for a hmbdc Client instance
109  * are garanteed to be called from a single OS thread, ie. no 2 callabacks of an Client
110  * instance are called concurrently, so the Client programmer can assume a
111  * single thread programming model callback-wise.
112  * The above also holds true for timer firing callbacks when a concrete Client deriving
113  * from TimerManager that manages the timers
114  *
115  * See in example hmbdc.cpp @snippet hmbdc.cpp write a Client
116  *
117  * @tparam CcClient the concrete Client type
118  * @tparam typename ... Messages message types that the Client interested
119  * if the type of JustBytes is declared, use a special callback see below
120  *
121  * @code
122  * void handleJustBytesCb(uint16_t tag, uint8_t* bytes, hasMemoryAttachment* att){...}
123  * @endcode
124  * is expected
125  * See in example hmbdc.cpp @snippet hmbdc.cpp use JustBytes instead of Message types
126  */
127 template <typename CcClient, MessageC ... Messages>
128 struct Client {
129  using Interests = std::tuple<Messages ...>;
130 
131  enum {
132  INTERESTS_SIZE = std::tuple_size<Interests>::value,
133  MAX_MEMORY_ATTACHMENT = max_size_in_tuple<
134  typename hmbdc::filter_in_tuple_by_base<hasMemoryAttachment, Interests>::type
135  >::value,
136  };
137 
138  /**
139  * @brief return the name of thread that runs this client, override if necessary
140  * @details this only used when the Client is running in direct mode
141  * @return thread name - default to be hmbdc0, hmbdc1 ...
142  */
143  char const* hmbdcName() const { return "hmbdc-anonymous"; }
144 
145  /**
146  * @brief an overrideable method.
147  * returns the schedule policy and priority, override if necessary
148  * priority is only used when policy is "SCHED_RR", or "SCHED_FIFO"
149  * @details this is only used when the Client is running in direct mode
150  * supported policy are "SCHED_OTHER"(=nullptr), "SCHED_RR", "SCHED_FIFO"
151  * @return a tuple made of schedule policy and priority, default to be SCHED_OTHER
152  */
153  std::tuple<char const*, int> schedSpec() const {
154 #ifndef _QNX_SOURCE
155  return std::make_tuple<char const*, int>(nullptr, 0);
156 #else
157  return std::make_tuple<char const*, int>(nullptr, 20);
158 #endif
159  }
160 
161  /**
162  * @brief an overridable method.
163  * client receives events in batches and the max batch size is controllable when running
164  * in direct mode Context. Here is to specify the max size.
165  * @details a message could only be reaching one client when using partition Context. In this
166  * case, reduce the size (reduce the greediness) might be useful
167  * @return the max number of messages when grabbing messages to process
168  */
169  size_t maxBatchMessageCount() const {return std::numeric_limits<size_t>::max();}
170 
171 
172  /**
173  * @brief called before any messages got dispatched - only once
174  * @details this is the place some preparation code goes to
175  *
176  * @param pClientDispatchingStarted pointer to the Client count that has started
177  * displatching within its Context; its value could
178  * change if you repeatedly check - might be used to sync with other Clients
179  * within the same Context (particularly IPC Context) at starting up stage of the system.
180  * When doing the sync, just be aware the Client might be running in a pool - do
181  * not hog the pool thread if possible.
182  * Note: Since ipc_creator Context has an implicitly purger Client, this value would be
183  * 1 greater than the user Clients count
184  */
185  virtual void messageDispatchingStartedCb(size_t const* pClientDispatchingStarted) {
186  //default do nothing
187  };
188 
189  /**
190  * @brief callback called when this Client is taken out of message dispatching
191  * @details after this call the Client is still at hook from the Context point of view
192  * (until droppedCb is called), so don't delete this Client yet or add it back to
193  * the Context. any exception thrown here is ignored,
194  *
195  * @param e the exception that caused the Client to be taken out of message dispatching
196  * e could be thrown by the Client itself in a callback function
197  * to voluntarily take itself out
198  */
199  virtual void stoppedCb(std::exception const& e) {
200  // called when this client is stopped, e.what() might have reason
201  std::cerr << "Client " << static_cast<CcClient*>(this)->hmbdcName()
202  << " exited with reason: " << e.what() << std::endl;
203  };
204 
205  /**
206  * @brief callback called after the Client is safely taken out of the Context
207  * @details exception thrown here is ignored and return true is assumed
208  * @return if false, this Client is added back to the Context to process messages
209  * otherwise, no more callback. You could even safely "delete this; return true;"
210  */
211  virtual bool droppedCb() {
212  // called when this client is dropped and free to be deleted
213  return true;
214  };
215 
216  /**
217  * @brief this callback is called all the time (frequently) - the exact timing is
218  * after a batch of messages are dispatched. After this call returns, the previously
219  * dispatched message's addresses are no longer valid, which means if you cache the
220  * event addresses in the previous handleMessageCb()s, you cannot use those after
221  * the return of the next invokeCb function.
222  * @details you can collectively process the messages received/cached so far here, or
223  * do something needs to be done all the time like powering another message loop
224  *
225  * @param dispatched the number of messages dispatched since last invokedCb called
226  */
227  virtual void invokedCb(size_t dispatched) {
228  // called as frequently as workload allowed
229  }
230 
231  /**
232  * @brief trivial
233  */
234  virtual ~Client(){}
235 
236 protected:
237  /**
238  * @brief the derived user's Client has the option to stop the current batch of event
239  * dispatching.
240  * @details for example, if the user's Client decides it has handled all interested
241  * messages and wants to skip the remaining messages within the CURRENT batch, call
242  * this within any callback functions.
243  * use with caution, only when it is absolutely certain that there is NO messages of
244  * ANY type the Client need to care are within the current batch.
245  */
246  void batchDone() {
247  batchDone_ = true;
248  }
249 
250 public:
251  /**
252  * @brief the following are for internal use, don't change or override
253  */
254  void stopped(std::exception const&e) noexcept {
255  try {
256  stoppedCb(e);
257  } catch (...) {}
258  }
259 
260  bool dropped() noexcept {
261  bool res = true;
262  try {
263  res = droppedCb();
264  } catch (...) {}
265  return res;
266  }
267 
268  template <typename Iterator>
269  size_t handleRangeImpl(Iterator it,
270  Iterator end, uint16_t threadId) {
271  CcClient& c = static_cast<CcClient&>(*this);
272  size_t res = 0;
273  for (;hmbdc_likely(!batchDone_ && it != end); ++it) {
274  if (MessageDispacher<CcClient, Interests>()(
275  c, *static_cast<MessageHead*>(*it))) res++;
276  }
277  batchDone_ = false;
278  return res;
279  }
280  typename std::conditional<MAX_MEMORY_ATTACHMENT != 0
281  , client_detail::InBandMemoryAttachmentProcessor<MAX_MEMORY_ATTACHMENT>
282  , std::nullptr_t>::type ibmaProc;
283 
284  uint16_t hmbdcNumber = 0xffff;
285 
286 protected:
287  bool batchDone_ = false; ///not part of API, do not touch
288 };
289 
290 /**
291  * if you do not have Message paramter pack, just have tuple
292  */
293 template <typename CcClient, typename MessageTuple>
295 template <typename CcClient, MessageC ... Messages>
296 struct client_using_tuple<CcClient, std::tuple<Messages...>> {
297  using type = Client<CcClient, Messages...>;
298 };
299 }} //hmbdc::app
virtual void invokedCb(size_t dispatched)
this callback is called all the time (frequently) - the exact timing is after a batch of messages are...
Definition: Client.hpp:227
char const * hmbdcName() const
return the name of thread that runs this client, override if necessary
Definition: Client.hpp:143
virtual ~Client()
trivial
Definition: Client.hpp:234
boost::interprocess::managed_shared_memory ::handle_t shmHandle
Definition: Message.hpp:169
Definition: TypedString.hpp:84
std::tuple< char const *, int > schedSpec() const
an overrideable method. returns the schedule policy and priority, override if necessary priority is o...
Definition: Client.hpp:153
bool cache(InBandHasMemoryAttachment< Message > const &ibma, uint16_t typeTagIn)
Definition: Client.hpp:23
virtual bool droppedCb()
callback called after the Client is safely taken out of the Context
Definition: Client.hpp:211
void batchDone()
the derived user&#39;s Client has the option to stop the current batch of event dispatching.
Definition: Client.hpp:246
void stopped(std::exception const &e) noexcept
the following are for internal use, don&#39;t change or override
Definition: Client.hpp:254
size_t maxBatchMessageCount() const
an overridable method. client receives events in batches and the max batch size is controllable when ...
Definition: Client.hpp:169
Definition: Client.hpp:294
Definition: Message.hpp:459
virtual void stoppedCb(std::exception const &e)
callback called when this Client is taken out of message dispatching
Definition: Client.hpp:199
a trait class, if a Client can only run on a single specific thread in Pool, derive the Client from i...
Definition: Client.hpp:96
Definition: MetaUtils.hpp:100
A Client represents a thread of execution/a task. The execution is managed by a Context. a Client object could participate in message dispatching as the receiver of specifed message types.
Definition: Client.hpp:128
virtual void messageDispatchingStartedCb(size_t const *pClientDispatchingStarted)
called before any messages got dispatched - only once
Definition: Client.hpp:185
a std tuple holding messages types it can dispatch
Definition: BlockingContext.hpp:206
if a specific hmbdc network transport (for example tcpcast, rmcast, and rnetmap) supports message wit...
Definition: Message.hpp:125
Definition: Base.hpp:12
uint64_t clientData[2]
byte size of the above
Definition: Message.hpp:183
Definition: Message.hpp:430