Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added send_binary_blocking function #355

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

michl2310
Copy link

Regarding my opened bug "connection.send_binary() out of memory #340" I created a blocking send function with a local buffer and a deadline_timer which will cancel the asio::write after 3 seconds if it got stuck.

include/crow/websocket.h Outdated Show resolved Hide resolved
Co-authored-by: Farook Al-Sammarraie <farook@the-e-dev.com>
@CrowCpp CrowCpp deleted a comment from crow-clang-format bot Feb 24, 2022
@The-EDev The-EDev self-requested a review February 27, 2022 03:01
buffer.emplace_back(boost::asio::buffer(header));
buffer.emplace_back(boost::asio::buffer(msg));

boost::asio::io_service service;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to use adaptor_.get_io_service() instead of creating a new io_service?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to use it, however, it did not work properly for me. I think there might have been some timing issues where e.g. the deadline_timer is being canceled after write but the async_wait of the timer has not been started yet as the adaptor's IO was too busy?

buffer.emplace_back(boost::asio::buffer(msg));

boost::asio::io_service service;
boost::asio::deadline_timer deadline_timer(service);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Crow already has task_timer, would that be more appropriate?

if(!err)
{
ec = boost::system::errc::make_error_code(boost::system::errc::timed_out);
adaptor_.shutdown_write();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the adaptor is being shut down, shouldn't check_destroy() or delete this be used as well?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my thought was that the caller decides whether he wants to close the connection in an error case or not. That is also the reason why &ec has to be provided.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well if we're shutting down the adaptor I'm not sure there's anything left to do with the connection object, even for the http connection deleting the object was the course of action taken when an error occurs, the shutdown parts were added to it later to make the process cleaner.

Copy link
Author

@michl2310 michl2310 Mar 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that is correct but I had a different approach. I am saving all connections in a map. If I call automatically check_destroy() which internally deletes itself (delete this) then I would run into a segmentation fault next time I tried to use it. In this case I can check the &ec and decide to remove that entry from my map. But does some other user know that he has to do that as well only if a write ran into a timeout?

Copy link
Member

@The-EDev The-EDev Mar 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A use case like yours would probably be best solved with the close handler, maybe we can call the close method and have that delete the connection, and have the close handler deal with removing the connection from the map.

The approach I'm trying my best to take with Crow is to make getting something up and running as simple as can be while providing the as close as full access to the protocol internals for the convoluted edge cases that end up occurring.

Copy link
Author

@michl2310 michl2310 Mar 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to implement that approach but unfortunatelly I was not able to make it run in my asynchronous state machine.
It looks somehow like this now:
The Map is being locked everytime it is being used.
Every now and then something wants to send data -> map is being locked -> send.
During that send we have the case that it somehow hangs -> deadline timer -> check_destroy() -> onclose()-handler.
The onclose wants to lock the map as it wants to remove the connection but this is not possible as the map is still being locked by the send (deadlock).
Ok so now I outsource the onclose-task in a separate thread that waits for the send to finish and then locks the map to remove the connection.
Now "delete this" is being called as a final step of check_destroy() in the send task.
send-task returns and in an ideal world my outsourced thread will now recognize that it can lock the map to remove the deleted connection.
However, my asynchronous state machine decides to send another data and is faster than the outsourced thread.
Send() locks the map again and runs into a segfault as the connection had been deleted by itself.

Don't get me wrong. I am fine with your idea to have a very simple library and if you want to have check_destroy() being called in such a case then go for it, however, it did not work for me.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe returning a bool or enum or something could be applicable.

For your state machine, maybe you could check using an atomic bool flag (or using a similar approach), after sending data, if something has to be removed from the map.
E. g.: Send -> fails and results in onclose() -> sets flag and queues a deletion in a new thread
But before sending anything new, it checks that flag and resets it.

@@ -157,6 +158,36 @@ namespace crow
do_write();
});
}

/// Send a binary encoded message in blocking mode.
void send_binary_blocking(const std::string& msg, boost::system::error_code& ec) override
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would make sense to add a send_text_blocking(). Also is it necessary to put ec there?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my answer above regarding check_destroy()


boost::asio::io_service service;
boost::asio::deadline_timer deadline_timer(service);
deadline_timer.expires_from_now(boost::posix_time::seconds(3));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be the same as timeout_ in app.h.

@The-EDev The-EDev changed the title added send_binary_blocking function added send_binary_blocking function Mar 4, 2022
@The-EDev The-EDev linked an issue Apr 7, 2022 that may be closed by this pull request
@gittiver gittiver added the further information required existing information is lacking, or feedback is required label Jan 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
further information required existing information is lacking, or feedback is required
Projects
None yet
Development

Successfully merging this pull request may close these issues.

connection.send_binary() out of memory
5 participants