Project

General

Profile

Actions

Improvements #13676

open

Self-deleting Http::Client

Added by Steven Köhler 25 days ago. Updated 24 days ago.

Status:
New
Priority:
Normal
Assignee:
-
Target version:
-
Start date:
03/29/2025
Due date:
% Done:

0%

Estimated time:

Description

I am in a situation where I need a Http::Client to perform a web request (a payment process to be precise), which is triggered from within an application by the user. The request sends relevant data to an external web server and receives a response. As you can imagine, it is important that the response is properly handled. Due to the asynchronous nature of the web request, this raises some questions regarding the lifetime of the Http::Client. Storing it within the application is not viable, since the application (and thus the client) might be destroyed before the response is received. Storing it in the server itself is possible, but raises the question how and when to delete the client, since keeping it as long as the server is running seems unreasonable. Since there seems to be no direct way to check whether a client is done, a server-side polling solution seems unviable. The only possibility would be to connect a handler to the done signal to set a flag that the client is done, and regularly checking this flag, but this intoduces race conditions, since the server (due to threading issues) might detect the set flag and delete the client before the handler finished.

Long story short: When trying to find a solution, I came up with the idea of a self-deleting Http::Client, that can be used like this:

auto client = std::make_shared<Http::Client>();
client->done().connect([client](AsioWrapper::error_code ec, Http::Response& r) mutable
{
  /* do something */
  client.reset();
});
client->post(...);

The idea is to pass a shared_ptr owning the client to the connected event handler, which ultimately deletes the client at the end of the handler. This way, the client is ensured to live as long as the request is pending and either succeeds or gets a timeout - both triggers the done signal. This used to work until Wt 4.10.1 for the following reasons: An object is allowed to delete itself within a member function if the function does not access/modify the object after deleting it, since that would cause an access violation. Emitting the done signal in Client::emitDone is the last command in that function. As by the documentation, "the signal may be deleted at any time, in particular also while it is being emitted". Thus it is protected against self deletion and the Client object is no longer accessed as soon as the signal is being emitted, making it safe for self-deletion, too.

Unfortunately, WT-11408 changed that by introducing the (perfectly fine and reasonable) lock_guard/mutex to the Client::emitDone function, since the lock_guard is destroyed at the end of the function, which has to release the mutex and thus needs to access the client again, resulting in an access violation if the client already deleted itself from a connected handler. The same would happend, if the deletion happens anywhere else before the emitDone function is completely finished.

The proposed solution/improvement for this issue is to release the lock right after its job to protect the access to the _impl pointer is done, right before emitting the done signal. Thus, the client is no longer accessed as soon as emitting the signal starts, restoring the old behavior. I will create a pull request containing the change shortly. I also attached a small demo app to demonstrate the access violation with the current implementation.

I would be happy if you could merge that change into Wt. I am aware that self deleting objects may pose a risk if not handled with proper care, but it is also a powerful tool to handle the lifetime of asynchronous objects.

Best,
Steven


Files

main.cpp (1.07 KB) main.cpp Steven Köhler, 03/29/2025 01:41 PM
Actions #1

Updated by Steven Köhler 24 days ago

I have created the corresponding pull request #230.

Actions

Also available in: Atom PDF