c++ - EOF in boost::async_read with thread_pull and boost 1.54 -


i have strange problem server application. system simple: have 1+ devices , 1 server app communicate on network. protocol has binary packets variable length, fixed header (that contain info current packet size). example of packet:

char pct[maxsize] = {} pct[0] = 0x5a //preambule pct[1] = 0xa5 //preambule pct[2] = 0x07 //packet size pct[3] = 0x0a //command ... [payload] 

the protocol built on principle of command-answer.

i use boost::asio communication - io_service thread pull (4 threads) + async read/write operation (code example below) , create "query cycle" - each 200ms timer:

  • query 1 value device
  • get result, query second value
  • get result, start timer again

this work on boost 1.53 (debug , release). switch boost 1.54 (especially in release mode) magic begins. server successfuly starts, connects device , starts "query cycle". 30-60 seconds work (i receive data, data correct), start receive asio::error on last read handle (always in 1 place). error type: eof. after recieving error, must disconnect device.

some time of googling give me info eof indicate other side (device in case) initiated disconnect procedure. but, according logic of device can not true. may explain what's going on? may need set socket option or defines? see 2 possible reason:

  • my side init disconnect (with reason, don't know) , eof answer of action.
  • some socket timeout firing.

my environment:

  • os: windows 7/8
  • compiler: msvc 2012 update 3

sample code of main "query cycle". adapted official boost chat example code simplified reduce space :)

  • socketworker - low level wrapper sockets
  • deviceworker - class device communication
  • eres - internal struct error store
  • protocmd , protoanswer - wrapper raw array command , answer (chat_message analog boost chat example)
  • lw_service_proto namespace - predefined commands , max sizes of packets

so, code samples. socket wrapper:

namespace b = boost; namespace ba = boost::asio;  typedef b::function<void(const protoanswer answ)> datareceivertype;  class socketworker { private:     typedef ba::ip::tcp::socket sockettype;     typedef std::unique_ptr<sockettype> socketptrtype;     socketptrtype devsocket;     protocmd      sendcmd;     protoanswer   rcvansw;       //[other definitions]  public:  //--------------------------------------------------------------------------- eres socketworker::connect(/*[connect settings]*/) {     eres res(lgs_result_error, "connect device - unknow error");      using namespace boost::asio::ip;     boost::system::error_code sock_error;      //try connect     devsocket->connect(tcp::endpoint(address::from_string(/*[connect settings ip]*/), /*[connect settings port]*/), sock_error);      if(sock_error.value() > 0) {         //[work error]         devsocket->close();     }     else {         //[res code ok]     }       return res; } //--------------------------------------------------------------------------- eres socketworker::disconnect() {     if (devsocket->is_open())     {         boost::system::error_code ec;         devsocket->shutdown(bi::tcp::socket::shutdown_send, ec);         devsocket->close();     }     return eres(lgs_result_ok, "ok"); }  //--------------------------------------------------------------------------- //query cmd void socketworker::querycommand(const protocmd cmd, datareceivertype dataclb) {     sendcmd = std::move(cmd); //store command     if (sendcmd .commandlength() > 0)     {         ba::async_write(*devsocket.get(), ba::buffer(sendcmd.data(), sendcmd.length()),                         b::bind(&socketworker::handlesocketwrite,                                 this, ba::placeholders::error, dataclb));     }     else     {         cerr << "send command error: nothing send" << endl;     } }  //--------------------------------------------------------------------------- // boost socket handlers void socketworker::handlesocketwrite(const b::system::error_code& error,                                                     datareceivertype dataclb) {     if (error)     {         cerr << "send cmd error: " << error.message() << endl;         //[send error other place]         return;     }      //start reading header of answer (lw_service_proto::headersize == 3 bytes)     ba::async_read(*devsocket.get(),                    ba::buffer(rcvansw.data(), lw_service_proto::headersize),                    b::bind(&socketworker::handlesockreadheader,                             this, ba::placeholders::error, dataclb));  } //--------------------------------------------------------------------------- //handler read header void socketworker::handlesockreadheader(const b::system::error_code& error, datareceivertype dataclb) {     if (error)     {         //[error working]         return;     }      //decode header (check preambule ,  full packet size) , read answer payload     if (rcvansw.decodeheaderandgetcmdsize())     {       ba::async_read(*devsocket.get(),                      ba::buffer(rcvansw.answer(), rcvansw.answerlength()),                      b::bind(&socketworker::handlesockreadbody,                               this, ba::placeholders::error, dataclb));     } } //--------------------------------------------------------------------------- //handler andwer payload void socketworker::handlesockreadbody(const b::system::error_code& error, datareceivertype dataclb) {     //if no error - send anwser 'master'     if (!error){         if (dataclb != nullptr)              dataclb(rcvansw);     }     else{         //[error process]          //here got eof in release mode     } }  }; 

device worker

class deviceworker { private:     const static int lw_query_time = 200;     lwdevicesocketworker sockworker;     ba::io_service&    timerioservice;     typedef std::shared_ptr<ba::deadline_timer> timerptr;     timerptr        querytimer;     bool            querycycleworking;      //[other definitions] public:  eres deviceworker::connect() {     eres intres = sockworker.connect(/*[connect settings here]*/);      if(intres != lgs_result_ok) {         //[set result error]     }     else {         //[set result success]          //start "query cycle"         startnewcyclequery();     }      return intres; } //--------------------------------------------------------------------------- eres deviceworker::disconnect() {     return sockworker.disconnect(); } //--------------------------------------------------------------------------- void deviceworker::startnewcyclequery() {     querycycleworking = true;     //start timer     querytimer = make_shared<ba::deadline_timer>(timerioservice, bt::milliseconds(lw_query_time));     querytimer->async_wait(boost::bind(&deviceworker::handlequerytimer,                                        this, boost::asio::placeholders::error)); } //--------------------------------------------------------------------------- void deviceworker::stopcyclequery() {     //kill timer     if (querytimer)          querytimer->cancel();      querycycleworking = false; } //--------------------------------------------------------------------------- //timer handler void deviceworker::handlequerytimer(const b::system::error_code& error) {     if (!error)     {         protocmd cmd;             //query first value         cmd.encodecommandcore(lw_service_proto::cmdgetalarm, 1);         sockworker.querycommand(cmd, boost::bind(&deviceworker::receivealarmcycle,                                  this, _1));         } } //--------------------------------------------------------------------------- //receive first value void deviceworker::receivealarmcycle(protoanswer adata) {     //check , fix last bytes (remove \r\n commands)     adata.checkandfixfooter();      //[working answer]      if (querycycleworking)     {          //query second value         protocmd cmd;         cmd.encodecommandcore(lw_service_proto::cmdgetenergylevel, 1);         sockworker.querycommand(cmd, b::bind(&deviceworker::receiveenergycycle,                                        this, _1));     } } //--------------------------------------------------------------------------- //receive second value void deviceworker::receiveenergycycle(protoanswer edata) {     //check , fix last bytes (remove \r\n commands)     edata.checkandfixfooter();      //[working second value]      //start new "query cycle"     if (querycycleworking)         startnewcyclequery(); }  }; 

any ideas welcome :)

edit: after several test see anower picture:

  • this issue reproduce on boost 1.54 (debug , release mode, release - more faster), boost 1.53 no more error (maybe poorly clean code rebuild first times....)
  • with boost 1.54 , 1 thread (instead of 4) work well

i spend time debugger , boost source , making conclusion:

  • when receive eof data received.
  • this eof indicate nothing transfer in operation, i.e. socket result flag 0 (no error), boost operation flag if eof (transfer bytes == 0)

at moment forced switch on boost 1.53...

i had exact same problem , quite sure bug of boost::asio 1.54.0

here bug report.

the solution 1.53, although there patch available 1.54 in bug report page.


Comments