'C++, request URL changes if I execute it in different thread
I'm using restclient-cpp lib for requests (based on libcurl), on C++17, on Ubuntu 18.04.
In my program I need to do periodical get and post requests on different threads. At the beginning of my main I call RestClient::init(), then when I need to send a request I create it, I print it as:
std::cout << "Request url:\n" << request.url << "\n";
and then I assign it to my thread_pool to execute in another thread. The other thread calls execute() method in my Request struct:
struct Request {
RequestType type = RequestType::get;
std::string url;
std::string body_type;
std::string body;
[[nodiscard]] RestClient::Response execute() const {
switch (type) {
case get: { std::cout << " - Inside request.h get. URL:\n" << url << "\n"; return RestClient::get(url); }
default: { assert(false); }
}
}
}
The outputs from the first and second prints are as follows:
Request url in main thread:
https://api.bybit.com/private/linear/position/list
Pushed to threadpool
Inside request.h get. URL:
t��t.com/private/linear/position/list
Of course, the request fails because of unknown url. I don't understand how is it possible that the assignment of the Request object to another thread could modify just the beginning of my url string. If I execute the same request in the main thread it works just fine, but I can't work with that in my program.
Edit; reproducible exaple:
#include "../restclient-cpp/restclient.h"
#include "../thread_pool.hpp"
enum RequestType {
get, post, put, patch, del, head, options
};
struct Request {
RequestType type = RequestType::get;
std::string url;
std::string body_type;
std::string body;
[[nodiscard]] RestClient::Response execute() const {
switch (type) {
case get: { std::cout << " - Inside request.h get. URL:\n" << url << "\n"; return RestClient::get(url); }
default: { assert(false); }
}
}
}
Request() = default;
Request(RequestType type, std::string url): type(type), url(std::move(url)) {}
}
Request get_positions() {
return {RequestType::get,
"https://api.bybit.com/private/linear/position/list"}}
void callback(const RestClient::Response& response) {
std::cout << request.url << "\n";
}
int main(int argc, const char** argv) {
RestClient::init();
thread_pool pool;
std::function<void(const RestClient::Response&)> callback = [&](const RestClient::Response& response) {
callback(response);
};
Request request;
request = get_positions();
std::cout << "Request url:\n" << request.url << "\n";
pool.push_task([&](){
callback(request.execute());
});
}
Solution 1:[1]
I assume pool.push_task() doesn't block until the task is finished, correct? That wouldn't be good logic for a thread queue.
The lambda you are pushing into the pool is holding a reference to the request object that is local to main(). In your example, main() exits as soon as push_task() exits, at which time that request object goes out of scope and gets destroyed.
Meaning, if push_task() is not blocking, the lambda is now left with a dangling reference, in which case Request::execute() is being called on an invalid Request object, or the object is being destroyed while execute() is running. Either way, that is undefined behavior that can easily lead to the kind of symptom you have described (amongst others).
You need to ensure the Request object stays alive until the lambda is done using it. Either by:
having the lambda capture the
requestobject by value instead of by reference:int main(int argc, const char** argv) { ... Request request = get_positions(); std::cout << "Request url:\n" << request.url << "\n"; pool.push_task([=](){ callback(request.execute()); }); }creating the
requestobject dynamically and not destroying it until after the lambda exits. You can use a smart pointer to automate that:int main(int argc, const char** argv) { ... auto request = std::make_unique<Request>(get_positions()); std::cout << "Request url:\n" << request->url << "\n"; pool.push_task([request = move(request)](){ callback(request->execute()); }); }Or:
int main(int argc, const char** argv) { ... auto request = std::make_shared<Request>(get_positions()); std::cout << "Request url:\n" << request->url << "\n"; pool.push_task([request](){ callback(request->execute()); }); }
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
