Skip to content

Assertion failed when destroying httplib::Client #2068

@ghuynbibi1

Description

@ghuynbibi1

When destroying an httplib::Client object in one thread while a Get request is still active in another thread, the program aborts with an assertion error, even after calling httplib::Client::stop().

My use case involves a thread consuming SSE using Get. I want to stop the Get request gracefully so the thread can exit. However, calling stop() before destroying the client does not resolve the issue.

Assertion error:

client: httplib.h:7514: void httplib::ClientImpl::close_socket(Socket&): Assertion `socket_requests_in_flight_ == 0 || socket_requests_are_from_thread_ == std::this_thread::get_id()' failed.
Aborted (core dumped)

Tested on 2b5d1eea8d7e9f881ac4be04ce31df782b26b6a9

Minimal Reproduction:

client.cpp

#include "httplib.h"

#include <atomic>
#include <chrono>
#include <iostream>
#include <memory>
#include <thread>

using namespace httplib;
using namespace std;

int main(void) {
  std::unique_ptr<Client> client(std::make_unique<Client>("localhost:1234"));
  client->set_read_timeout(std::chrono::minutes(10));

  std::atomic<bool> stop{false};

  std::thread t([&] {
    client->Get("/event1", [&](const char *data, size_t data_length) -> bool {
      std::cout << std::string(data, data_length) << "\n";
      return !stop;
    });
  });

  std::this_thread::sleep_for(std::chrono::seconds(5));
  std::cout << "stop\n";
  stop = true;
  client->stop();
  std::cout << "stop done\n";

  std::cout << "destroy\n";
  client.reset();
  std::cout << "destroy done\n";

  t.join();
}

server.cpp

#include "httplib.h"

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>

using namespace httplib;
using namespace std;

class EventDispatcher {
public:
  EventDispatcher() {}

  void wait_event(DataSink *sink) {
    unique_lock<mutex> lk(m_);
    int id = id_;
    cv_.wait(lk, [&] { return cid_ == id; });
    sink->write(message_.data(), message_.size());
  }

  void send_event(const string &message) {
    lock_guard<mutex> lk(m_);
    cid_ = id_++;
    message_ = message;
    cv_.notify_all();
  }

private:
  mutex m_;
  condition_variable cv_;
  atomic_int id_{0};
  atomic_int cid_{-1};
  string message_;
};

int main(void) {
  EventDispatcher ed;

  Server svr;
  svr.Get("/event1", [&](const Request & /*req*/, Response &res) {
    cout << "connected to event1..." << endl;
    res.set_chunked_content_provider("text/event-stream",
                                     [&](size_t /*offset*/, DataSink &sink) {
                                       ed.wait_event(&sink);
                                       return true;
                                     });
  });

  thread t([&] {
    int id = 0;
    while (true) {
      this_thread::sleep_for(chrono::seconds(1));
      // cout << "send event: " << id << std::endl;
      std::stringstream ss;
      ss << "data: " << id << "\n\n";
      ed.send_event(ss.str());
      id++;
    }
  });

  svr.listen("localhost", 1234);
}

Question:

Is this the correct way to stop and destroy the client gracefully? Any suggestions or workarounds would be greatly appreciated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions