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
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions include/crow/websocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,36 @@ namespace crow // NOTE: Already documented in "crow/app.h"
{
send_data(0x2, std::move(msg));
}

/// 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()

{
auto header = build_header(2, msg.size());
std::vector<boost::asio::const_buffer> buffer;
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?

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?

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.


deadline_timer.async_wait([&](const boost::system::error_code& err)
{
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.

}
});

dispatch([&, this]
{
boost::asio::write(adaptor_.socket(), buffer, boost::asio::transfer_all(), ec);
deadline_timer.cancel();
});

service.run();
}

/// Send a plaintext message.
void send_text(std::string msg) override
Expand Down
Loading