hmbdc
simplify-high-performance-messaging-programming
SendTransportEngine.hpp
1 #include "hmbdc/Copyright.hpp"
2 #pragma once
3 #include "hmbdc/app/Client.hpp"
4 #include "hmbdc/app/Logger.hpp"
5 #include "hmbdc/tips/rmcast/Transport.hpp"
6 #include "hmbdc/tips/rmcast/BackupSendServer.hpp"
7 #include "hmbdc/tips/rmcast/Messages.hpp"
8 #include "hmbdc/tips/rmcast/McSendTransport.hpp"
9 #include "hmbdc/tips/rmcast/DefaultUserConfig.hpp"
10 
11 #include "hmbdc/time/Time.hpp"
12 #include "hmbdc/time/Rater.hpp"
13 #include "hmbdc/numeric/BitMath.hpp"
14 #include "hmbdc/MetaUtils.hpp"
15 
16 #include <algorithm>
17 #include <memory>
18 #include <tuple>
19 #include <mutex>
20 #include <type_traits>
21 
22 namespace hmbdc { namespace tips { namespace rmcast {
23 
24 namespace sendtransportengine_detail {
25 HMBDC_CLASS_HAS_DECLARE(hmbdc_net_queued_ts);
26 
27 /**
28  * @brief capture the transportation mechanism
29  *
30  */
34  /**
35  * @brief ctor
36  * @param cfg jason specifing the transport - see example, perf-rmcast.cpp
37  * @param maxMessageSize max messafe size in bytes to be sent
38  * hold the message in buffer until buffer is full to start sending
39  */
40  SendTransport(hmbdc::app::Config cfg, size_t maxMessageSize);
41 
42  size_t bufferedMessageCount() const {
43  return buffer_.remainingSize();
44  }
45 
46  size_t subscribingPartyDetectedCount(uint16_t tag) const {
47  return outboundSubscriptions_.check(tag);
48  }
49 
50  template <typename MemoryAttachementMessage>
51  typename std::enable_if<std::is_base_of<app::hasMemoryAttachment
52  , typename std::decay<MemoryAttachementMessage>::type>::value, void>::type
53  queue(MemoryAttachementMessage&& msg) {
54  if (!minRecvToStart_ && !outboundSubscriptions_.check(msg.getTypeTag())) {
55  msg.release();
56  return;
57  }
58  using Message = typename std::decay<MemoryAttachementMessage>::type;
59  if (hmbdc_unlikely(sizeof(Message) > maxMessageSize_
60  || sizeof(app::MemorySeg) > maxMessageSize_
61  || sizeof(app::StartMemorySegTrain) > maxMessageSize_)) {
62  HMBDC_THROW(std::out_of_range
63  , "maxMessageSize too small to hold a message when constructing SendTransportEngine");
64  }
65 
66  // + train header and actual msg
67  size_t n = (msg.app::hasMemoryAttachment::len + maxMemorySegPayloadSize_ - 1ul) / maxMemorySegPayloadSize_ + 2;
68  if (hmbdc_unlikely(n > buffer_.capacity())) {
69  HMBDC_THROW(std::out_of_range
70  , "send engine buffer too small capacity=" << buffer_.capacity() << "<" << n);
71  }
72  auto it = buffer_.claim(n);
73  queueMemorySegTrain(it, n, std::forward<MemoryAttachementMessage>(msg));
74  buffer_.commit(it, n);
75  }
76 
77 
78  template <MessageC Message>
79  typename std::enable_if<!std::is_base_of<hmbdc::app::hasMemoryAttachment
80  , typename std::decay<Message>::type>::value, void>::type
81  queue(Message&& msg) {
82  if (!outboundSubscriptions_.check(msg.getTypeTag())) return;
83  auto n = 1;
84  auto it = buffer_.claim(n);
85  queue(it, std::forward<Message>(msg));
86  buffer_.commit(it, n);
87  }
88 
89  template <MessageC Message>
90  typename std::enable_if<!std::is_base_of<hmbdc::app::hasMemoryAttachment
91  , typename std::decay<Message>::type>::value, bool>::type
92  tryQueueMessages(Message&& msg) {
93  if (!outboundSubscriptions_.check(msg.getTypeTag())) return true;
94  auto n = 1;
95  auto it = buffer_.tryClaim(n);
96  if (it) {
97  queue(it, std::forward<Message>(msg));
98  buffer_.commit(it, n);
99  return true;
100  }
101  return false;
102  }
103 
104  template <typename Message, typename ... Args>
105  void queueInPlace(Args&&... args) {
106  static_assert(std::is_trivially_destructible<Message>::value, "cannot send message with dtor");
107  auto s = buffer_.claim();
108  char* addr = static_cast<char*>(*s);
109  auto h = new (addr) TransportMessageHeader;
110  if (hmbdc_likely(sizeof(Message) <= maxMessageSize_)) {
111  new (addr + sizeof(TransportMessageHeader)) app::MessageWrap<Message>(std::forward<Args>(args)...);
112  h->messagePayloadLen = sizeof(app::MessageWrap<Message>);
113  h->setSeq(s.seq_);
114  } else {
115  HMBDC_THROW(std::out_of_range
116  , "maxMessageSize too small to hold a message when constructing SendTransportEngine");
117  }
118  buffer_.commit(s);
119  }
120 
121  void queueJustBytes(uint16_t tag, void const* bytes, size_t len
122  , app::hasMemoryAttachment* att) {
123  if (!att) {
124  if (!outboundSubscriptions_.check(tag)) return;
125  auto it = buffer_.claim();
126  auto s = *it;
127  char* addr = static_cast<char*>(s);
128  auto h = new (addr) TransportMessageHeader;
129  if (hmbdc_likely(len <= maxMessageSize_)) {
130  new (addr + sizeof(TransportMessageHeader)) app::MessageWrap<app::JustBytes>(tag, bytes, len, att);
131  h->messagePayloadLen = sizeof(app::MessageHead) + len;
132  h->setSeq(it.seq_);
133  } else {
134  HMBDC_THROW(std::out_of_range
135  , "maxMessageSize too small to hold a message when constructing SendTransportEngine");
136  }
137  buffer_.commit(it);
138  } else {
139  if (!minRecvToStart_ && !outboundSubscriptions_.check(tag)) {
140  att->release();
141  return;
142  }
143  if (hmbdc_unlikely(len > maxMessageSize_
144  || sizeof(app::MemorySeg) > maxMessageSize_
145  || sizeof(app::StartMemorySegTrain) > maxMessageSize_)) {
146  HMBDC_THROW(std::out_of_range
147  , "maxMessageSize too small to hold a message when constructing SendTransportEngine");
148  }
149 
150  // + train header and actual msg
151  size_t n = (att->len + maxMemorySegPayloadSize_ - 1ul) / maxMemorySegPayloadSize_ + 2;
152  if (hmbdc_unlikely(n > buffer_.capacity())) {
153  HMBDC_THROW(std::out_of_range
154  , "send engine buffer too small capacity=" << buffer_.capacity() << "<" << n);
155  }
156  auto it = buffer_.claim(n);
157  queueMemorySegTrain(it, n, tag, *att, len);
158  buffer_.commit(it, n);
159  }
160  }
161 
162  void runOnce() {
163  hmbdc::app::utils::EpollTask::instance().poll();
164  if (hmbdc_unlikely(minRecvToStart_ != std::numeric_limits<size_t>::max()
165  && asyncBackupSendServer_.readySessionCount() >= minRecvToStart_)) {
166  minRecvToStart_ = std::numeric_limits<size_t>::max(); //now started no going back
167  mcSendTransport_.startSend();
168  }
169  asyncBackupSendServer_.runOnce();
170  if (advertisedTypeTagsDirty_) {
171  std::unique_lock<std::mutex> g(advertisedTypeTags_.lock, std::try_to_lock);
172  if (g.owns_lock()) {
173  mcSendTransport_.setAds(asyncBackupSendServer_.advertisingMessages());
174  advertisedTypeTagsDirty_ = false;
175  }
176  }
177  mcSendTransport_.runOnce(asyncBackupSendServer_.sessionCount());
178  }
179 
180  void stop();
181 
182 
183  size_t sessionsRemainingActive() const {
184  return asyncBackupSendServer_.readySessionCount();
185  }
186 
187  template <MessageTupleC Messages, typename Node>
188  void advertiseFor(Node const& node, uint16_t mod, uint16_t res) {
189  std::scoped_lock<std::mutex> g(advertisedTypeTags_.lock);
190  advertisedTypeTags_.markPubsFor<Messages>(node, mod, res);
191  asyncBackupSendServer_.advertisingMessages(advertisedTypeTags_);
192  advertisedTypeTagsDirty_ = true;
193  }
194 
195 private:
196  size_t maxMessageSize_;
198  Buffer buffer_;
199 
200  hmbdc::time::Rater rater_;
201  McSendTransport mcSendTransport_;
202  size_t minRecvToStart_;
203  hmbdc::time::ReoccuringTimer typeTagAdTimer_;
204  HMBDC_SEQ_TYPE lastBackupSeq_;
205  hmbdc::time::ReoccuringTimer flushTimer_;
206  uint16_t maxMemorySegPayloadSize_;
207  bool waitForSlowReceivers_;
208  reliable::ToCleanupAttQueue toCleanupAttQueue_;
209  TypeTagSet outboundSubscriptions_;
210  AsyncBackupSendServer asyncBackupSendServer_;
211  bool stopped_;
212  std::atomic<bool> advertisedTypeTagsDirty_ = false;
213  TypeTagSetST advertisedTypeTags_;
214 
215 private:
216  uint16_t
217  outBufferSizePower2();
218 
219  template<typename M>
220  void queueMemorySegTrain(typename Buffer::iterator it, size_t n, M&& m) {
221  using Message = typename std::decay<M>::type;
222  //train head
223  auto s = *it;
224  char* addr = static_cast<char*>(s);
225  auto h = new (addr) TransportMessageHeader;
226  auto& trainHead = (new (addr + sizeof(TransportMessageHeader))
227  app::MessageWrap<app::StartMemorySegTrain>)->template get<app::StartMemorySegTrain>();
228 
229  trainHead.inbandUnderlyingTypeTag = m.getTypeTag();
230  trainHead.att = m;
231  trainHead.segCount = n - 2;
232  h->messagePayloadLen = sizeof(app::MessageWrap<app::StartMemorySegTrain>);
233  h->setSeq(it++.seq_);
234 
235  //train body
236  {
237  auto totalLen = (size_t)m.app::hasMemoryAttachment::len;
238  char* base = (char*)m.app::hasMemoryAttachment::attachment;
239  decltype(totalLen) offset = 0;
240  while(totalLen > offset) {
241  auto s = *it;
242  char* addr = static_cast<char*>(s);
243  auto h = new (addr) TransportMessageHeader;
244  auto& seg = (new (addr + sizeof(TransportMessageHeader)) app::MessageWrap<app::MemorySeg>)
245  ->template get<app::MemorySeg>();
246  seg.seg = base + offset;
247  seg.inbandUnderlyingTypeTag = m.getTypeTag();
248  auto l = (uint16_t)std::min((size_t)maxMemorySegPayloadSize_, totalLen - offset);
249  seg.len = l;
250  h->messagePayloadLen = sizeof(app::MessageHead) + l; //skipped bytes
251  h->setSeq(it++.seq_);
252  offset += l;
253  }
254  }
255 
256  //actual message
257  {
258  auto s = *it;
259  char* addr = static_cast<char*>(s);
260  auto h = new (addr) TransportMessageHeader;
261  new (addr + sizeof(TransportMessageHeader)) app::MessageWrap<Message>(std::forward<M>(m));
262  h->messagePayloadLen = sizeof(app::MessageWrap<Message>);
263  h->flag = app::hasMemoryAttachment::flag;
264  h->setSeq(it++.seq_);
265  }
266  }
267 
268  void queueMemorySegTrain(typename Buffer::iterator it, size_t n
269  , uint16_t tag, app::hasMemoryAttachment const& m, size_t len);
270 
271  template<typename M, typename ... Messages>
272  void queue(typename Buffer::iterator it
273  , M&& m, Messages&&... msgs) {
274  using Message = typename std::decay<M>::type;
275  static_assert(std::is_trivially_destructible<Message>::value, "cannot send message with dtor");
276  auto s = *it;
277  char* addr = static_cast<char*>(s);
278  auto h = new (addr) TransportMessageHeader;
279  if (hmbdc_likely(sizeof(Message) <= maxMessageSize_)) {
280  new (addr + sizeof(TransportMessageHeader)) app::MessageWrap<Message>(std::forward<M>(m));
281  h->messagePayloadLen = sizeof(app::MessageWrap<Message>);
282  h->setSeq(it.seq_);
283  } else {
284  HMBDC_THROW(std::out_of_range
285  , "maxMessageSize too small to hold a message when constructing SendTransportEngine");
286  }
287  if constexpr (has_hmbdc_net_queued_ts<Message>::value) {
288  h->template wrapped<Message>().hmbdc_net_queued_ts = hmbdc::time::SysTime::now();
289  }
290  queue(++it, std::forward<Messages>(msgs)...);
291  }
292 
293  void queue(Buffer::iterator it) {}
294 
295  void cullSlow();
296 };
297 
300 , hmbdc::app::Client<SendTransportEngine> {
302 
303  void rotate() {
304  if (runLock_.try_lock()) {
305  this->checkTimers(hmbdc::time::SysTime::now());
307  runLock_.unlock();
308  }
309  }
310 
311  /*virtual*/
312  void messageDispatchingStartedCb(size_t const*) override {
313  runLock_.lock();
314  }
315 
316 
317  /*virtual*/
318  void invokedCb(size_t) HMBDC_RESTRICT override {
319  this->runOnce();
320  }
321 
322  /*virtual*/
323  bool droppedCb() override {
324  stop();
325  return true;
326  };
327 
328  using EngineTransport::hmbdcName;
329 
330  std::tuple<char const*, int> schedSpec() const {
331  return std::make_tuple(this->schedPolicy_.c_str(), this->schedPriority_);
332  }
333 
335  this->runLock_.unlock();
336  }
337 
338 private:
339  std::mutex runLock_;
340 };
341 
342 } //sendtransportengine_detail
343 using SendTransport = sendtransportengine_detail::SendTransport;
344 using SendTransportEngine = sendtransportengine_detail::SendTransportEngine;
345 }}}
346 
347 namespace hmbdc { namespace tips { namespace rmcast {
348 
349 namespace sendtransportengine_detail {
350 
351 inline
354  , size_t maxMessageSize)
355 : EngineTransport((cfg.setAdditionalFallbackConfig(hmbdc::app::Config(DefaultUserConfig))
356  , cfg.resetSection("tx", false)))
357 , maxMessageSize_(maxMessageSize)
358 , buffer_(maxMessageSize + sizeof(TransportMessageHeader) + sizeof(app::MessageHead)
359  , outBufferSizePower2())
360 , rater_(hmbdc::time::Duration::seconds(1u)
361  , config_.getExt<size_t>("sendBytesPerSec")
362  , config_.getExt<size_t>("sendBytesBurst")
363  , config_.getExt<size_t>("sendBytesBurst") != 0ul)
364 , mcSendTransport_(config_, maxMessageSize, buffer_, rater_, toCleanupAttQueue_)
365 , minRecvToStart_(config_.getExt<size_t>("minRecvToStart"))
366 , typeTagAdTimer_(hmbdc::time::Duration::seconds(config_.getExt<uint32_t>("typeTagAdvertisePeriodSeconds")))
367 , lastBackupSeq_(std::numeric_limits<HMBDC_SEQ_TYPE>::max())
368 , flushTimer_(hmbdc::time::Duration::microseconds(config_.getExt<uint32_t>("netRoundtripLatencyMicrosec")))
369 , maxMemorySegPayloadSize_(std::min(mtu_ - sizeof(TransportMessageHeader) - sizeof(app::MessageHead)
370  , TransportMessageHeader::maxPayloadSize() - sizeof(app::MessageHead))
371 )
372 , waitForSlowReceivers_(config_.getExt<bool>("waitForSlowReceivers"))
373 , asyncBackupSendServer_(config_, buffer_, rater_, toCleanupAttQueue_, outboundSubscriptions_)
374 , stopped_(false) {
375  if (maxMessageSize_ > mtu_) {
376  HMBDC_THROW(std::out_of_range, "mtu needs to >= " << maxMessageSize_);
377  }
378 
379  if (maxMessageSize_ > TransportMessageHeader::maxPayloadSize()) {
380  HMBDC_THROW(std::out_of_range
381  , "maxMessageSize_ needs to <=" << TransportMessageHeader::maxPayloadSize());
382  }
383  auto sendBytesBurst = config_.getExt<size_t>("sendBytesBurst");
384  auto sendBytesBurstMin = maxMessageSize + sizeof(TransportMessageHeader);
385  if (sendBytesBurst && sendBytesBurst < sendBytesBurstMin) {
386  HMBDC_THROW(std::out_of_range, "sendBytesBurst needs to >= " << sendBytesBurstMin);
387  }
388 
389  toCleanupAttQueue_.set_capacity(buffer_.capacity() + 10);
390  typeTagAdTimer_.setCallback(
391  [this](hmbdc::time::TimerManager& tm, hmbdc::time::SysTime const& now) {
392  mcSendTransport_.setAdPending();
393  if (!waitForSlowReceivers_) cullSlow();
394  }
395  );
396  schedule(hmbdc::time::SysTime::now(), typeTagAdTimer_);
397 
398  flushTimer_.setCallback(
399  [this](hmbdc::time::TimerManager& tm, hmbdc::time::SysTime const& now) {
400  mcSendTransport_.setSeqAlertPending();
401  }
402  );
403  schedule(hmbdc::time::SysTime::now(), flushTimer_);
404 }
405 
406 inline
407 void
408 SendTransport::
409 stop() {
410  asyncBackupSendServer_.stop();
411  buffer_.reset(0);
412  buffer_.reset(1);
413  stopped_ = true;
414 };
415 
416 inline
417 uint16_t
418 SendTransport::
419 outBufferSizePower2() {
420  auto res = config_.getExt<uint16_t>("outBufferSizePower2");
421  if (res) {
422  return res;
423  }
424  res =hmbdc::numeric::log2Upper(128ul * 1024ul / (8ul + maxMessageSize_));
425  HMBDC_LOG_N("auto set --outBufferSizePower2=", res);
426  return res;
427 }
428 
429 inline
430 void
431 SendTransport::
432 queueMemorySegTrain(typename Buffer::iterator it, size_t n
433  , uint16_t tag, app::hasMemoryAttachment const& m, size_t len) {
434  //train head
435  auto s = *it;
436  char* addr = static_cast<char*>(s);
437  auto h = new (addr) TransportMessageHeader;
438  auto& trainHead = (new (addr + sizeof(TransportMessageHeader))
439  app::MessageWrap<app::StartMemorySegTrain>)->template get<app::StartMemorySegTrain>();
440  trainHead.inbandUnderlyingTypeTag = tag;
441  trainHead.segCount = n - 2;
442  trainHead.att = m;
443  h->messagePayloadLen = sizeof(app::MessageWrap<app::StartMemorySegTrain>);
444  h->setSeq(it++.seq_);
445 
446  //train body
447  {
448  auto totalLen = (size_t)m.len;
449  char* base = (char*)m.attachment;
450  decltype(totalLen) offset = 0;
451  while(totalLen > offset) {
452  auto s = *it;
453  char* addr = static_cast<char*>(s);
454  auto h = new (addr) TransportMessageHeader;
455  auto& seg = (new (addr + sizeof(TransportMessageHeader)) app::MessageWrap<app::MemorySeg>)
456  ->template get<app::MemorySeg>();
457  seg.seg = base + offset;
458  auto l = (uint16_t)std::min((size_t)maxMemorySegPayloadSize_, totalLen - offset);
459  seg.len = l;
460  seg.inbandUnderlyingTypeTag = tag;
461  h->messagePayloadLen = sizeof(app::MessageHead) + l; //skipped bytes
462  h->setSeq(it++.seq_);
463  offset += l;
464  }
465  }
466 
467  //actual message
468  {
469  auto s = *it;
470  char* addr = static_cast<char*>(s);
471  auto h = new (addr) TransportMessageHeader;
472  new (addr + sizeof(TransportMessageHeader)) app::MessageWrap<app::JustBytes>(
473  tag, &m, len, &m);
474  h->messagePayloadLen = sizeof(app::MessageHead) + len;
475  h->flag = app::hasMemoryAttachment::flag;
476  h->setSeq(it++.seq_);
477  }
478 }
479 
480 inline
481 void
482 SendTransport::
483 cullSlow() {
484  if (hmbdc_likely(minRecvToStart_ == std::numeric_limits<size_t>::max())) {
485  auto seq = buffer_.readSeq(1);
486  if (seq == lastBackupSeq_ && buffer_.isFull()) {
487  asyncBackupSendServer_.killSlowestSession();
488  }
489  lastBackupSeq_ = seq;
490  }
491 }
492 } //sendtransportengine_detail
493 }}}
494 
T getExt(const path_type &param, bool throwIfMissing=true) const
get a value from the config
Definition: Config.hpp:238
class to hold an hmbdc configuration
Definition: Config.hpp:44
void invokedCb(size_t) HMBDC_RESTRICT override
this callback is called all the time (frequently) - the exact timing is after a batch of messages are...
Definition: SendTransportEngine.hpp:318
Definition: TypedString.hpp:84
Definition: Timers.hpp:70
void messageDispatchingStartedCb(size_t const *) override
called before any messages got dispatched - only once
Definition: SendTransportEngine.hpp:312
SendTransport(hmbdc::app::Config cfg, size_t maxMessageSize)
ctor
Definition: SendTransportEngine.hpp:353
Definition: Message.hpp:212
Definition: Message.hpp:405
Definition: Time.hpp:14
bool droppedCb() override
callback called after the Client is safely taken out of the Context
Definition: SendTransportEngine.hpp:323
void schedule(SysTime fireAt, Timer &timer)
schedule the timer to start at a specific time
Definition: Timers.hpp:79
Definition: TypeTagSet.hpp:138
Definition: Message.hpp:263
a Node is a thread of execution that can suscribe and receive Messages
Definition: Node.hpp:51
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
Definition: TypeTagSet.hpp:198
Definition: Rater.hpp:11
Definition: Message.hpp:418
if a specific hmbdc network transport (for example tcpcast, rmcast, and rnetmap) supports message wit...
Definition: Message.hpp:125
Definition: Base.hpp:12
capture the transportation mechanism
Definition: SendTransportEngine.hpp:31
Definition: Timers.hpp:117