Skip to content

Commit a40af2f

Browse files
Check that all are reassembled before closing (#128)
* Check that all fragments are reassembled before closing change WPC close order, add a check to fragment list. Adapt gw-example to do clean exit * Handle fragment only in dispatch task remove garbage collect from polling task, call periodically garbage collect from dispatch garbage collect still called on frag received for compatibility
1 parent 7fddd04 commit a40af2f

File tree

9 files changed

+229
-79
lines changed

9 files changed

+229
-79
lines changed

Diff for: example/linux/gw-example/main.c

+38-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
#include <getopt.h>
8+
#include <signal.h>
89
#include <stdio.h>
910
#include <stdlib.h>
1011
#include <string.h>
@@ -43,6 +44,10 @@ static pthread_t m_thread_publish;
4344
static pthread_mutex_t m_pub_queue_mutex;
4445
static pthread_cond_t m_pub_queue_not_empty_cond = PTHREAD_COND_INITIALIZER;
4546

47+
static char topic_all_requests[16 + sizeof(m_gateway_id)]; //"gw-request/+/<gateway_id/#"
48+
49+
static volatile bool running = true;
50+
4651
// Statically allocated but could be mallocated
4752
typedef struct
4853
{
@@ -69,6 +74,11 @@ static bool m_pub_queue_empty = true;
6974

7075
static MQTTClient m_client = NULL;
7176

77+
static void signal_handler(int signum)
78+
{
79+
running = false;
80+
}
81+
7282
static bool MQTT_publish(char * topic, uint8_t * payload, size_t payload_size, bool retained)
7383
{
7484
message_to_publish_t * message_p;
@@ -242,7 +252,6 @@ static bool reconnect(uint32_t timeout_s)
242252
int rc;
243253
size_t proto_size;
244254
char topic_status[sizeof(TOPIC_EVENT_PREFIX) + sizeof(m_gateway_id) + 1];
245-
char topic_all_requests[16 + sizeof(m_gateway_id)]; //"gw-request/+/<gateway_id/#"
246255
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
247256
MQTTClient_willOptions will_options = MQTTClient_willOptions_initializer;
248257
MQTTClient_SSLOptions ssl_options = MQTTClient_SSLOptions_initializer;
@@ -289,11 +298,6 @@ static bool reconnect(uint32_t timeout_s)
289298
TOPIC_EVENT_PREFIX,
290299
m_gateway_id);
291300

292-
snprintf(topic_all_requests,
293-
sizeof(topic_all_requests),
294-
"gw-request/+/%s/#",
295-
m_gateway_id);
296-
297301
while (timeout_s > 0)
298302
{
299303
if ((rc = MQTTClient_connect(m_client, &conn_opts)) == MQTTCLIENT_SUCCESS)
@@ -331,6 +335,17 @@ static bool reconnect(uint32_t timeout_s)
331335
return true;
332336
}
333337

338+
static bool mqtt_unsubscribe_topics(void)
339+
{
340+
if (MQTTClient_unsubscribe(m_client, topic_all_requests) != MQTTCLIENT_SUCCESS)
341+
{
342+
LOGE("Failed to unsubscribe from topic %s\n", topic_all_requests);
343+
return false;
344+
}
345+
LOGI("Successfully unsubscribed from topic %s\n", topic_all_requests);
346+
return true;
347+
}
348+
334349
static void on_mqtt_connection_lost(void *context, char *cause)
335350
{
336351
LOGE("Connection lost\n");
@@ -345,6 +360,8 @@ static bool MQTT_connect(uint32_t timeout_s,
345360

346361
MQTTClient_init_options global_init_options = MQTTClient_init_options_initializer;
347362
global_init_options.do_openssl_init = true;
363+
364+
snprintf(topic_all_requests, sizeof(topic_all_requests), "gw-request/+/%s/#", m_gateway_id);
348365

349366
MQTTClient_global_init(&global_init_options);
350367

@@ -434,6 +451,9 @@ int main(int argc, char * argv[])
434451
pthread_mutexattr_init(&attr);
435452
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
436453

454+
signal(SIGINT, signal_handler);
455+
signal(SIGTERM, signal_handler);
456+
437457
// Parse the arguments
438458
static struct option long_options[]
439459
= { { "baudrate", required_argument, 0, 'b' },
@@ -540,10 +560,20 @@ int main(int argc, char * argv[])
540560

541561
LOGI("Starting gw with id %s on host %s\n", gateway_id, mqtt_host);
542562

543-
for (;;)
563+
while (running)
544564
{
545565
sleep(2);
546566
}
547567

548-
LOGE("End of program\n");
568+
LOGI("Clean exit requested\n");
569+
mqtt_unsubscribe_topics();
570+
WPC_Proto_close();
571+
if (MQTTClient_disconnect(m_client, 10000) != MQTTCLIENT_SUCCESS)
572+
{
573+
LOGE("MQTT failed to disconnect\n");
574+
}
575+
LOGI("MQTT disconnected\n");
576+
MQTTClient_destroy(&m_client);
577+
pthread_mutex_destroy(&m_pub_queue_mutex);
578+
LOGI("Clean exit completed\n");
549579
}

Diff for: lib/api/wpc_proto.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@
1010
#include <stddef.h>
1111
#include <stdint.h>
1212

13+
/**
14+
* Max default delay to keep incomplete fragmented packet inside our buffers
15+
*/
16+
#define FRAGMENT_MAX_DURATION_S 45
1317

1418
/**
1519
* Max possible overhead estimation for wp_GenericMessage
1620
* compared to specific single message. Should be added to
1721
* size of single message to estimate the max sized occupied
18-
* by the full proto encoded message */
22+
* by the full proto encoded message
23+
*/
1924
#define WPC_PROTO_GENERIC_MESSAGE_OVERHEAD 20
2025

2126
/*

Diff for: lib/platform/linux/platform.c

+74-17
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* See file LICENSE for full license details.
44
*
55
*/
6+
#include <errno.h>
67
#include <stdbool.h>
78
#include <stdlib.h>
89
#include <pthread.h>
@@ -14,21 +15,32 @@
1415
#define MAX_LOG_LEVEL INFO_LOG_LEVEL
1516
#include "logger.h"
1617
#include "platform.h"
18+
#include "reassembly.h"
19+
#include "wpc_proto.h"
1720

1821
// Maximum number of indication to be retrieved from a single poll
1922
#define MAX_NUMBER_INDICATION 30
2023

2124
// Polling interval to check for indication
2225
#define POLLING_INTERVAL_MS 20
2326

27+
// Wakeup timeout for dispatch thread, mainly for garbage collection of fragments
28+
#define DISPATCH_WAKEUP_TIMEOUT_MS 5000
29+
2430
// Mutex for sending, ie serial access
2531
static pthread_mutex_t sending_mutex;
2632

2733
// This thread is used to poll for indication
2834
static pthread_t thread_polling;
2935

30-
// Set to false to stop polling thread execution
31-
static bool m_polling_thread_running;
36+
typedef enum {
37+
POLLING_THREAD_RUN,
38+
POLLING_THREAD_STOP,
39+
POLLING_THREAD_STOP_REQUESTED
40+
} polling_thread_state_t;
41+
42+
// Request to handle polling thread state
43+
static polling_thread_state_t m_polling_thread_state_request = POLLING_THREAD_STOP;
3244

3345
// This thread is used to dispatch indication
3446
static pthread_t thread_dispatch;
@@ -85,13 +97,20 @@ static pthread_cond_t m_queue_not_empty_cond = PTHREAD_COND_INITIALIZER;
8597
*/
8698
static void * dispatch_indication(void * unused)
8799
{
100+
struct timespec ts;
101+
88102
pthread_mutex_lock(&m_queue_mutex);
89103
while (m_dispatch_thread_running)
90104
{
91105
if (m_queue_empty)
92106
{
93107
// Queue is empty, wait
94-
pthread_cond_wait(&m_queue_not_empty_cond, &m_queue_mutex);
108+
clock_gettime(CLOCK_REALTIME, &ts);
109+
ts.tv_sec += DISPATCH_WAKEUP_TIMEOUT_MS ; // 5 second timeout
110+
pthread_cond_timedwait(&m_queue_not_empty_cond, &m_queue_mutex, &ts);
111+
112+
// Force a garbage collect (to be sure it's called even if no frag are received)
113+
reassembly_garbage_collect();
95114

96115
// Check if we wake up but nothing in queue
97116
if (m_queue_empty)
@@ -170,10 +189,29 @@ static void * poll_for_indication(void * unused)
170189
// Initially wait for 500ms before any polling
171190
uint32_t wait_before_next_polling_ms = 500;
172191

173-
while (m_polling_thread_running)
192+
m_polling_thread_state_request = POLLING_THREAD_RUN;
193+
194+
while (m_polling_thread_state_request != POLLING_THREAD_STOP)
174195
{
175196
usleep(wait_before_next_polling_ms * 1000);
176197

198+
if(m_polling_thread_state_request == POLLING_THREAD_STOP_REQUESTED)
199+
{
200+
if (!m_queue_empty)
201+
{
202+
// Dispatch did not process all indications. Just wait for it to complete.
203+
wait_before_next_polling_ms = POLLING_INTERVAL_MS;
204+
continue;
205+
}
206+
207+
if (reassembly_is_queue_empty())
208+
{
209+
LOGI("Reassembly queue is empty, exiting polling thread\n");
210+
m_polling_thread_state_request = POLLING_THREAD_STOP;
211+
break;
212+
}
213+
}
214+
177215
// Get the number of free buffers in the indication queue
178216
// Note: No need to lock the queue as only m_ind_queue_read can be updated
179217
// and could still be modified when we release the lock after computing
@@ -207,16 +245,26 @@ static void * poll_for_indication(void * unused)
207245
free_buffer_room = m_ind_queue_read - m_ind_queue_write;
208246
}
209247
}
210-
211-
max_num_indication = free_buffer_room > MAX_NUMBER_INDICATION ?
212-
MAX_NUMBER_INDICATION :
213-
free_buffer_room;
248+
249+
if (m_polling_thread_state_request == POLLING_THREAD_STOP_REQUESTED)
250+
{
251+
// In case we are about to stop, let's poll only one by one to have more chance to
252+
// finish uncomplete fragmented packet and not start to receive a new one
253+
max_num_indication = 1;
254+
LOGD("Poll for one more fragment to empty reassembly queue\n");
255+
}
256+
else
257+
{
258+
// Let's read max indications that can fit in the queue
259+
max_num_indication = MIN(MAX_NUMBER_INDICATION, free_buffer_room);
260+
}
214261

215262
LOGD("Poll for %d indications\n", max_num_indication);
216263

217264
get_ind_res = m_get_indication_f(max_num_indication, onIndicationReceivedLocked);
218265

219-
if (get_ind_res == 1)
266+
if ((get_ind_res == 1)
267+
&& (m_polling_thread_state_request != POLLING_THREAD_STOP_REQUESTED))
220268
{
221269
// Still pending indication, only wait 1 ms to give a chance
222270
// to other threads but not more to have better throughput
@@ -226,6 +274,7 @@ static void * poll_for_indication(void * unused)
226274
{
227275
// In case of error or if no more indication, just wait
228276
// the POLLING INTERVAL to avoid polling all the time
277+
// In case of stop request, wait for to give time to push data received
229278
wait_before_next_polling_ms = POLLING_INTERVAL_MS;
230279
}
231280
}
@@ -242,7 +291,14 @@ bool Platform_lock_request()
242291
{
243292
// It must never happen but add a check and
244293
// return to avoid a deadlock
245-
LOGE("Mutex already locked %d\n", res);
294+
if (res == EINVAL)
295+
{
296+
LOGW("Mutex no longer exists (destroyed)\n");
297+
}
298+
else
299+
{
300+
LOGE("Mutex lock failed %d\n", res);
301+
}
246302
return false;
247303
}
248304
return true;
@@ -320,7 +376,6 @@ bool Platform_init(Platform_get_indication_f get_indication_f,
320376
goto error2;
321377
}
322378

323-
m_polling_thread_running = true;
324379
// Start a thread to poll for indication
325380
if (pthread_create(&thread_polling, NULL, poll_for_indication, NULL) != 0)
326381
{
@@ -352,21 +407,23 @@ void Platform_close()
352407
{
353408
void * res;
354409
pthread_t cur_thread = pthread_self();
355-
// Signal our dispatch thread to stop
356-
m_dispatch_thread_running = false;
357-
// Signal condition to wakeup thread
358-
pthread_cond_signal(&m_queue_not_empty_cond);
359410

360411
// Signal our polling thread to stop
361412
// No need to signal it as it will wakeup periodically
362-
m_polling_thread_running = false;
413+
m_polling_thread_state_request = POLLING_THREAD_STOP_REQUESTED;
363414

364-
// Wait for both tread to finish
415+
// Wait for polling tread to finish
365416
if (cur_thread != thread_polling)
366417
{
367418
pthread_join(thread_polling, &res);
368419
}
369420

421+
// Signal our dispatch thread to stop
422+
m_dispatch_thread_running = false;
423+
// Signal condition to wakeup thread
424+
pthread_cond_signal(&m_queue_not_empty_cond);
425+
426+
// Wait for dispatch tread to finish
370427
if (cur_thread != thread_dispatch)
371428
{
372429
pthread_join(thread_dispatch, &res);

Diff for: lib/wpc/dsap.c

+3-19
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,6 @@ typedef struct
2828
bool busy;
2929
} packet_with_indication_t;
3030

31-
// Minimum period between two consecutive garbage collects of
32-
// uncomplete fragments
33-
// GC is anyway synchronous with received fragment so period between 2 GC
34-
// could be much bigger if not fragments are received for a while
35-
#define MIN_GARBAGE_COLLECT_PERIOD_S 5
36-
37-
// Max timeout in seconds for uncomplete fragmented packet to be discarded
38-
// from rx queue.
39-
static uint32_t m_fragment_max_duration_s = 0;
40-
4131
// Static buffer used to reassemble messages. Allocated statically
4232
// to not have it allocated on stack dynamically. Could also be allocated
4333
// dynamically with platform malloc, but as there is only one needed, static
@@ -318,7 +308,6 @@ void dsap_data_tx_indication_handler(dsap_data_tx_ind_pl_t * payload)
318308
void dsap_data_rx_frag_indication_handler(dsap_data_rx_frag_ind_pl_t * payload,
319309
unsigned long long timestamp_ms_epoch)
320310
{
321-
static unsigned long long last_gc_ts_ms = 0;
322311
reassembly_fragment_t frag;
323312
size_t full_size;
324313
app_qos_e qos;
@@ -394,13 +383,8 @@ void dsap_data_rx_frag_indication_handler(dsap_data_rx_frag_ind_pl_t * payload,
394383

395384
// Do GC synchronously to avoid races as all fragment related actions happens on same thread
396385
// and no need for an another scheduling method to add in Platform
397-
if (m_fragment_max_duration_s > 0 &&
398-
Platform_get_timestamp_ms_monotonic() - last_gc_ts_ms > (MIN_GARBAGE_COLLECT_PERIOD_S * 1000))
399-
{
400-
// Time for a new GC
401-
reassembly_garbage_collect(m_fragment_max_duration_s);
402-
last_gc_ts_ms = Platform_get_timestamp_ms_monotonic();
403-
}
386+
reassembly_garbage_collect();
387+
404388
}
405389

406390
void dsap_data_rx_indication_handler(dsap_data_rx_ind_pl_t * payload,
@@ -500,7 +484,7 @@ bool dsap_unregister_for_data()
500484

501485
bool dsap_set_max_fragment_duration(unsigned int fragment_max_duration_s)
502486
{
503-
m_fragment_max_duration_s = fragment_max_duration_s;
487+
reassembly_set_max_fragment_duration(fragment_max_duration_s);
504488
return true;
505489
}
506490

Diff for: lib/wpc/include/dsap.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ bool dsap_unregister_for_data();
203203
#endif
204204

205205
/**
206-
* \brief Set maximum duration to keep fragment in our buffer until packet is ful
206+
* \brief Set maximum duration to keep fragment in our buffer until packet is full
207207
* \param fragment_max_duration_s
208208
* Maximum time in s to keep fragments from incomplete packets inside our buffers
209209
*/

0 commit comments

Comments
 (0)