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