Aclk improvements (#20775)
* Code cleanup * Collect packets that have time out then do cleanup * No need to run cleanup on demand * Add a periodic cleanup check_packet_monitor_list_for_timeouts return true of max cleanup was performed Keep stats of pending packets waiting puback * Replace mqtt_wss_now_usec with now_monotonic_usec * Cleanup packet timeput monitor list on disconnection and clean reconnection -- reset statistics as well Keep chart of puback wait messages * Additional statistics in aclk-state * Set timestamp on query creation * Query threads and MQTT ACK processing now acquire locks in the same order (pending_packets → main_buffer) to prevent AB-BA deadlock.
Stelios Fragkakis committed
Aug 7, 2025 at 16:59 UTC
b004542ceed26e9c883dff1c4efde5a0e4e29c47
7 files changed
+94
-48
src/aclk/aclk.c
+25
-5
@@ -1089,7 +1089,13 @@ char *aclk_state(void)
1089
buffer_sprintf(wb, "Yes\nClaimed Id: %s\nCloud URL: %s\nACLK Proxy: %s\nPublish Latency: %s\n", claim_id.str, cloud_base_url ? cloud_base_url : "null", aclk_proxy ? aclk_proxy : "none", latency_str);
1090
}
1091
1092
- buffer_sprintf(wb, "Online: %s\nReconnect count: %d\nBanned By Cloud: %s\n", aclk_online() ? "Yes" : "No", aclk_connection_counter > 0 ? (aclk_connection_counter - 1) : 0, aclk_disable_runtime ? "Yes" : "No");
1092
+ bool aclk_is_online = aclk_online();
1093
+
1094
+ struct mqtt_wss_stats aclk_stats;
1095
+ if (aclk_is_online)
1096
+ aclk_stats = aclk_statistics();
1097
+
1098
+ buffer_sprintf(wb, "Online: %s\nReconnect count: %d\nBanned By Cloud: %s\n", aclk_is_online ? "Yes" : "No", aclk_connection_counter > 0 ? (aclk_connection_counter - 1) : 0, aclk_disable_runtime ? "Yes" : "No");
1099
if (last_conn_time_mqtt && ((tmptr = localtime_r(&last_conn_time_mqtt, &tmbuf))) ) {
1100
char timebuf[26];
1101
strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr);
@@ -1111,8 +1117,9 @@ char *aclk_state(void)
1117
buffer_sprintf(wb, "Next Connection Attempt At: %s\nLast Backoff: %.3f", timebuf, last_backoff_value);
1118
}
1119
1114
- if (aclk_online()) {
1115
- buffer_sprintf(wb, "Received Cloud MQTT Messages: %d\nMQTT Messages Confirmed by Remote Broker (PUBACKs): %d", aclk_rcvd_cloud_msgs, aclk_pubacks_per_conn);
1120
+ if (aclk_is_online) {
1121
+ buffer_sprintf(wb, "Received Cloud MQTT Messages: %d\nMQTT Messages Confirmed by Remote Broker (PUBACKs): %d\nPending PUBACKS: %d\n",
1122
+ aclk_rcvd_cloud_msgs, aclk_pubacks_per_conn, aclk_stats.mqtt.packets_waiting_puback);
1123
1124
RRDHOST *host;
1125
rrd_rdlock();
@@ -1186,6 +1193,16 @@ static json_object *timestamp_to_json(const time_t *t)
1193
1194
char *aclk_state_json(void)
1195
{
1196
+
1197
+ bool aclk_is_online = aclk_online();
1198
+
1199
+ struct mqtt_wss_stats aclk_stats;
1200
+
1201
+ if (aclk_is_online)
1202
+ aclk_stats = aclk_statistics();
1203
+ else
1204
+ memset(&aclk_stats, 0, sizeof(aclk_stats));
1205
+
1206
json_object *tmp, *grp, *msg = json_object_new_object();
1207
1208
tmp = json_object_new_boolean(1);
@@ -1221,7 +1238,7 @@ char *aclk_state_json(void)
1238
tmp =json_object_new_int64((int64_t) latency);
1239
json_object_object_add(msg, "publish_latency_us", tmp);
1240
1224
- tmp = json_object_new_boolean(aclk_online());
1241
+ tmp = json_object_new_boolean(aclk_is_online);
1242
json_object_object_add(msg, "online", tmp);
1243
1244
tmp = json_object_new_string("Protobuf");
@@ -1236,13 +1253,16 @@ char *aclk_state_json(void)
1253
tmp = json_object_new_int(aclk_pubacks_per_conn);
1254
json_object_object_add(msg, "received-mqtt-pubacks", tmp);
1255
1256
+ tmp = json_object_new_int((int32_t) aclk_stats.mqtt.packets_waiting_puback);
1257
+ json_object_object_add(msg, "pending-mqtt-pubacks", tmp);
1258
+
1259
tmp = json_object_new_int(aclk_connection_counter > 0 ? (aclk_connection_counter - 1) : 0);
1260
json_object_object_add(msg, "reconnect-count", tmp);
1261
1262
json_object_object_add(msg, "last-connect-time-utc", timestamp_to_json(&last_conn_time_mqtt));
1263
json_object_object_add(msg, "last-connect-time-puback-utc", timestamp_to_json(&last_conn_time_appl));
1264
json_object_object_add(msg, "last-disconnect-time-utc", timestamp_to_json(&last_disconnect_time));
1245
- json_object_object_add(msg, "next-connection-attempt-utc", !aclk_online() ? timestamp_to_json(&next_connection_attempt) : NULL);
1265
+ json_object_object_add(msg, "next-connection-attempt-utc", !aclk_is_online ? timestamp_to_json(&next_connection_attempt) : NULL);
1266
tmp = NULL;
1267
if (!aclk_online() && last_backoff_value)
1268
tmp = json_object_new_double(last_backoff_value);
src/aclk/aclk_query_queue.c
+1
@@ -58,6 +58,7 @@ aclk_query_t *aclk_query_new(aclk_query_type_t type)
58
{
59
aclk_query_t *query = get_query();
60
query->type = type;
61
+ now_monotonic_high_precision_timeval(&query->created_tv);
62
return query;
63
}
64
src/aclk/aclk_rx_msgs.c
+2
-5
@@ -233,12 +233,9 @@ int handle_old_proto_cmd(const char *msg, size_t msg_len)
233
char *str = mallocz(msg_len+1);
234
memcpy(str, msg, msg_len);
235
str[msg_len] = 0;
236
- if (aclk_handle_cloud_cmd_message(str)) {
237
- freez(str);
238
- return 1;
239
- }
236
+ int rc = aclk_handle_cloud_cmd_message(str);
237
freez(str);
241
- return 0;
238
+ return rc;
239
}
240
241
int create_node_instance_result(const char *msg, size_t msg_len)
src/aclk/mqtt_websockets/common_public.h
+1
@@ -25,6 +25,7 @@ struct mqtt_ng_stats {
25
int tx_messages_queued;
26
int tx_messages_sent;
27
int rx_messages_rcvd;
28
+ int packets_waiting_puback;
29
size_t tx_buffer_used;
30
size_t tx_buffer_free;
31
size_t tx_buffer_size;
src/aclk/mqtt_websockets/mqtt_ng.c
+54
-19
@@ -658,15 +658,21 @@ static void mqtt_ng_destroy_tx_alias_hash(c_rhash hash)
658
c_rhash_destroy(hash);
659
}
660
661
+static void destroy_timeout_monitor_list(struct mqtt_ng_client *client)
662
+{
663
+ spinlock_lock(&client->pending_packets.spinlock);
664
+ (void) JudyLFreeArray(&client->pending_packets.JudyL, PJE0);
665
+ spinlock_unlock(&client->pending_packets.spinlock);
666
+ __atomic_store_n(&client->stats.packets_waiting_puback, 0, __ATOMIC_RELAXED);
667
+}
668
+
669
void mqtt_ng_destroy(struct mqtt_ng_client *client)
670
{
671
transaction_buffer_destroy(&client->main_buffer);
672
673
mqtt_ng_destroy_tx_alias_hash(client->tx_topic_aliases.stoi_dict);
674
mqtt_ng_destroy_rx_alias_hash(client->rx_aliases);
667
- spinlock_lock(&client->pending_packets.spinlock);
668
- (void) JudyLFreeArray(&client->pending_packets.JudyL, PJE0);
669
- spinlock_unlock(&client->pending_packets.spinlock);
675
+ destroy_timeout_monitor_list(client);
676
freez(client);
677
}
678
@@ -798,11 +804,12 @@ static int optimized_add(struct header_buffer *buf, void *data, size_t data_len,
804
return 0;
805
}
806
801
-static void remove_packet_from_timeout_monitor_list(struct mqtt_ng_client *client, uint16_t packet_id)
807
+static void remove_packet_from_timeout_monitor_list_unsafe(struct mqtt_ng_client *client, uint16_t packet_id)
808
{
803
- spinlock_lock(&client->pending_packets.spinlock);
804
- (void) JudyLDel(&client->pending_packets.JudyL, (Word_t) packet_id, PJE0);
805
- spinlock_unlock(&client->pending_packets.spinlock);
809
+ int rc = JudyLDel(&client->pending_packets.JudyL, (Word_t) packet_id, PJE0);
810
+ // rc = 1 if the packer was deleted, so update statistics
811
+ if (likely(rc))
812
+ __atomic_fetch_sub(&client->stats.packets_waiting_puback, 1, __ATOMIC_RELAXED);
813
}
814
815
#define PACKET_TIMEOUT_EPOCH (1704067200L) // Jan 1, 2024 00:00:00 UTC
@@ -813,13 +820,15 @@ static void add_packet_to_timeout_monitor_list(struct mqtt_ng_client *client, ui
820
time_t now = now_realtime_sec();
821
// Add it to the JudyL array
822
uint32_t *Pvalue = (uint32_t *) JudyLIns(&client->pending_packets.JudyL, (Word_t) packet_id, PJE0);
816
- if (!Pvalue || Pvalue == PJERR) {
823
+ if (Pvalue == PJERR) {
824
nd_log(NDLS_DAEMON, NDLP_ERR, "Error inserting packet_id (%" PRIu16 ") into JudyL array.", packet_id);
825
spinlock_unlock(&client->pending_packets.spinlock);
826
return;
827
}
828
*Pvalue = (uint32_t) ((now - PACKET_TIMEOUT_EPOCH) + PACKET_ACK_TIMEOUT_SECS);
829
spinlock_unlock(&client->pending_packets.spinlock);
830
+
831
+ __atomic_fetch_add(&client->stats.packets_waiting_puback, 1, __ATOMIC_RELAXED);
832
}
833
834
#define TRY_GENERATE_MESSAGE(generator_function, ...) \
@@ -1016,6 +1025,9 @@ int mqtt_ng_connect(struct mqtt_ng_client *client,
1025
buffer_purge(&client->main_buffer.hdr_buffer);
1026
UNLOCK_HDR_BUFFER(&client->main_buffer);
1027
1028
+ if (clean_start)
1029
+ destroy_timeout_monitor_list(client);
1030
+
1031
spinlock_lock(&client->tx_topic_aliases.spinlock);
1032
// according to MQTT spec topic aliases should not be persisted
1033
// even if clean session is true
@@ -1159,6 +1171,7 @@ static void mark_message_for_gc(struct buffer_fragment *frag)
1171
static int mark_packet_acked(struct mqtt_ng_client *client, uint16_t packet_id)
1172
{
1173
size_t reclaimable = 0;
1174
+ spinlock_lock(&client->pending_packets.spinlock);
1175
LOCK_HDR_BUFFER(&client->main_buffer);
1176
struct buffer_fragment *frag = BUFFER_FIRST_FRAG(&client->main_buffer.hdr_buffer);
1177
while (frag) {
@@ -1166,6 +1179,7 @@ static int mark_packet_acked(struct mqtt_ng_client *client, uint16_t packet_id)
1179
if (!frag->sent) {
1180
nd_log(NDLS_DAEMON, NDLP_ERR, "Received packet_id (%" PRIu16 ") belongs to MQTT packet which was not yet sent!", packet_id);
1181
UNLOCK_HDR_BUFFER(&client->main_buffer);
1182
+ spinlock_unlock(&client->pending_packets.spinlock);
1183
return 1;
1184
}
1185
usec_t latency = now_monotonic_usec() - frag->sent_monotonic_ut;
@@ -1178,7 +1192,8 @@ static int mark_packet_acked(struct mqtt_ng_client *client, uint16_t packet_id)
1192
transaction_buffer_garbage_collect(&client->main_buffer);
1193
1194
UNLOCK_HDR_BUFFER(&client->main_buffer);
1181
- remove_packet_from_timeout_monitor_list(client, packet_id);
1195
+ remove_packet_from_timeout_monitor_list_unsafe(client, packet_id);
1196
+ spinlock_unlock(&client->pending_packets.spinlock);
1197
return 0;
1198
}
1199
@@ -1189,25 +1204,40 @@ static int mark_packet_acked(struct mqtt_ng_client *client, uint16_t packet_id)
1204
}
1205
nd_log(NDLS_DAEMON, NDLP_ERR, "Received packet_id (%" PRIu16 ") is unknown!", packet_id);
1206
UNLOCK_HDR_BUFFER(&client->main_buffer);
1207
+ spinlock_unlock(&client->pending_packets.spinlock);
1208
return 1;
1209
}
1210
1195
-static void check_packet_monitor_list_for_timeouts(struct mqtt_ng_client *client)
1211
+#define MAX_TIMED_OUT_PACKETS (1024)
1212
+
1213
+static bool check_packet_monitor_list_for_timeouts(struct mqtt_ng_client *client)
1214
{
1215
+ uint16_t timed_out_packets[MAX_TIMED_OUT_PACKETS];
1216
+ size_t timed_out_count = 0;
1217
+
1218
spinlock_lock(&client->pending_packets.spinlock);
1219
bool first_then_next = true;
1220
uint32_t *Pvalue;
1221
Word_t packet_id = 0;
1222
time_t now = now_realtime_sec();
1223
+
1224
while ((Pvalue = (uint32_t *) JudyLFirstThenNext(client->pending_packets.JudyL, &packet_id, &first_then_next))) {
1225
uint32_t expire_time_delta = *Pvalue;
1226
if (now >= (PACKET_TIMEOUT_EPOCH + expire_time_delta)) {
1205
- spinlock_unlock(&client->pending_packets.spinlock);
1206
- (void) mark_packet_acked(client, (uint16_t) packet_id);
1207
- spinlock_lock(&client->pending_packets.spinlock);
1227
+ if (timed_out_count < MAX_TIMED_OUT_PACKETS) {
1228
+ timed_out_packets[timed_out_count++] = (uint16_t)packet_id;
1229
+ } else
1230
+ break;
1231
}
1232
}
1233
spinlock_unlock(&client->pending_packets.spinlock);
1234
+
1235
+ // Process timeouts outside the lock
1236
+ for (size_t i = 0; i < timed_out_count; i++) {
1237
+ mark_packet_acked(client, timed_out_packets[i]);
1238
+ }
1239
+
1240
+ return (timed_out_count == MAX_TIMED_OUT_PACKETS);
1241
}
1242
1243
#define PUBLISH_SP_SIZE 64
@@ -1244,11 +1274,6 @@ int mqtt_ng_publish(struct mqtt_ng_client *client,
1274
}
1275
1276
int rc = TRY_GENERATE_MESSAGE(mqtt_ng_generate_publish, topic, topic_free, msg, msg_free, msg_len, publish_flags, packet_id, topic_id);
1247
- if (rc == MQTT_NG_MSGGEN_BUFFER_OOM) {
1248
- check_packet_monitor_list_for_timeouts(client);
1249
- rc = TRY_GENERATE_MESSAGE(mqtt_ng_generate_publish, topic, topic_free, msg, msg_free, msg_len, publish_flags, packet_id, topic_id);
1250
- }
1251
-
1277
if (rc == MQTT_NG_MSGGEN_OK)
1278
add_packet_to_timeout_monitor_list(client, *packet_id);
1279
return rc;
@@ -1930,7 +1955,6 @@ static int parse_data(struct mqtt_ng_client *client)
1955
}
1956
parser->state = MQTT_PARSE_MQTT_PACKET_DONE;
1957
ping_timeout = 0;
1933
- check_packet_monitor_list_for_timeouts(client);
1958
break;
1959
case MQTT_CPT_DISCONNECT:
1960
rc = parse_disconnect_varhdr(client);
@@ -2171,6 +2195,8 @@ int handle_incoming_traffic(struct mqtt_ng_client *client)
2195
return rc;
2196
}
2197
2198
+#define PACKET_TIMEOUT_REPEAT_CHECK (60)
2199
+
2200
int mqtt_ng_sync(struct mqtt_ng_client *client)
2201
{
2202
if (client->client_state == MQTT_STATE_RAW || client->client_state == MQTT_STATE_DISCONNECTED)
@@ -2179,6 +2205,15 @@ int mqtt_ng_sync(struct mqtt_ng_client *client)
2205
if (client->client_state == MQTT_STATE_ERROR)
2206
return 1;
2207
2208
+ // Check for packet timeouts and cleanup
2209
+ static time_t last_maintenance = 0;
2210
+ if (now_realtime_sec() - last_maintenance >= PACKET_TIMEOUT_REPEAT_CHECK) {
2211
+ // if check packet returns true then we did max cleanup, possibly there are more packets to cleanup
2212
+ // so do not update last_maintenance thus forcing check again
2213
+ if (likely(!check_packet_monitor_list_for_timeouts(client)))
2214
+ last_maintenance = now_realtime_sec();
2215
+ }
2216
+
2217
worker_is_busy(WORKER_ACLK_TRY_SEND_ALL);
2218
2219
LOCK_HDR_BUFFER(&client->main_buffer);
src/aclk/mqtt_websockets/mqtt_wss_client.c
+8
-19
@@ -754,17 +754,6 @@ static int t_till_next_keepalive_ms(mqtt_wss_client client)
754
return timeout_ms;
755
}
756
757
-#ifdef MQTT_WSS_CPUSTATS
758
-static uint64_t mqtt_wss_now_usec(void) {
759
- struct timespec ts;
760
- if(clock_gettime(CLOCK_MONOTONIC, &ts) == -1) {
761
- nd_log(NDLS_DAEMON, NDLP_ERR, "clock_gettime(CLOCK_MONOTONIC, ×pec) failed.");
762
- return 0;
763
- }
764
- return (uint64_t)ts.tv_sec * USEC_PER_SEC + (ts.tv_nsec % NSEC_PER_SEC) / NSEC_PER_USEC;
765
-}
766
-#endif
767
-
757
int mqtt_wss_service(mqtt_wss_client client, int timeout_ms)
758
{
759
char *ptr;
@@ -774,7 +763,7 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms)
763
764
#ifdef MQTT_WSS_CPUSTATS
765
uint64_t t2;
777
- uint64_t t1 = mqtt_wss_now_usec();
766
+ uint64_t t1 = now_monotonic_usec();
767
#endif
768
769
// Check user requested TO doesn't interfere with MQTT keep alives
@@ -787,7 +776,7 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms)
776
}
777
778
#ifdef MQTT_WSS_CPUSTATS
790
- t2 = mqtt_wss_now_usec();
779
+ t2 = now_monotonic_usec();
780
client->stats.time_keepalive += t2 - t1;
781
#endif
782
@@ -805,7 +794,7 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms)
794
worker_is_busy(WORKER_ACLK_POLL_OK);
795
796
#ifdef MQTT_WSS_CPUSTATS
808
- t1 = mqtt_wss_now_usec();
797
+ t1 = now_monotonic_usec();
798
#endif
799
800
if (ret == 0) {
@@ -828,7 +817,7 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms)
817
}
818
819
#ifdef MQTT_WSS_CPUSTATS
831
- t2 = mqtt_wss_now_usec();
820
+ t2 = now_monotonic_usec();
821
client->stats.time_keepalive += t2 - t1;
822
#endif
823
@@ -866,7 +855,7 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms)
855
}
856
857
#ifdef MQTT_WSS_CPUSTATS
869
- t1 = mqtt_wss_now_usec();
858
+ t1 = now_monotonic_usec();
859
client->stats.time_read_socket += t1 - t2;
860
#endif
861
@@ -890,7 +879,7 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms)
879
}
880
881
#ifdef MQTT_WSS_CPUSTATS
893
- t2 = mqtt_wss_now_usec();
882
+ t2 = now_monotonic_usec();
883
client->stats.time_process_websocket += t2 - t1;
884
#endif
885
@@ -907,7 +896,7 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms)
896
}
897
898
#ifdef MQTT_WSS_CPUSTATS
910
- t1 = mqtt_wss_now_usec();
899
+ t1 = now_monotonic_usec();
900
client->stats.time_process_mqtt += t1 - t2;
901
#endif
902
@@ -945,7 +934,7 @@ int mqtt_wss_service(mqtt_wss_client client, int timeout_ms)
934
util_clear_pipe(client->write_notif_pipe[PIPE_READ_END]);
935
936
#ifdef MQTT_WSS_CPUSTATS
948
- t2 = mqtt_wss_now_usec();
937
+ t2 = now_monotonic_usec();
938
client->stats.time_write_socket += t2 - t1;
939
#endif
940
src/daemon/pulse/pulse-network.c
+3
@@ -315,6 +315,7 @@ void pulse_network_do(bool extended __maybe_unused) {
315
if(extended) {
316
static RRDSET *st_aclk_queue_size = NULL;
317
static RRDDIM *rd_messages = NULL;
318
+ static RRDDIM *rd_puback_wait = NULL;
319
320
if (unlikely(!st_aclk_queue_size)) {
321
st_aclk_queue_size = rrdset_create_localhost(
@@ -334,9 +335,11 @@ void pulse_network_do(bool extended __maybe_unused) {
335
rrdlabels_add(st_aclk_queue_size->rrdlabels, "endpoint", "aclk", RRDLABEL_SRC_AUTO);
336
337
rd_messages = rrddim_add(st_aclk_queue_size, "messages", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
338
+ rd_puback_wait = rrddim_add(st_aclk_queue_size, "puback wait", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
339
}
340
341
rrddim_set_by_pointer(st_aclk_queue_size, rd_messages, (collected_number)t.mqtt.tx_messages_queued);
342
+ rrddim_set_by_pointer(st_aclk_queue_size, rd_puback_wait, (collected_number)t.mqtt.packets_waiting_puback);
343
rrdset_done(st_aclk_queue_size);
344
}
345