Thanks to visit codestin.com
Credit goes to github.com

Skip to content

cancel() runs completion handlers immediately but should post/defer them instead #210

@dkl

Description

@dkl

Hi,

socket::cancel() appears to call pending completion handlers immediately, instead of delaying their execution until after the call returns. As a result there are dead locks when calling more socket operations (such as async_send()) from the completion handlers, because the socket uses a non-recursive mutex internally. This differs from other boost::asio objects such as boost::asio::steady_timer, which allow this case, so for example you can restart a timer from inside the operation_aborted completion handler.

An example to show the issue:

// Build: g++ -Wall -g azmq_cancel_timing.cpp -lzmq -lboost_filesystem -o azmq_cancel_timing

#include <azmq/socket.hpp>
#include <azmq/version.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
#include <stdio.h>
#include <zmq.hpp>

int main()
{
	printf("boost version: %i\n", BOOST_VERSION);
	printf("azmq version: %i\n", AZMQ_VERSION);

	boost::asio::io_context ioctx;

	boost::asio::steady_timer timer(ioctx);
	timer.expires_from_now(std::chrono::seconds(1));
	timer.async_wait(
		[](boost::system::error_code const& ec)
		{
			printf("timer async_wait completion handler, ec = %s\n", ec.message().c_str());
		}
	);

	boost::asio::ip::udp::socket udpsocket(ioctx, boost::asio::ip::udp::endpoint(boost::asio::ip::make_address("127.0.0.1"), 0));
	std::array<uint8_t, 1000> buffer1;
	udpsocket.async_receive(boost::asio::buffer(buffer1),
		[](boost::system::error_code const& ec, size_t)
		{
			printf("udp socket async_receive completion handler, ec = %s\n", ec.message().c_str());
		}
	);

	azmq::socket azmqsocket(ioctx, ZMQ_PULL);
	azmqsocket.set_option(azmq::socket::linger(0));
	azmqsocket.connect("tcp://127.0.0.1:12345");
	std::array<uint8_t, 1000> buffer2;
	azmqsocket.async_receive(boost::asio::buffer(buffer2),
		[](boost::system::error_code const& ec, size_t)
		{
			printf("azmq socket async_receive completion handler, ec = %s\n", ec.message().c_str());
		}
	);

	printf("timer.cancel()...\n");
	timer.cancel();
	printf("timer.cancel()... done\n");

	printf("udpsocket.cancel()...\n");
	udpsocket.cancel();
	printf("udpsocket.cancel()... done\n");

	printf("azmqsocket.cancel()...\n");
	azmqsocket.cancel();
	printf("azmqsocket.cancel()... done\n");

	printf("io_context.run()...\n");
	ioctx.run();
	printf("io_context.run()... done\n");

	return 0;
}

Actual output, azmq socket competion handler called during cancel(), instead of later like the others:

boost version: 108200
azmq version: 10002
timer.cancel()...
timer.cancel()... done
udpsocket.cancel()...
udpsocket.cancel()... done
azmqsocket.cancel()...
azmq socket async_receive completion handler, ec = Operation canceled
azmqsocket.cancel()... done
io_context.run()...
timer async_wait completion handler, ec = Operation canceled
udp socket async_receive completion handler, ec = Operation canceled
io_context.run()... done

Expected output: All completion handlers are called later through the io_service.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions