Skip to main content
Add deadlock consideration for mutex option.
Source Link
evoskuil
  • 1.1k
  • 1
  • 9
  • 13

The async_read and async_write functions internally use the io_service in order to obtain threads on which to post completion handlers, blocking until threads are available. This makes them hazardous to guard with mutex locks. When a mutex is used to guard these functions a deadlock will occur when threads back up against the lock, starving the io_service. Given that there is no other way to guard async_write when sending > 64k with a multithread io_service, it effectively locks us into a single thread in that scenario - which of course resolves the concurrency question.

The async_read and async_write functions internally use the io_service in order to obtain threads on which to post completion handlers, blocking until threads are available. This makes them hazardous to guard with mutex locks. When a mutex is used to guard these functions a deadlock will occur when threads back up against the lock, starving the io_service.

The async_read and async_write functions internally use the io_service in order to obtain threads on which to post completion handlers, blocking until threads are available. This makes them hazardous to guard with mutex locks. When a mutex is used to guard these functions a deadlock will occur when threads back up against the lock, starving the io_service. Given that there is no other way to guard async_write when sending > 64k with a multithread io_service, it effectively locks us into a single thread in that scenario - which of course resolves the concurrency question.

Add deadlock consideration for mutex option.
Source Link
evoskuil
  • 1.1k
  • 1
  • 9
  • 13
  1. With concurrent callers the first problem can be resolved easily with a mutex or a strand, using the former if you don't want to buffer the work and the latter if you do. This protects the socket during the function invocations but does nothing for the other problems.

  2. The second problem seems hardest, because it's difficult to see what's going on inside of the code executing asynchronously from the two functions. The async functions both post work to the io_service of the socket.

  1. The third problem is easilycan be resolved by using a (different) mutex that is locked before the async_write, passed into the completion handler and unlocked at that point. This will prevent any caller from beginning a write until all parts of the preceding write are complete.

Assuming that the work posted to the io_service does not incorporate socket operations, there is no need to limit the io_service to a single thread. It does however reinforce the importance of guarding against concurrent execution of the async functions. So, for example, if one follows the chat example but instead adds another thread to the io_service, there becomes a problem. With async function invocations executing within function handlers, you have concurrent function execution. This would require either a mutex, or all async function invocations to be reposted for execution on a strand (which is just wasteful if resource protection is the objective).

This means any guard around the async_write call (mutex or strand) will not protect the socket if there are multiple io_service threads and more than 64kb of data to post (by default, this may possibly vary). Therefore, in this case, the interleave guard is necessary not only for interleave safety, but also thread safety of the socket. I verified all of this in a debugger.

THE MUTEX OPTION

The async_read and async_write functions internally use the io_service in order to obtain threads on which to post completion handlers, blocking until threads are available. This makes them hazardous to guard with mutex locks. When a mutex is used to guard these functions a deadlock will occur when threads back up against the lock, starving the io_service.

  1. With concurrent callers the first problem can be resolved easily with a mutex or a strand, using the former if you don't want to buffer the work and the latter if you do. This protects the socket during the function invocations but does nothing for the other problems.

  2. The second problem seems hardest, because it's difficult to see what's going on inside of the code executing asynchronously from the two functions. The async functions both post work to the io_service of the socket.

  1. The third problem is easily resolved by using a (different) mutex that is locked before the async_write, passed into the completion handler and unlocked at that point. This will prevent any caller from beginning a write until all parts of the preceding write are complete.

Assuming that the work posted to the io_service does not incorporate socket operations, there is no need to limit the io_service to a single thread. It does however reinforce the importance of guarding against concurrent execution of the async functions. So, for example, if one follows the chat example but instead adds another thread to the io_service, there becomes a problem. With async function invocations executing within function handlers, you have concurrent function execution. This would require either a mutex, or all async function invocations to be reposted for execution on a strand (which is just wasteful if resource protection is the objective).

This means any guard around the async_write call (mutex or strand) will not protect the socket if there are multiple io_service threads and more than 64kb of data to post (by default, this may possibly vary). Therefore, in this case, the interleave guard is necessary not only for interleave safety, but also thread safety of the socket. I verified all of this in a debugger.

  1. With concurrent callers the first problem can be resolved with a mutex or a strand, using the former if you don't want to buffer the work and the latter if you do. This protects the socket during the function invocations but does nothing for the other problems.

  2. The second problem seems hardest, because it's difficult to see what's going on inside of the code executing asynchronously from the two functions. The async functions both post work to the io_service of the socket.

  1. The third problem can be resolved by using a (different) mutex that is locked before the async_write, passed into the completion handler and unlocked at that point. This will prevent any caller from beginning a write until all parts of the preceding write are complete.

Assuming that the work posted to the io_service does not incorporate socket operations, there is no need to limit the io_service to a single thread. It does however reinforce the importance of guarding against concurrent execution of the async functions. So, for example, if one follows the chat example but instead adds another thread to the io_service, there becomes a problem. With async function invocations executing within function handlers, you have concurrent function execution. This would require either a mutex, or all async function invocations to be reposted for execution on a strand.

This means any guard around the async_write call (mutex or strand) will not protect the socket if there are multiple io_service threads and more than 64kb of data to post (by default, this may possibly vary). Therefore, in this case, the interleave guard is necessary not only for interleave safety, but also thread safety of the socket. I verified all of this in a debugger.

THE MUTEX OPTION

The async_read and async_write functions internally use the io_service in order to obtain threads on which to post completion handlers, blocking until threads are available. This makes them hazardous to guard with mutex locks. When a mutex is used to guard these functions a deadlock will occur when threads back up against the lock, starving the io_service.

Add new information on interleave/thread safety.
Source Link
evoskuil
  • 1.1k
  • 1
  • 9
  • 13

UPDATE 2

With respect to the third problem (interleaving), if the data size exceeds 65536 bytes, the work is broken up internal to async_write and sent in parts. But it is critical to understand that, if there is more than one thread in the io_service, chunks of work other than the first will be posted to different threads. This all happens internal in the async_write function before your completion handler is called. The implementation creates its own intermediate completion handlers and uses them to execute all but the first socket operation.

This means any guard around the async_write call (mutex or strand) will not protect the socket if there are multiple io_service threads and more than 64kb of data to post (by default, this may possibly vary). Therefore, in this case, the interleave guard is necessary not only for interleave safety, but also thread safety of the socket. I verified all of this in a debugger.

UPDATE 2

With respect to the third problem (interleaving), if the data size exceeds 65536 bytes, the work is broken up internal to async_write and sent in parts. But it is critical to understand that, if there is more than one thread in the io_service, chunks of work other than the first will be posted to different threads. This all happens internal in the async_write function before your completion handler is called. The implementation creates its own intermediate completion handlers and uses them to execute all but the first socket operation.

This means any guard around the async_write call (mutex or strand) will not protect the socket if there are multiple io_service threads and more than 64kb of data to post (by default, this may possibly vary). Therefore, in this case, the interleave guard is necessary not only for interleave safety, but also thread safety of the socket. I verified all of this in a debugger.

Adding update regarding second issue.
Source Link
evoskuil
  • 1.1k
  • 1
  • 9
  • 13
Loading
deleted 14 characters in body
Source Link
evoskuil
  • 1.1k
  • 1
  • 9
  • 13
Loading
Source Link
evoskuil
  • 1.1k
  • 1
  • 9
  • 13
Loading