Search code examples
boostboost-asioboost-beastboost-beast-websocket

Trying to Implement Idle Timeout by cancelling current async_read and continuing with next read/write operation


I am using a timer and an async_read, so whenever the timer runs out it cancels the async_read by doing ws_.next_layer().cancel() so that i can continue with my next operation. but when I catch the timeout error and continue the next write opertaion ws_.write() I get an operation cancelled error. Does the ws_.next_layer().cancel() make the socket unusable for subsequent operations. Is there any other alternate way to capture an idle timeout and quit from an async_read, while still keeping the socket functional without closing and reconnecting it.

#include "Socket_Factory_1.h"


class TimeoutException : public std::exception {
public:
    explicit TimeoutException(const std::string& message) : message_(message) {}
    
    virtual const char* what() const noexcept override {
        return message_.c_str();
    }

private:
    std::string message_;
};

WebSocketClient::WebSocketClient(const std::string& host, const std::string& port)
    : host_(host), port_(port), ioc_(), resolver_(ioc_), ws_(ioc_), timer_(ioc_) {}

WebSocketClient::~WebSocketClient() {
    close();
}

void WebSocketClient::connect() {
    try {
        ws_.binary(true);
        auto const results = resolver_.resolve(host_, port_);
        asio::connect(ws_.next_layer(), results.begin(), results.end());
        ws_.handshake(host_, "/");
    } catch (const std::exception& e) {
        std::cerr << "Connect Error: " << e.what() << std::endl;
        throw;
    }
}

void WebSocketClient::send(const void* data, size_t size) {
    try {
        ws_.write(asio::buffer(data, size));
    } catch (const std::exception& e) {
        std::cerr << "Send Error: " << e.what() << std::endl;
        throw;
    }
}

std::string WebSocketClient::receive() {
    std::cout<<"HI1";
    
        
        read_successful = false;
        timeout_occurred = false;

        // Set up the timer
        std::chrono::seconds timeout_duration(3);
        timer_.expires_after(timeout_duration);
        std::cout<<"HI";
        // Start an asynchronous read operation
        ws_.async_read(buffer,
            [this](beast::error_code ec, std::size_t bytes_transferred) {
                if (!ec) {
                    result = beast::buffers_to_string(buffer.data());
                    read_successful = true; // Mark read as successful
                } 
                else if(ec==asio::error::operation_aborted){
                    std::cout<<"aborted"<<std::endl;
                }
                else {
                    std::cerr << "Read Error: " << ec.message() << std::endl;
                }
            }
        );

        // Set up the timeout handler
        timer_.async_wait(
            [this](beast::error_code ec) {
                if (ec != asio::error::operation_aborted) {
                    timeout_occurred = true; // Mark timeout as occurred
                }else{

                    std::cout<<"aborted tiemr"<<std::endl;
                }
            }
        );

        // Run the IO context to perform the asynchronous operations
        while (!read_successful && !timeout_occurred) {
            ioc_.run_one(); // Process one ready handler
        }
        buffer.clear();
        if (timeout_occurred) {
            ws_.next_layer().cancel();
            ioc_.run();
            // ioc_.restart();
            std::cerr << "Read operation timed out\n";
            throw TimeoutException("Timeout");
        }
        if (read_successful) {
            timer_.cancel();
            return result;
        }
        return ""; // Return empty string if neither read nor timeout occurred
    }
void WebSocketClient::close() {
    try {
        if (ws_.is_open()) {
            ws_.close(websocket::close_code::normal);
        }
    } catch (const std::exception& e) {
        std::cerr << "Close Error: " << e.what() << std::endl;
    }
}

Iam trying to implement a idle timeout so the code exits from an infinite loop of async_read


Solution

  • Does the ws_.next_layer().cancel()

    You'd have to assume as much. The docs say e.g.

    Per-Operation Cancellation

    This asynchronous operation supports cancellation for the following net::cancellation_type values:

    • net::cancellation_type::terminal
    • net::cancellation_type::total

    total cancellation succeeds if the operation is suspended due to ongoing control operations such as a ping/pong.

    terminal cancellation succeeds when supported by the underlying stream.

    terminal cancellation leaves the stream in an undefined state, so that only closing it is guaranteed to succeed.

    Since you cannot guarantee total cancellation, you have to assume terminal cancellation, which means that you can only close the transport.

    Ideas

    You should probably implement application logic in the application layer. E.g. have an outgoing queue and stop the writeloop instead of hard-cancelling operations on the underlying stream.