2

I'm trying to learn io_service and work with shared pointers. I want the code to work infinitely until I call stop method or sth like this. Unfortunately after seeing workHandler's output on the screen the program shutdowns. Can anybody explain why this happen?

#include <boost/asio.hpp>
#include <iostream>
#include <atomic>
#include <memory>
#include <thread>
#include <vector>

class Service : public std::enable_shared_from_this<Service> {
    std::shared_ptr<boost::asio::io_service> _service;
    std::shared_ptr<boost::asio::io_service::work> _work;
    std::vector<std::thread> _threads;
    std::atomic<bool> _started{false};

public:
    Service() 
    : _service(std::make_shared<boost::asio::io_service>()),
    _work(std::make_shared<boost::asio::io_service::work>(*_service))
    {}

    void start() { 
        auto self(this->shared_from_this());
        auto startHandler = [this, self]() {
            std::cout << "StartHandler\n";
            while(!_started) _service->run();
        };

        _threads.emplace_back(std::thread(startHandler));
    }

    std::shared_ptr<boost::asio::io_service>& get() { return _service; }
};

class Worker : public std::enable_shared_from_this<Worker> {
    std::shared_ptr<Service> _service;
    std::shared_ptr<boost::asio::io_service> _io_service;

public:
    Worker(const std::shared_ptr<Service>& service)
    : _service(service),
    _io_service(_service->get())
    {}

    void work() {
        auto self(this->shared_from_this());
        auto workHandler = [this, self]() {
            std::cout << "WorkHandler\n";
        };

        _io_service->post(workHandler);
    }
};

int main() {
    auto ser = std::make_shared<Service>();
    ser->start();
    auto worker = std::make_shared<Worker>(ser);
    worker->work();
}
Mateusz
  • 37
  • 6
  • [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/q/25385173/5910058) – Jesper Juhl Apr 25 '20 at 18:14
  • I have debugged the code and unfortunately I have no clue why it exits. As I understand io_service should always have sth to do thankfully to io_service::work object. Here it ends after workHandler so that's why I put my question. – Mateusz Apr 25 '20 at 18:33
  • The code does what you've written. You create objects then they are destroyed at the end of the scope. Why do you think it should stop before exiting? The std::thread that you never join will also help by promptly calling std::terminate. – rustyx Apr 25 '20 at 18:58
  • 1
    Well the captured shared_from_this should keep it alive, @rustyx, that's probably the point of the question. – sehe Apr 25 '20 at 21:07

1 Answers1

2

You're running into undefined behaviour.

Your handlers capture shared pointers to the Service/Work objects alright. But nothing stops main from exiting, which will run exit handlers and tear down global library infrastructure. This is not what you want.

The problems are caused by an over-use of shared pointers. Shared pointers are ONLY good for shared ownership. In the majority of your code there is no shared ownership (main owns the Service!). Simplify:

Live On Coliru

#include <boost/asio.hpp>
#include <boost/optional.hpp>
#include <iostream>
#include <memory>
#include <thread>
#include <list>

namespace ba = boost::asio;

class Service {
    ba::io_service _service;
    boost::optional<ba::io_service::work> _work {_service};
    std::list<std::thread> _threads;

public:
    ~Service() {
        _work.reset(); // allow service to complete
        for (auto& thread : _threads)
            if (thread.joinable())
                thread.join();
    }

    void start() { 
        _threads.emplace_back([this] {
            _service.run();
        });
    }

    ba::io_service& get() { return _service; } 
};

class Worker : public std::enable_shared_from_this<Worker> {
    ba::io_service& _io;

public:
    Worker(Service& service) : _io(service.get()) {}

    void work() {
        auto self(shared_from_this());
        auto workHandler = [self]() {
            std::cout << "WorkHandler " << std::endl;
        };

        _io.post(workHandler);
    }
};

int main() {
    Service ser;
    ser.start();

    std::make_shared<Worker>(ser)->work();
}

Prints

WorkHandler

But most importantly: doesn't invoke UB and exits cleanly through joining the threads.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Off-topic: Using 1.67+ interfaces brings some subtle changes http://coliru.stacked-crooked.com/a/b866368f9a4650f0 – sehe Apr 25 '20 at 21:56