@@ -180,6 +180,7 @@ class TransportUDPBase {
180180 MAX_DATAGRAM_SIZE = 65507 , // https://stackoverflow.com/questions/42609561/udp-maximum-packet-size/42610200
181181 MAX_KEYS = 47 ,
182182 MAX_FULL_KEY_SIZE = 1024 , // roughly metric plus all tags
183+ MAX_HOST_TAG_LEN = 128 ,
183184 };
184185
185186 // Metric name builder. Use by calling transport.metric("metric").tag("tag1").tag("tag2").write_count(1);
@@ -197,9 +198,6 @@ class TransportUDPBase {
197198 }
198199 // Assigns arbitrary tag value by key name
199200 MetricBuilder & tag (string_view key, string_view str) {
200- if (key.size () == 2 && key.data ()[0 ] == ' _' && key.data ()[1 ] == ' h' ) {
201- host_set = true ;
202- }
203201 auto begin = buffer + buffer_pos;
204202 auto end = buffer + MAX_FULL_KEY_SIZE ;
205203 if (STATSHOUSE_UNLIKELY (!(begin = pack_string (begin, end, key)))) { did_not_fit = true ; return *this ; }
@@ -262,7 +260,6 @@ class TransportUDPBase {
262260 }
263261 TransportUDPBase *transport;
264262 bool env_set = false ; // so current default_env will be added in pack_header even if set later.
265- bool host_set = false ; // if explicit _h is set, do not add default hostname tag.
266263 bool did_not_fit = false ; // we do not want any exceptions in writing metrics
267264 size_t next_tag = 1 ;
268265 size_t tags_count = 0 ;
@@ -276,16 +273,10 @@ class TransportUDPBase {
276273 if (::gethostname (host_buf, sizeof (host_buf) - 1 ) == 0 ) {
277274 host_buf[sizeof (host_buf) - 1 ] = ' \0 ' ;
278275 host_tag.assign (host_buf);
276+ if (host_tag.size () > MAX_HOST_TAG_LEN ) {
277+ host_tag.resize (MAX_HOST_TAG_LEN );
278+ }
279279 }
280- if (host_tag.empty ()) {
281- return ;
282- }
283- host_tag_tl_pair.resize (4 + host_tag.size () + 4 ); // key name, host, padding
284- auto begin = &host_tag_tl_pair[0 ];
285- auto end = begin + host_tag_tl_pair.size ();
286- begin = pack32 (begin, end, 2 | (uint32_t (' _' ) << 8 ) | (uint32_t (' h' ) << 16 ));
287- begin = pack_string (begin, end, host_tag);
288- host_tag_tl_pair.resize (begin - &host_tag_tl_pair[0 ]);
289280 }
290281 virtual ~TransportUDPBase () = default ;
291282
@@ -401,6 +392,7 @@ class TransportUDPBase {
401392protected:
402393 Stats stats; // allow implementations to update stats directly for simplicity
403394 mutable mutex mu;
395+ std::string host_tag;
404396 bool write_count_locked (const MetricBuilder &metric, double count, uint32_t tsUnixSec = 0 ) {
405397 return write_count_impl (metric, count, tsUnixSec);
406398 }
@@ -430,8 +422,6 @@ class TransportUDPBase {
430422 std::string default_env;
431423 std::string default_env_tl_pair; // "0", default_env in TL format to optimized writing
432424 std::string app_name;
433- std::string host_tag;
434- std::string host_tag_tl_pair; // "_h", hostname in TL format;
435425
436426 size_t max_payload_size = DEFAULT_DATAGRAM_SIZE ;
437427 bool immediate_flush = false ;
@@ -616,19 +606,13 @@ class TransportUDPBase {
616606 if (STATSHOUSE_UNLIKELY (!enoughSpace (begin, end, metric.buffer_pos ))) { return nullptr ; }
617607 std::memcpy (begin, metric.buffer , metric.buffer_pos );
618608 const bool add_env = !metric.env_set && !default_env_tl_pair.empty ();
619- const bool add_host = !metric.host_set && !host_tag_tl_pair.empty ();
620- put32 (begin + metric.tags_count_pos (), metric.tags_count + int (add_env) + int (add_host));
609+ put32 (begin + metric.tags_count_pos (), metric.tags_count + int (add_env));
621610 begin += metric.buffer_pos ;
622611 if (add_env) {
623612 if (STATSHOUSE_UNLIKELY (!enoughSpace (begin, end, default_env_tl_pair.size ()))) { return nullptr ; }
624613 std::memcpy (begin, default_env_tl_pair.data (), default_env_tl_pair.size ());
625614 begin += default_env_tl_pair.size ();
626615 }
627- if (add_host) {
628- if (STATSHOUSE_UNLIKELY (!enoughSpace (begin, end, host_tag_tl_pair.size ()))) { return nullptr ; }
629- std::memcpy (begin, host_tag_tl_pair.data (), host_tag_tl_pair.size ());
630- begin += host_tag_tl_pair.size ();
631- }
632616 if (fields_mask & TL_STATSHOUSE_METRIC_COUNTER_FIELDS_MASK ) {
633617 if (STATSHOUSE_UNLIKELY (!(begin = pack64 (begin, end, doubleBits (counter))))) { return nullptr ;}
634618 }
@@ -901,7 +885,8 @@ class TransportTCP : public TransportUDPBase {
901885 TransportTCP (const std::string &host, int port, const std::string &resolve_targets = " " )
902886 : host_{host}
903887 , resolve_targets_{resolve_targets}
904- , port_{port} {
888+ , port_{port}
889+ , handshake_(build_tcp_handshake_v2(host_tag)) {
905890 (void )refresh_pools ();
906891 primary_worker_.th = std::thread (&TransportTCP::run_worker, this , &primary_worker_);
907892 secondary_worker_.th = std::thread (&TransportTCP::run_worker, this , &secondary_worker_);
@@ -938,7 +923,10 @@ class TransportTCP : public TransportUDPBase {
938923 }
939924
940925private:
941- enum { tcpConnBucketCount = 512 };
926+ enum {
927+ tcpConnBucketCount = 512 ,
928+ stuckReconDelaySec = 30 ,
929+ };
942930 struct resolved_addr {
943931 ::sockaddr_storage storage{};
944932 socklen_t len = 0 ;
@@ -952,6 +940,8 @@ class TransportTCP : public TransportUDPBase {
952940 int sock = -1 ;
953941 std::thread th;
954942 std::atomic<size_t > would_block_bytes{0 };
943+ std::atomic_bool force_reconnect{false };
944+ std::chrono::steady_clock::time_point last_stuck_recon{std::chrono::steady_clock::now ()};
955945 };
956946 struct send_result {
957947 bool ok = false ;
@@ -962,6 +952,7 @@ class TransportTCP : public TransportUDPBase {
962952 std::string host_;
963953 std::string resolve_targets_;
964954 int port_ = 0 ;
955+ std::string handshake_; // precomputed reconnect key (statshousev2 + host)
965956 worker primary_worker_;
966957 worker secondary_worker_;
967958 std::mutex route_mu_;
@@ -996,6 +987,11 @@ class TransportTCP : public TransportUDPBase {
996987 return flush_packet_result::ok;
997988 }
998989 if (enqueue (*secondary, payload)) {
990+ primary->force_reconnect .store (true , std::memory_order_relaxed);
991+ {
992+ std::lock_guard<std::mutex> lock{primary->mu };
993+ primary->cv .notify_one ();
994+ }
999995 std::lock_guard<std::mutex> lock{route_mu_};
1000996 std::swap (primary_route_, secondary_route_);
1001997 return flush_packet_result::ok;
@@ -1017,21 +1013,47 @@ class TransportTCP : public TransportUDPBase {
10171013 void run_worker (worker *w) {
10181014 for (;;) {
10191015 std::vector<char > payload;
1016+ bool have_payload = false ;
1017+ bool need_recon = false ;
10201018 {
10211019 std::unique_lock<std::mutex> lock{w->mu };
1022- w->cv .wait (lock, [this , w]() { return stop_.load () || !w->queue .empty (); });
1023- if (w->queue .empty () && stop_.load ()) {
1020+ w->cv .wait (lock, [this , w]() {
1021+ return stop_.load () || !w->queue .empty () || w->force_reconnect .load (std::memory_order_relaxed);
1022+ });
1023+ if (w->force_reconnect .exchange (false , std::memory_order_relaxed)) {
1024+ need_recon = true ;
1025+ }
1026+ if (!w->queue .empty ()) {
1027+ payload = std::move (w->queue .front ());
1028+ w->queue .pop_front ();
1029+ have_payload = true ;
1030+ } else if (stop_.load ()) {
10241031 break ;
10251032 }
1026- payload = std::move (w->queue .front ());
1027- w->queue .pop_front ();
10281033 }
1029- send_payload_with_reconnect (*w, payload);
1034+ if (need_recon) {
1035+ maybe_stuck_reconnect (*w);
1036+ }
1037+ if (have_payload) {
1038+ send_payload_with_reconnect (*w, payload);
1039+ }
10301040 }
10311041 }
10321042
1043+ void maybe_stuck_reconnect (worker &w) {
1044+ const auto now = std::chrono::steady_clock::now ();
1045+ if (now < w.last_stuck_recon + std::chrono::seconds (stuckReconDelaySec)) {
1046+ return ;
1047+ }
1048+ close_socket (w.sock );
1049+ w.last_stuck_recon = now;
1050+ }
1051+
10331052 void send_payload_with_reconnect (worker &w, const std::vector<char > &payload) {
10341053 for (;;) {
1054+ if (w.force_reconnect .exchange (false , std::memory_order_relaxed)) {
1055+ maybe_stuck_reconnect (w);
1056+ }
10351057 if (w.sock < 0 && !reconnect (w)) {
10361058 if (stop_.load ()) {
10371059 return ;
@@ -1107,8 +1129,7 @@ class TransportTCP : public TransportUDPBase {
11071129 set_errno_error (err, " statshouse::TransportTCP connect() failed" );
11081130 return false ;
11091131 }
1110- static const char kHeader [] = " statshousev1" ;
1111- const send_result header_write = send_all (sock, kHeader , sizeof (kHeader ) - 1 );
1132+ const send_result header_write = send_all (sock, handshake_.data (), handshake_.size ());
11121133 if (!header_write.ok ) {
11131134 int err = header_write.err ;
11141135 ::close (sock);
@@ -1241,10 +1262,27 @@ class TransportTCP : public TransportUDPBase {
12411262 kb.tag (" 1" , " 5" ); // lang: cpp
12421263 kb.tag (" 2" , " 1" ); // kind: would block
12431264 kb.tag (" 3" , app_name_locked ());
1265+ if (!host_tag.empty ()) {
1266+ kb.tag (" _h" , host_tag);
1267+ }
12441268 const double lost = static_cast <double >(n);
12451269 (void )write_values_locked (kb, &lost, 1 , 0 , 0 );
12461270 }
12471271
1272+ static std::string build_tcp_handshake_v2 (const std::string &host_tag) {
1273+ std::string buf;
1274+ buf.reserve (11 + 1 + 4 + host_tag.size ());
1275+ buf.append (" statshousev" );
1276+ buf.push_back (' 2' );
1277+ const uint32_t n = static_cast <uint32_t >(host_tag.size ());
1278+ buf.push_back (static_cast <char >(n));
1279+ buf.push_back (static_cast <char >(n >> 8 ));
1280+ buf.push_back (static_cast <char >(n >> 16 ));
1281+ buf.push_back (static_cast <char >(n >> 24 ));
1282+ buf.append (host_tag);
1283+ return buf;
1284+ }
1285+
12481286 void set_errno_error (int err, const char *msg) {
12491287 stats.last_error .clear ();
12501288 stats.last_error .append (msg);
0 commit comments