Is ip::tcp::socket.close() thread safe?

boost-asio, c++11

Solution

In general, it is unsafe to make concurrent calls to the same socket object1. The `async_read()` composed operation is composed of zero or more intermediate `async_read_some()` operations. These intermediate operations are only initiated in threads that are currently calling `io_service::run()`. The internal threads are fairly transparent, and none of the threads listed in the Platform-Specific Implementation Notes will present a problem.

Therefore:

- If a single thread is invoking `io_service::run()` and `socket.close()` is invoked from within a handler, then it is safe as there is no possibility of concurrent execution. The documentation refers to this as an implicit strand.

- If a single thread is invoking `io_service::run()` and `socket.close()` is invoked from outside of a handler, then it is unsafe, as `socket` may have two concurrent calls: `close()` from outside of the `io_service` and `async_read_some()` from a thread that is currently calling `io_service::run()`. To make it thread safe, post a handler into the `io_service` that invokes `socket.close()`.

- If multiple threads are invoking `io_service::run()`, then an explicit strand is required to guarantee thread safety. The `async_read()` needs to be initiated from within a `strand`, and its completion handler must also be wrapped by the same strand. Furthermore, `socket.close()` should be dispatched through the strand.

For stackful coroutines, using the `spawn()` overload that accepts a `strand` will execute the provided function within the context of the `strand`. Furthermore, when the `yield_context` object is passed as the handler to asynchronous operations, the handlers, including intermediate handlers from composed operations, are invoked within the context of the `strand`. Hence, to ensure thread safety, `socket.close()` must either be:

invoked within the coroutine:

// The lambda will execute within the context of my_strand.
boost::asio::spawn(my_strand,
  [socket&](boost::asio::yield_context yield)
  {
    // In my_strand.
    // ...

    // The socket.async_read_some() operations that composed async_read()
    // will run within the context of my_strand.
    async_read(socket, ..., yield);

    // Still within my_strand.
    socket.close();
  });

explicitly dispatched on `my_strand`:

// The lambda will execute within the context of my_strand.
boost::asio::spawn(my_strand,
  [socket&](boost::asio::yield_context yield)
  {
    // In my_strand.
    // ...

    // The socket_.async_read_some() operations that composed async_read()
    // will run within the context of my_strand.
    async_read(socket, ..., yield);
  });

my_strand.dispatch([socket&](){ socket.close(); });

For more details on thread safety, composed operations, and strands, consider reading this answer.

1. The revision history documents an anomaly to this rule. If supported by the OS, synchronous read, write, accept, and connection operations are thread safe. I an including it here for completeness, but suggest using it with caution.

Problem

If there is a `async_read` on the socket ongoing, there should be a internal thread of `io_service` checking the status of the socket. Is it safe to call `socket.close()` from another thread (maybe when it is running a separate handler of the `io_service`)? I mean even I can guarantee that my handlers will not use the asio `socket` concurrently, is it good enough (when taking the internal threads of `io_service` into consideration)? Update: I am using `async_read` in a stackful coroutine. Quite similar to the example at the bottom of this doc, except that mine has an extra layer of function calling with the `yield_context`. If I dispatch the `socket.close()` operation in `my_strand` in that example, is the whole thing thread safe? In another word, will all concerned operations (`async_read`, intermediate `async_read_some`, implicit handlers of the stackful coroutine, `socket.close()`) run through a single strand `my_strand`?

Original source

Related problems