DEEPANSH NAGARIA
DEEPANSH NAGARIA

Reputation: 113

What causes bad file descriptor error with async_write?

I am using boost asio for a client server application and ran into this problem, with this not so informative error message(at least to me ;)), I am sending structs as messages to and fro, the sending works perfectly well from the client side, but almost similar attempt from the server's side causes this problem(rather error) : Send failed: Bad file descriptor

here's a snippet of the sending part ( please ask for any other details required in the comments):

void read_from_ts(const char*  buf, int len) {  // this is the read callback function
    if (len <= 0) {
        std::cerr << "Error: Connection closed by peer. " << __FILE__ << ":" << __LINE__ << std::endl;
        tcp_client_.close(tcp_connection_);
        tcp_connection_ = nullptr;
        ios_.stop(); // exit
        return;
    }

    const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);
    int tempId = obj->MessageHeaderIn.TemplateID;
    Responses r;
    switch(tempId)
    {
      case 10018: //login
        const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);

        //std::cout<<"Login request received"<<"\n";
        boost::asio::ip::tcp::socket sock_(ios_);
        r.login_ack(sock_);

        /*will add more*/
    }

    std::cout << "RX: " << len << " bytes\n";
  }

  class Responses
  {
    public:
      int login_ack(boost::asio::ip::tcp::socket& socket)
      {
        //std::cout<<"here"<<"\n";
        UserLoginResponse info;
        MessageHeaderOutComp mh;
        ResponseHeaderComp rh;

        rh.MsgSeqNum = 0; //no use for now
        rh.RequestTime = 0; //not used at all
        mh.BodyLen = 53; //no use
        mh.TemplateID = 10019; // IMP

        info.MessageHeaderOut = mh;
        info.LastLoginTime  = 0;
        info.DaysLeftForPasswdExpiry = 10; //not used
        info.GraceLoginsLeft = 10; //not used
        rh.SendingTime = 0;
        info.ResponseHeader = rh;
        //Pad6 not used
        async_write(socket, boost::asio::buffer(&info, sizeof(info)), on_send_completed);
      }
      static void on_send_completed(boost::system::error_code ec, size_t bytes_transferred) {
          if (ec)
              std::cout << "Send failed: " << ec.message() << "\n"; //**error shows up here**
          else
              std::cout << "Send succesful (" << bytes_transferred << " bytes)\n";
      }
  };
};

Upvotes: 2

Views: 1744

Answers (1)

sehe
sehe

Reputation: 392979

UPDATE Just noticed a third, trivial explanation when reading your code, see added bullet

Typically when the file-descriptor is closed elsewhere.

If you're using Asio, this typically means

  • the socket¹ object was destructed. This can be a beginner error when the code doesn't extend the lifetime of objects for the duration of asynchronous operations

  • the file-descriptor was passed to other code that closed it (e.g. using native_handle(https://www.boost.org/doc/libs/1_73_0/doc/html/boost_asio/reference/basic_stream_socket/native_handle.html) and the other code closed it (e.g. because it assumes ownership and did error-handling).

  • UPDATE Or, it can mean your socket was never initialized to begin. In your code I read:

    //std::cout<<"Login request received"<<"\n";
    boost::asio::ip::tcp::socket sock_(ios_);
    r.login_ack(sock_);
    

    However, that just constructs a new socket, never connects or binds it and tries to do login_ack on it. That won't work because login_ack doesn't bind nor connect the socket and invokes async_write on it.

    Did you mean to use tcp_connection_.sock_ or similar?

In general closing file-descriptors in third-party code is an error in multi-threaded code because it invites race conditions which will lead to arbitrary stream corruption (see e.g. How do you gracefully select() on sockets that could be closed on another thread?)

You can use shutdown instead in the majority of cases

UNDEFINED BEHAVIORS

Also, note that

  • info doesn't have sufficient lifetime (it goes out of scope before the async_write would be completed
  • your login_ack never returns a value

Imagining Fixes

This is what I imagine surrounding code to look like, when removing the above problems.

In fact it could be significantly simpler due to the static nature of the response, but I didn't want to assume all responses would be that simple, so I went with shared-pointer lifetime:

Live On Coliru

#include <boost/asio.hpp>
#include <boost/core/ignore_unused.hpp>
#include <iostream>
using boost::asio::ip::tcp;

struct MyProgram {
    boost::asio::io_context ios_;

    struct UserLoginRequest {
        struct MessageHeaderInComp {
            int TemplateID = 10018;
        } MessageHeaderIn;
    };

    struct Connection {
        tcp::socket sock_;
        template <typename Executor>
        Connection(Executor ex) : sock_{ex} {}
    };

    std::unique_ptr<Connection> tcp_connection_ = std::make_unique<Connection>(ios_.get_executor());

    struct {
        void close(std::unique_ptr<Connection> const&);
    } tcp_client_;

    struct Responses {
        static auto login_ack() {
            struct UserLoginResponse {
                struct MessageHeaderOutComp {
                    int BodyLen = 53;             // no use
                    int TemplateID = 10019;       // IMP
                } MessageHeaderOut;
                int LastLoginTime  = 0;
                int DaysLeftForPasswdExpiry = 10; // not used
                int GraceLoginsLeft = 10;         // not used
                struct ResponseHeaderComp {
                    int MsgSeqNum = 0;            // no use for now
                    int RequestTime = 0;          // not used at all
                    int SendingTime = 0;
                } ResponseHeader;
            };
            return std::make_shared<UserLoginRequest>();
        }
    };

    void read_from_ts(const char*  buf, int len) {  // this is the read callback function
        if (len <= 0) {
            std::cerr << "Error: Connection closed by peer. " << __FILE__ << ":" << __LINE__ << std::endl;
            tcp_client_.close(tcp_connection_);
            tcp_connection_ = nullptr;
            ios_.stop(); // exit
            return;
        }

        const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);
        int tempId = obj->MessageHeaderIn.TemplateID;

        switch(tempId) {
            case 10018: //login
                const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);

                //std::cout<<"Login request received"<<"\n";
                boost::asio::ip::tcp::socket sock_(ios_);
                auto response = Responses::login_ack();
                async_write(tcp_connection_->sock_, boost::asio::buffer(response.get(), sizeof(*response)),
                    [response](boost::system::error_code ec, size_t bytes_transferred) {
                        if (ec)
                            std::cout << "Send failed: " << ec.message() << "\n"; //**error shows up here**
                        else
                            std::cout << "Send succesful (" << bytes_transferred << " bytes)\n";
                    });

                /*will add more*/
                boost::ignore_unused(obj);
        }

        std::cout << "RX: " << len << " bytes\n";
      }

};

int main() {
    MyProgram p;
}

¹ (or acceptor/posix::strean_descriptor)

Upvotes: 3

Related Questions