Frequent sending of client requests leads to memory leaks?
MrHulu opened this issue · comments
Dear Contributors. Here I go again, I've been running some tests lately and I'm having some doubts about the results.
Environment
- libcoap version : [v4.3.1-174-gf3861a6]
- Build System: [CMake]
- Operating System: [Windows]
- Hosted Environment: [None]
Problem Description
Client requests are sent frequently resulting in a lot of requests being sent too late, which in turn keeps growing the program memory.
Test background
I did some stress testing in the libcoap-minimal project. I have made some modifications to the project as follows:
- In the server, I initialized two resources, the hello resource and the name resource, with uri
coap/hello
andcoap/name
respectively. The hello resource registers the callback for GET requests and the name resource registers the callback for PUT requests. - On the client side, two requests are sent in a loop every 3 milliseconds, one
CON
+GET
, and the otherNON+PUT
. I also listened in the loop for the keyboard keys to be pressed, and theCON+GET
request will stop sending when key 1 is pressed, and theNON+PUT
request will stop sending when key 2 is pressed.
Test the process
Send both requests at the same time
- Start
server.exe
and thenclient.exe
. - After waiting for the client to send in a loop for about a few minutes, I noticed that the memory of
client.exe
in the task manager window kept growing and the memory ofserver.exe
did not change at all. - I watched the server and client logs and everything looked normal, the latest log from the client showed the same token as the latest log from the server (it was printing too fast to see it very well, but it was close).
- I press key 2 and the
NON+PUT
request stops sending! - I continue to press key 1,
CON+GET
request stops sending - At this point I noticed that the server's log is still outputting non-stop, I looked closely and realized that the server is still receiving
CON+GET
requests now, and from the token I can see that these requests should have been sent by the previous client before it was too late. And the memory is slowly decreasing.
Send one request at a time
- Start
server.exe
and thenclient.exe
. - After waiting for the client to send in a loop for about a few minutes, the memory grows as well, but slowly.
- I look at the logs for server and client, and everything looks fine.
- I press key1/key2 and the
CON+GET
/NON+PUT
requests stop sending. - The client and server logs stop outputting almost simultaneously, but the memory doesn't decrease.
Test result
- When the client frequently sends two requests at the same time, the memory keeps growing and there are many requests that are not sent in time. When the client stops sending two requests, the memory will fall back.
- When the client makes only one kind of request frequently, the memory grows slowly and doesn't fall back when it stops sending.
Some questions
- Is there a limit to how often a client can send requests, and how often should I send a request so that I don't have the problem in the test?
- Is there a memory leak?
Code
server.cc
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <iostream>
#include "common.hh"
int
main(void) {
coap_context_t *ctx = nullptr;
coap_address_t dst;
coap_resource_t *resource = nullptr;
coap_resource_t *resource1 = nullptr;
coap_endpoint_t *endpoint = nullptr;
int result = EXIT_FAILURE;;
coap_str_const_t *ruri = coap_make_str_const("coap/hello");
coap_str_const_t *ruri1 = coap_make_str_const("coap/name");
coap_startup();
/* resolve destination address where server should be sent */
if (resolve_address("localhost", "5788", &dst) < 0) {
coap_log_crit("failed to resolve address\n");
goto finish;
}
/* create CoAP context and a client session */
ctx = coap_new_context(nullptr);
if (!ctx || !(endpoint = coap_new_endpoint(ctx, &dst, COAP_PROTO_UDP))) {
coap_log_emerg("cannot initialize context\n");
goto finish;
}
resource = coap_resource_init(ruri, 0);
resource1 = coap_resource_init(ruri1, 0);
coap_register_handler(resource, COAP_REQUEST_GET,
[](auto, auto,
const coap_pdu_t *request,
auto,
coap_pdu_t *response) {
if(request) {
std::cout << "request---"; coap_show_pdu(COAP_LOG_WARN, request);
}
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
coap_add_data(response, 5,
(const uint8_t *)"world");
std::cout << "response---"; coap_show_pdu(COAP_LOG_WARN, response);
std::flush(std::cout);
});
coap_register_handler(resource1, COAP_REQUEST_PUT,
[](auto, auto,
const coap_pdu_t *request,
auto,
coap_pdu_t *response) {
if(request) {
std::cout << "request---"; coap_show_pdu(COAP_LOG_WARN, request);
}
coap_pdu_set_code(response, COAP_RESPONSE_CODE_CHANGED);
std::cout << "response---"; coap_show_pdu(COAP_LOG_WARN, response);
std::flush(std::cout);
});
coap_add_resource(ctx, resource);
coap_add_resource(ctx, resource1);
coap_log(LOG_EMERG, "start coap server\n");
while (true) { coap_io_process(ctx, COAP_IO_WAIT); }
result = EXIT_SUCCESS;
finish:
coap_free_context(ctx);
coap_cleanup();
return result;
}
client.cc
/* minimal CoAP client
*
* Copyright (C) 2018-2023 Olaf Bergmann <bergmann@tzi.org>
*/
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <conio.h>
#include <iostream>
#include <thread>
#include "common.hh"
int
main(void) {
coap_context_t *ctx = nullptr;
coap_session_t *session = nullptr;
coap_address_t dst;
int result = EXIT_FAILURE;;
std::string path = "/coap/hello";
std::string path1 = "/coap/name";
coap_startup();
/* Set logging level */
coap_set_log_level(LOG_INFO);
/* resolve destination address where server should be sent */
if (resolve_address("localhost", "5788", &dst) < 0) {
coap_log_crit("failed to resolve address\n");
goto finish;
}
/* create CoAP context and a client session */
if (!(ctx = coap_new_context(nullptr))) {
coap_log_emerg("cannot create libcoap context\n");
goto finish;
}
/* Support large responses */
coap_context_set_block_mode(ctx,
COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY);
if (!(session = coap_new_client_session(ctx, nullptr, &dst,
COAP_PROTO_UDP))) {
coap_log_emerg("cannot create client session\n");
goto finish;
}
/* coap_register_response_handler(ctx, response_handler); */
coap_register_response_handler(ctx, [](auto, auto,
const coap_pdu_t *received,
auto) {
//coap_show_pdu(COAP_LOG_WARN, received);
return COAP_RESPONSE_OK;
});
coap_register_nack_handler(ctx, [](auto,
const coap_pdu_t *sent,
const coap_nack_reason_t reason,
const coap_mid_t mid){
coap_log(COAP_LOG_WARN, "nack: reason[%d] mid[%x]", reason, mid);
coap_show_pdu(COAP_LOG_WARN, sent);
return;
});
auto getflag = true;
auto putflag = true;
while (1) {
if(_kbhit()) {
int key = _getch();
// key: number 1
if(key == 49) {
getflag = false;
}
// key: number 2
else if(key == 50) {
putflag = false;
}
}
if(getflag) {
auto pdu = coap_new_pdu(COAP_MESSAGE_CON, COAP_REQUEST_CODE_GET,session);
uint8_t token[8];
size_t token_len;
coap_session_new_token(session, &token_len, token);
coap_add_token(pdu, token_len, token);
coap_uri_t uri;
uint8_t buf[1024];
coap_optlist_t *optList = nullptr;
coap_split_uri((uint8_t *)path.c_str(), path.size(), &uri);
coap_uri_into_options(&uri, &optList, 1, buf, sizeof(buf));
coap_add_optlist_pdu(pdu, &optList);
coap_delete_optlist(optList);
coap_send(session, pdu);
coap_show_pdu(COAP_LOG_INFO, pdu);
}
if (putflag) {
auto pdu1 = coap_new_pdu(COAP_MESSAGE_NON, COAP_REQUEST_CODE_PUT, session);
uint8_t token[8];
size_t token_len;
coap_session_new_token(session, &token_len, token);
coap_add_token(pdu1, token_len, token);
coap_uri_t uri;
uint8_t buf[1024];
coap_optlist_t *optList = nullptr;
coap_split_uri((uint8_t *)path1.c_str(), path1.size(), &uri);
coap_uri_into_options(&uri, &optList, 1, buf, sizeof(buf));
uint8_t opt[4] = {0};
coap_insert_optlist(&optList, coap_new_optlist(COAP_OPTION_CONTENT_FORMAT, coap_encode_var_safe(opt, sizeof(opt), COAP_MEDIATYPE_TEXT_PLAIN), opt));
coap_add_optlist_pdu(pdu1, &optList);
coap_add_data(pdu1, 4, (const uint8_t *)"moza");
coap_delete_optlist(optList);
coap_send(session, pdu1);
coap_show_pdu(COAP_LOG_INFO, pdu1);
}
std::this_thread::sleep_for(std::chrono::milliseconds(3));
coap_io_process(ctx, 0);
}
result = EXIT_SUCCESS;
finish:
coap_session_release(session);
coap_free_context(ctx);
coap_cleanup();
return result;
}
It is unclear what you mean by running one or two requests. I assume from your code that this is for the two requests mode.
I do not think there is a memory leak, but your use of libcoap is causing outstanding transmissions to be queued up, which then get flushed out after pressing the 1
and/or 2
keys.
You are calling coap_io_process()
once per loop. If for any reason there is a delay in a response (or it gets lost due to network saturation), then you add in a CON onto the transmit queue on the next loop iteration rather than just sending it. This bumps memory usage. And so on. Once you stop adding in the CONs, then client memory usage will drop off as the backed up CONs are flushed out.
3ms
is a short time to wait between sending a pair of packets and getting back the responses, so the chances of packet transmit backup queuing is likely.
I would try something like (replacing your sleep statement) so coap_io_process()
is called more frequently
coap_tick_t next = 0;
coap_tick_t now;
while (1) {
..
coap_ticks(&now);
while (now < next) {
uint64_t rem_us = coap_ticks_to_rt_us(next - now);
coap_io_process(ctx, rem_us/1000);
coap_ticks(&now);
}
coap_io_process(ctx, 0);
/* Delay sending to once every 3 ms */
next = now + 3 * COAP_TICKS_PER_SECOND/1000;
}
and see what happens.
@MrHulu Any updates on this ?
Closing Issue as suggestions given.