In simple terms your design should look like this:
Call async_read
When you need to write
Call async_write
Do not call async_write again until the previous async_write call has returned.
If you need to call multiple async_write operations, then que your buffers.
Call async_write
callback pull next buffer from que
Call async_write
callback pull next buffer from que
Call async_write
callback pull next buffer from que
By doing this, you're ensuring this only one async_write operating is in effect at all times per socket. Which is how it should be.
Do NOT do this.
Call async_write
Call async_write
Call async_write
callback
callback
callback
This is where you run into issues by trying to stop async_write after a read operation has occurred with an error.
When async_read returns with an error you no longer have to take extra steps in your async_write operation to cancel concurrent calls.
Call async_read
Call async_write
async_read callback returns with error (do not call async_read again)
async_write returns with error (do not call async_write again)
So when calling async_write check a flag (iswriting) if the flag is true, then push your buffer into your que. When the callback returns, check your que, pull the next buffer and call async_write again. If iswriting is false, then just call async_write instead of pushing it to the que. If your callback returns and your que is finally empty then set iswriting to false again.
This is all pretty straight forward...