Автономная библиотека ASIO по-разному работает в системах OSX и Linux после ошибки записи - PullRequest
0 голосов
/ 02 марта 2020

Я заметил, что в OSX функция asio :: async_write всегда вызывает обработчик обратного вызова. Но на linux (Ubuntu 18.04) после того, как операция async_write завершается с ошибкой 3 раза (сброс соединения по одноранговому или сломанному каналу), обратный вызов обработчика больше не вызывается после следующего вызова async_write.

Пожалуйста, посмотрите на Пример кода:

    asio::io_service ioService;

    asio::ip::tcp::resolver resolver(ioService);

    // ---- Initialize server -----
    auto acceptor = make_unique<asio::ip::tcp::acceptor>(ioService,
        resolver.resolve(asio::ip::tcp::resolver::query(asio::ip::tcp::v4(), "localhost", "12345"))->endpoint());;
    asio::ip::tcp::socket serverSocket(ioService);
    std::promise<void> connectedPromise;
    std::promise<void> disconnectedPromise;
    std::vector<uint8_t> readBuffer(1);
    acceptor->async_accept(serverSocket, [&](asio::error_code errorCode) {
        std::cout << "Socket accepted!" << std::endl;
        connectedPromise.set_value();
        serverSocket.async_read_some(asio::buffer(readBuffer), [&](asio::error_code errorCode, std::size_t length) {
            if (errorCode) {
                std::cout << "Read error: " << errorCode.message() << std::endl;
                disconnectedPromise.set_value();
            }
        });
    });

    // ----- Initialize client --------
    asio::ip::tcp::socket clientSocket(ioService);
    asio::connect(clientSocket, resolver.resolve({asio::ip::tcp::v4(), "localhost", "12345"}));

    // ----- Start io service loop
    std::thread mainLoop([&]() {
        ioService.run();
    });

    connectedPromise.get_future().get(); // Wait until connected

    // ----- Perform 10 async_write operations with 100 ms delay --------

    std::promise<void> done;
    std::atomic<int> writesCount{0};
    std::vector<uint8_t> writeBuffer(1);

    std::function<void (const asio::error_code&, std::size_t)> writeHandler = [&](const asio::error_code& errorCode, std::size_t) -> void {
        if (errorCode) {
            std::cout << errorCode.message() << std::endl;
        }
        if (++writesCount < 10) {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            asio::async_write(serverSocket, asio::buffer(writeBuffer), writeHandler);
        } else {
            done.set_value();
        }
    };

    asio::async_write(serverSocket, asio::buffer(writeBuffer), writeHandler);

    clientSocket.close(); // Perform disconnect from client side
    disconnectedPromise.get_future().get(); // Wait until disconnected

    std::cout << "Waiting for all operations complete" << std::endl;
    done.get_future().get(); // Wait until all 10 async_write operations complete
    std::cout << "All operations complete" << std::endl;

    ioService.stop();
    mainLoop.join();

Вывод в OSX:

Socket accepted!
Broken pipe
Read error: Connection reset by peer
Broken pipe
Waiting for all operations complete
Broken pipe
Broken pipe
Broken pipe
Broken pipe
Broken pipe
Broken pipe
Broken pipe
All operations complete

Вывод в Ubuntu 18.04:

Socket accepted!
Read error: End of file
Connection reset by peer
Waiting for all operations complete
Broken pipe
Broken pipe

Linux версия зависает на строке done.get_future().get(), потому что Обработчик завершения async_write не вызывается после нескольких ошибок сломанной трубы. Я ожидаю, что любая операция async_write должна привести к вызову обработчика независимо от состояния сокета, как в версии OSX. Это ошибка в linux версии?

Версия Asio: 1.14.0 (автономная версия)

1 Ответ

0 голосов
/ 02 марта 2020

У вас есть условие гонки на ioService.run().

Ссылка сообщает:

Функциональные блоки run () до всех работ завершены закончено и больше нет обработчиков для отправки .

Вы должны вызвать reset() на ioService, если служба перестала работать из-за отсутствия обработчиков.

Обычный выход из функции run () подразумевает, что объект io_context остановлен (функция останавливается () возвращает true). Последующие вызовы run (), run_one (), poll () или poll_one () будут возвращаться немедленно, если не было предшествующего вызова restart ().

На диаграмме ниже показано, где происходит условие гонки:

main thread                                   background thread

[1] async_accept

[2] ioService.run()
                                              [3] handler for async_accept is called 
                                                  connectedPromise.set_value();
                                              [4] async_read_some 

[5] connectedPromise.get_future().get();

---> now here is a problem 
                   What [6.a] or [6.b] will be called as first?

[6.a] async_write which can push 
      a new handler to be processed      
                                       or 
                                          [6.b] handler for async_read_some 
                                               if this handler was called,
                                               ioService::run() ends, and you have to call reset on 
                                               it to accept new incoming handlers

(в квадратных скобках указаны все шаги по времени)

В вашем случае 6.b происходит. Обработчик для async_read_some вызывается первым, и в этом обработчике вы не запускаете никаких новых обработчиков. В результате ioService::run() останавливается, и обработчик для async_write не будет вызываться.

Попробуйте использовать executor_work_guard , чтобы предотвратить остановку ioService::run(), когда нет обработчиков для отправки.

...