echo server modification causes undefined behaviour

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

echo server modification causes undefined behaviour

Boost - Users mailing list

I'm a database developer and I'm using boost for the first time.
I've copied the echo server & client example from the Boost website and modified it somewhat so that when I receive a SQL statement from the client I pass pass it onto a function that performs the db execution. Once executed Ireply back to the client how many rows were affected.

The code works , if I send only a single request at a time from the synchronous client, so I modified the async server so that the read is a read_until.
The change now crashes the server.

Could somebody  please help ?
Attached is the code.




_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users

sync_db_client2.cpp (3K) Download Attachment
async_db_server.cpp (3K) Download Attachment
logger.h (4K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: echo server modification causes undefined behaviour

Boost - Users mailing list
On 7/09/2017 11:19, Peter Koukoulis via Boost-users wrote:

> I'm a database developer and I'm using boost for the first time.
> I've copied the echo server & client example from the Boost website
> <http://www.boost.org/doc/libs/1_64_0/doc/html/boost_asio/examples/cpp11_examples.html> and
> modified it somewhat so that when I receive a SQL statement from the
> client I pass pass it onto a function that performs the db execution.
> Once executed Ireply back to the client how many rows were affected.
>
> The code works , if I send only a single request at a time from the
> synchronous client, so I modified the async server so that the read is a
> read_until.
> The change now crashes the server.

read_until has a different behaviour from basic reads; you must preserve
the streambuf between consecutive calls and consume only the bytes you
have been told about.

This is because the buffer will contain more data than was actually
requested -- the underlying network read will read a block of data that
might contain multiple terminator characters, but read_until reports
only the data up to (and including) the first terminator each time.

In particular:
>     boost::asio::async_read_until(sk, receive_data, '\n',
>         [this](const boost::system::error_code& ec, std::size_t bytes_transferred) {
>               if (!ec) {
>                 boost::asio::streambuf::const_buffers_type bufs = receive_data.data();
>                 std::string str(boost::asio::buffers_begin(bufs), boost::asio::buffers_begin(bufs) + receive_data.size());
>                 do_write(bytes_transferred, rows_affected(str) );
>               }
>             });

Do not use receive_data.size(); use bytes_transferred instead.

Also, after copying the bytes from the buffer into the stream, issue
receive_data.consume(bytes_transferred).  This tells Asio that the bytes
have been processed so it can move on to the next terminator the next
time you call async_read_until.

There's an example of this at
http://coliru.stacked-crooked.com/a/51b5be0caf331187

_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|

Re: echo server modification causes undefined behaviour

Boost - Users mailing list
Hi Gavin

thanks for the info, I've modified the do_read function as follows:

  void do_read() {
    excep_log( "Start reading");
    boost::asio::async_read_until(sk, receive_data, '\n',
        [this](const boost::system::error_code& ec, std::size_t bytes_transferred) {
              if (!ec) {
                boost::asio::streambuf::const_buffers_type bufs = receive_data.data();
                std::string str(boost::asio::buffers_begin(bufs), boost::asio::buffers_begin(bufs) + bytes_transferred);
                receive_data.consume(bytes_transferred);
                excep_log( "SQL Statement: " + str);
                do_write(bytes_transferred, rows_affected(str) );
              }
            });
  }

The server still crashes, though the error is no longer a "core dump", but a "Segmentation fault".
The log file generated shows output as follows:
Start: 0
0: Start reading
0: SQL �\�
7: 22021 ERROR:  invalid byte sequence for encoding "UTF8": 0xba

7: Before write

As you can see, when a session is started (start function), the read_until starts immediately,   but at that point the client has only connected and not yet sent a SQL statement (22021 ERROR is a database error).
So how can I start a session, but defer the reading until the client has sent an SQL statement ?


Any suggestions would be appreciated
Peter



On Thursday, 7 September 2017, 6:25, Gavin Lambert via Boost-users <[hidden email]> wrote:


On 7/09/2017 11:19, Peter Koukoulis via Boost-users wrote:

> I'm a database developer and I'm using boost for the first time.
> I've copied the echo server & client example from the Boost website
> <http://www.boost.org/doc/libs/1_64_0/doc/html/boost_asio/examples/cpp11_examples.html> and

> modified it somewhat so that when I receive a SQL statement from the
> client I pass pass it onto a function that performs the db execution.
> Once executed Ireply back to the client how many rows were affected.
>
> The code works , if I send only a single request at a time from the
> synchronous client, so I modified the async server so that the read is a
> read_until.
> The change now crashes the server.

read_until has a different behaviour from basic reads; you must preserve
the streambuf between consecutive calls and consume only the bytes you
have been told about.

This is because the buffer will contain more data than was actually
requested -- the underlying network read will read a block of data that
might contain multiple terminator characters, but read_until reports
only the data up to (and including) the first terminator each time.

In particular:
>    boost::asio::async_read_until(sk, receive_data, '\n',
>        [this](const boost::system::error_code& ec, std::size_t bytes_transferred) {
>              if (!ec) {
>                boost::asio::streambuf::const_buffers_type bufs = receive_data.data();
>                std::string str(boost::asio::buffers_begin(bufs), boost::asio::buffers_begin(bufs) + receive_data.size());
>                do_write(bytes_transferred, rows_affected(str) );
>              }
>            });

Do not use receive_data.size(); use bytes_transferred instead.

Also, after copying the bytes from the buffer into the stream, issue
receive_data.consume(bytes_transferred).  This tells Asio that the bytes
have been processed so it can move on to the next terminator the next
time you call async_read_until.

There's an example of this at
http://coliru.stacked-crooked.com/a/51b5be0caf331187

_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users



_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users
Reply | Threaded
Open this post in threaded view
|

Re: echo server modification causes undefined behaviour

Boost - Users mailing list
On 7/09/2017 22:21, Peter Koukoulis via Boost-users wrote:

> thanks for the info, I've modified the do_read function as follows:
>
>    void do_read() {
>      excep_log( "Start reading");
>      boost::asio::async_read_until(sk, receive_data, '\n',
>          [this](const boost::system::error_code& ec, std::size_t
> bytes_transferred) {
>                if (!ec) {
>                  boost::asio::streambuf::const_buffers_type bufs =
> receive_data.data();
>                  std::string str(boost::asio::buffers_begin(bufs),
> boost::asio::buffers_begin(bufs) + bytes_transferred);
>                  receive_data.consume(bytes_transferred);
>                  excep_log( "SQL Statement: " + str);
>                  do_write(bytes_transferred, rows_affected(str) );
>                }
>              });
>    }
>
> The server still crashes, though the error is no longer a "core dump",
> but a "Segmentation fault".
> The log file generated shows output as follows:
> Start: 0
> 0: Start reading
> 0: SQL �\�
> 7: 22021 ERROR:  invalid byte sequence for encoding "UTF8": 0xba
>
> 7: Before write
>
> As you can see, when a session is started (start function), the
> read_until starts immediately,   but at that point the client has only
> connected and not yet sent a SQL statement (22021 ERROR is a database
> error).
> So how can I start a session, but defer the reading until the client has
> sent an SQL statement ?

Another problem with your original code is that the session is not being
kept alive.

>            std::make_shared<session>(std::move(sk))->start();

This creates a shared instance of session and calls start() on it, but
as soon as start() returns it will discard the local reference to the
session -- unless something inside of the start() call keeps a reference
alive, that means the session will probably be deleted before the read
operation completes.  (This is why you're getting garbage when the
callback does eventually get called -- it's reading deleted memory.)

The usual technique for keeping a shared_ptr alive is to ensure that you
pass it into the async callback.  Call shared_from_this() outside of
your lambda and capture *that* variable instead of capturing 'this'.
You'll need to do the same thing in the write operation as well.

(Capturing 'this' is not sufficient to keep the object alive, as it's
just a raw pointer.)

See the example in
http://www.boost.org/doc/libs/1_65_1/doc/html/boost_asio/example/cpp11/echo/async_tcp_echo_server.cpp

_______________________________________________
Boost-users mailing list
[hidden email]
https://lists.boost.org/mailman/listinfo.cgi/boost-users