obgm / libcoap

A CoAP (RFC 7252) implementation in C

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 and coap/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 other NON+PUT. I also listened in the loop for the keyboard keys to be pressed, and the CON+GET request will stop sending when key 1 is pressed, and the NON+PUT request will stop sending when key 2 is pressed.

Test the process

Send both requests at the same time

  • Start server.exe and then client.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 of server.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 then client.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.