Suppose you have some external synchronous code you cannot modify, and you require it to run async but also require it to be cancellable. If the external code is blocking then I have two options.
A) Fool the user and let my async method return immediately on cancellation, well aware that the code is still running to completion somewhere.
B) Cancel execution
I would like to implement an interface for option B
namespace externallib {
std::uint64_t timeconsuming_operation()
{
std::uint64_t count = 0;
for (auto i = 0; i < 1E+10; ++i)
{
count++;
}
return count;
}
}
template <typename R>
struct async_operation
{
struct CancelledOperationException
{
std::string what() const
{
return what_;
}
private:
std::string what_{ "Operation was cancelled." };
};
template<typename Callable>
async_operation(Callable&& c)
{
t_ = std::thread([this, c]()
{
promise_.set_value(c()); // <-- Does not care about cancel(), mostly because c() hasn't finished..
});
}
std::future<R> get()
{
return promise_.get_future();
}
void cancel()
{
promise_.set_exception(std::make_exception_ptr(CancelledOperationException()));
}
~async_operation()
{
if (t_.joinable())
t_.join();
}
private:
std::thread t_;
std::promise<R> promise_;
};
void foo()
{
async_operation<std::uint64_t> op([]()
{
return externallib::timeconsuming_operation();
});
using namespace std::chrono_literals;
std::this_thread::sleep_for(5s);
op.cancel();
op.get();
}
In the code above I cannot wrap my head around the limitation of external code being blocking, how, if at all, is it possible to cancel execution early?
Short answer:
Don't cancel/terminate thread execution unless it is mission critical. Use approach "A" instead.
Long answer:
As #Caleth noted, there is no standard nor cross platform way to do this. All you can do is to get a native handle to a thread and use platform specific function. But there are some important pit falls.
win32
You may terminate a thread with TerminateThread function, but:
stack variables will not be destructed
thread_local variables will not be destructed
DLLs will not be notified
MSDN says:
TerminateThread is a dangerous function that should only be used in
the most extreme cases.
pthread
Here situation is slightly better. You have a chance to free your resources when pthread_cancel is got called, but:
By default, target thread terminates on cancellation points. It means that you cannot cancel a code that doesn't have any cancellation point. Basically, for(;;); won't be canceled at all.
Once cancellation point is reached, implementation specific exception is thrown, so resources can be gracefully freed.
Keep in mind, that this exception can be caught by try/catch, but it's required to be re-thrown.
This behavior can be disabled by pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr);. But in case cancellation point is not met, resources won't be freed (as for win32)
Example
#include <iostream>
#include <thread>
#include <chrono>
#if defined(_WIN32)
#include <Windows.h>
void kill_thread(HANDLE thread) {
TerminateThread(thread, 0);
}
#else
#include <pthread.h>
void kill_thread(pthread_t thread) {
pthread_cancel(thread);
}
#endif
class my_class {
public:
my_class() { std::cout << "my_class::my_class()" << std::endl; }
~my_class() { std::cout << "my_class::~my_class()" << std::endl; }
};
void cpu_intensive_func() {
#if !defined(_WIN32)
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, nullptr);
#endif
my_class cls;
for(;;) {}
}
void io_func() {
my_class cls;
int a;
std::cin >> a;
}
void io_func_with_try_catch() {
my_class cls;
try {
int a;
std::cin >> a;
} catch(...) {
std::cout << "exception caught!" << std::endl;
throw;
}
}
void test_cancel(void (*thread_fn) (void)) {
std::thread t(thread_fn);
std::this_thread::sleep_for(std::chrono::seconds(1));
kill_thread(t.native_handle());
t.join();
std::cout << "thread exited" << std::endl;
std::cout << "--------------------" << std::endl;
}
int main() {
test_cancel(cpu_intensive_func);
test_cancel(io_func);
test_cancel(io_func_with_try_catch);
return 0;
}
You may see that:
The destructor is never called on windows.
Removing of pthread_setcanceltype leads to hang.
The internal pthread exception could be caught.
There is no portable way to end a thread before it wants to.
Depending on your platform, there may be ways of ending a thread, which you will probably need to get std::thread::native_handle to utilise. This is highly likely to lead to undefined behaviour, so I don't recommend it.
You can run that external synchronous code in another process and terminate that entire process. This way the interruption won't affect your process and cause undefined behaviour.
Related
I have one main thread that will send an async job to the task queue on the other thread. And this main thread can trigger a destroy action at any time, which could cause the program to crash in the async task, a piece of very much simplified code like this:
class Bomb {
public:
int trigger;
mutex my_mutex;
};
void f1(Bomb *b) {
lock_guard<std::mutex> lock(b->my_mutex); //won't work! Maybe b have been destructed!
sleep(1);
cout<<"wake up.."<<b->trigger<<"..."<<endl;
}
int main()
{
Bomb *b = new Bomb();
b->trigger = 1;
thread t1(f1, b);
sleep(1);
//lock here won't work
delete b;//in actual case it is triggered by outside users
t1.join();
return 0;
}
The lock in f1 won't work since the destructor can be called first and trying to read mutex will crash. Put lock in destructor or before the delete also won't work for the same reason.
So is there any better way in this situation? Do I have to put mutex in the global scope and inside destructor to solve the issue?
In code, my comment looks like this :
#include <future>
#include <mutex>
#include <iostream>
#include <chrono>
#include <thread>
// do not use : using namespace std;
class Bomb
{
public:
void f1()
{
m_future = std::async(std::launch::async,[this]
{
async_f1();
});
}
private:
void async_f1()
{
using namespace std::chrono_literals;
std::lock_guard<std::mutex> lock{ m_mtx };
std::cout << "wake up..\n";
std::this_thread::sleep_for(1s);
std::cout << "thread done.\n";
}
std::future<void> m_future;
std::mutex m_mtx;
};
int main()
{
{
std::cout << "Creating bomb\n";
Bomb b; // no need to use unecessary new
b.f1();
}
std::cout << "Bomb destructed\n";
return 0;
}
I am trying to make kind of "running check" to avoid running one function multiple times at once it is for my another project. I have to use while() and detach() , the problem is I don't really know how can I check if thread is joinable(), because when I am not doing this this, the error comes out: Unhandled exception at 0x7632A842 in dasd.exe: Microsoft C++ exception: std::system_error at memory location 0x009BF614. but when I use code below I am getting no errors, but loop won't work
#include <future>
#include <thread>
#include <chrono>
#include <iostream>
using namespace std::chrono_literals;
void Thing()
{
std::this_thread::sleep_for(3s);
std::cout << "done\n";
}
int main()
{
std::packaged_task<void()> task(Thing);
auto future = task.get_future();
std::thread ac(std::move(task));
while (true)
{
std::cout << ac.joinable() << std::endl;
if (future.wait_for(1ms) == std::future_status::ready && ac.joinable())
{
ac.detach();
std::cout << "good\n";
}
std::this_thread::sleep_for(1s);
}
}
the output is:
1
1
1
done
1
good
0
0
.......
the question is: how can i make successful loop avoiding errors? I am trying for such as long time, and i think it is about something what i just don't know...
Thank You in advance
Don't detach().
People use detach() far, far too often.
It should only be used in relatively rare circumstances. A thread running after the end of main is not a good idea, and without formal synchronization with the end of the thread, preventing that is basically impossible.
There are two ways to do this with a detach()ed thread -- the _at_thread_exit methods of std::promise, or using OS-specific APIs.
A thread pool might be what you want.
template<class T>
struct threadsafe_queue {
std::optional<T> try_pop();
T wait_and_pop();
void push(T);
std::deque<T> pop_all();
private:
mutable std::mutex m;
std::condition_variable cv;
std::deque<T> data;
};
struct thread_pool {
explicit thread_pool( std::size_t number_of_threads );
std::size_t thread_count() const;
void add_thread(std::size_t n=1);
void abort_all_tasks_and_threads();
void wait_for_empty_queue();
~thread_pool();
template<class F>
std::future<std::invoke_result_t<F>> add_task( F f );
private:
using task=std::future<void()>; // or std::packaged_task<void> or something custom
std::vector<std::thread> threads;
threadsafe_queue< task > tasks;
};
something vaguely like that.
Then make a 1 thread thread-pool, and shove tasks into that.
I want to create a thread that can be interrupted while waiting (it waits data from other processes and I want to stop the process in nice way).
I've read the 9.2 part of C++ Concurrency in Action 2nd Edition, and I've tried to implement that ideas, but I've some problem and I don't know where to check.
This is my code based on that example:
#include <iostream>
#include <stdexcept>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <future>
// Exception that should be raised when there's an interruption.
// It's raised when the thread is interrupted, so we can catch
// it and finish the thread execution.
class InterruptedException : public std::runtime_error {
public:
InterruptedException(const std::string& message) : std::runtime_error(message) {}
virtual ~InterruptedException() {}
};
// Interrupt flag. This class represents a local-thread flag that
// tells if the thread is interrupted or not.
class InterruptFlag {
public:
InterruptFlag() :
m_threadConditionVariable(nullptr),
m_threadConditionVariableAny(nullptr) {}
void set() {
m_flag.store(true, std::memory_order_relaxed);
std::lock_guard<std::mutex> lk(m_setClearMutex);
if (m_threadConditionVariable) {
m_threadConditionVariable->notify_all();
}
else if (m_threadConditionVariableAny) {
m_threadConditionVariableAny->notify_all();
}
}
template <typename Lockable>
void wait(std::condition_variable_any& cv, Lockable& lk) {
struct CustomLock {
InterruptFlag* m_self;
Lockable& m_lk;
CustomLock(InterruptFlag* self, std::condition_variable_any& cond, Lockable& lk) :
m_self(self),
m_lk(lk) {
m_self->m_setClearMutex.unlock();
m_self->m_threadConditionVariableAny = &cond;
}
void unlock() {
m_lk.unlock();
m_self->m_setClearMutex.unlock();
}
void lock() {
std::lock(m_self->m_setClearMutex, lk);
}
~CustomLock() {
m_self->m_threadConditionAny = nullptr;
m_self->m_setClearMutex.unlock();
}
};
CustomLock cl(this, cv, lk);
InterruptPoint();
cv.wait(cl);
InterruptPoint();
}
void setConditionVariable(std::condition_variable& cv) {
std::lock_guard<std::mutex> lk(m_setClearMutex);
m_threadConditionVariable = &cv;
}
void clearConditionVariable() {
std::lock_guard<std::mutex> lk(m_setClearMutex);
m_threadConditionVariable = nullptr;
}
bool isSet() const {
return m_flag.load(std::memory_order_relaxed);
}
private:
std::atomic<bool> m_flag;
std::condition_variable* m_threadConditionVariable;
std::condition_variable_any* m_threadConditionVariableAny;
std::mutex m_setClearMutex;
};
// Thread-local interrupt flag instance. The variable should be
// created for every thread, since it's thread_local.
thread_local InterruptFlag ThisThreadInterruptFlag;
// Convenience class for cleaning the flag due to RAII.
struct ClearConditionVariableOnDestruct {
~ClearConditionVariableOnDestruct() {
ThisThreadInterruptFlag.clearConditionVariable();
}
};
// Function that throws the exception that tells that the thread
// is interrupted. For doing it checks the state of ThisThreadInterruptFlag.
void InterruptionPoint() {
if (ThisThreadInterruptFlag.isSet()) {
throw InterruptedException("Interrupted");
}
}
// Function that must be used inside the thread function body for waiting.
// It waits for the condition variable, when it notifies from other threads,
// but it also notifies if the thread is interrupted.
void InterruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lk) {
InterruptionPoint();
ThisThreadInterruptFlag.setConditionVariable(cv);
ClearConditionVariableOnDestruct guard;
InterruptionPoint();
cv.wait_for(lk, std::chrono::milliseconds(1));
InterruptionPoint();
}
// This class represents the interruptible thread. It adds a interrupt()
// method that when called interupts the thread execution, if it's waiting
// at some point where InterruptibleWait function is locked.
class Interruptible {
public:
template <typename FunctionType>
Interruptible(FunctionType f) {
std::promise<InterruptFlag*> p;
m_internalThread = std::thread([f, &p]() {
p.set_value(&ThisThreadInterruptFlag);
try {
f();
}
catch (InterruptedException) {
}
});
m_flag = p.get_future().get();
}
void join() {
m_internalThread.join();
}
void detach() {
m_internalThread.detach();
}
bool joinable() const {
return m_internalThread.joinable();
}
void interrupt() {
if (m_flag) {
m_flag->set();
}
}
private:
std::thread m_internalThread;
InterruptFlag* m_flag;
};
std::mutex mtx;
std::unique_lock<std::mutex> lk(mtx);
int main(int argc, char* argv[]) {
std::cout << "Interrupting thread example" << std::endl;
bool test = false;
std::condition_variable cv;
auto f = [&cv, &test]() {
test = true;
InterruptibleWait(cv, lk);
// Since it locks forever, it should never reach this point.
test = false;
};
Interruptible interruptibleThread(f);
std::this_thread::sleep_for(std::chrono::milliseconds(30));
// We interrupt the function while it's blocked in InterruptibleWait
interruptibleThread.interrupt();
interruptibleThread.join();
std::cout << "test value is " << std::boolalpha << test << ". It should be true." << std::endl;
return 0;
}
Basically I create a Interruptible class representing a thread that can be interrupted. I interrupt it during its execution by calling its interrupt() method. The thread can be interrupted if it's locked with in a InterruptibleWait function call. This function behave like a std::condition.wait(), in fact it wants a reference to it, but it also handle the interruption flag.
If I start the program. I obtain an error from Visual Studio when running.
I don't know what I'm doing wrong. What should I do in order to make InterruptibleWait work correctly?
My best guess based on the given information:
The exception isn't caught in the thread entry point function, and escapes that function. When this happens in a thread started by std::thread, abort is called for you (indirectly through std::terminate) by the std::thread implementation, as required by the standard. To fix this, try catching all exceptions in the function passed to std::thread.
See the cppreference articles on std::thread and std::terminate
This is a fairly involved question, and unfortunately may involve compiling and running some code on your machine. Moreover, it's neither pure Objective-C nor it's C++, but rather using C++ from within Objective-C (i believe one might call it Objective-C++).
So, I'm using this library in my code. There's a class called ThreadsafeFace. For simplicity, one should think of it as an asynchronous socket (because that's what it uses underneath, in fact - unix socket). But the code I'm dealing with doesn't even get to the point where socket is being used for sending or receiving data.
Anyway, I have a C++ wrapper class FaceProcessor which looks like this:
FaceProcessor.h:
#include <stdio.h>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
namespace ndn {
class Face;
}
class FaceProcessorImpl;
class FaceProcessor {
public:
FaceProcessor(std::string host);
~FaceProcessor();
void start();
void stop();
bool isProcessing();
// non blocking
void dispatchSynchronized(boost::function<void(boost::shared_ptr<ndn::Face>)> dispatchBlock);
// blocking
void performSynchronized(boost::function<void(boost::shared_ptr<ndn::Face>)> dispatchBlock);
static boost::shared_ptr<FaceProcessor> forLocalhost();
private:
boost::shared_ptr<FaceProcessorImpl> _pimpl;
};
FaceProcessor.cpp:
#include "face-processor.hpp"
#include <boost/function.hpp>
#include <boost/asio.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <ndn-cpp/threadsafe-face.hpp>
using namespace ndn;
using namespace boost;
using namespace boost::asio;
class FaceProcessorImpl : public enable_shared_from_this<FaceProcessorImpl>{
public:
FaceProcessorImpl(std::string host);
~FaceProcessorImpl();
void start();
void stop();
bool isProcessing();
// non blocking
void dispatchSynchronized(boost::function<void(boost::shared_ptr<ndn::Face>)> dispatchBlock);
// blocking
void performSynchronized(boost::function<void(boost::shared_ptr<ndn::Face>)> dispatchBlock);
bool initFace();
void runFace();
private:
std::string host_;
shared_ptr<io_service::work> ioWork_;
io_service io_;
shared_ptr<Face> face_;
thread t_;
bool isRunningFace_;
};
shared_ptr<FaceProcessor>
FaceProcessor::forLocalhost()
{
return make_shared<FaceProcessor>("localhost");
}
FaceProcessor::FaceProcessor(std::string host):
_pimpl(make_shared<FaceProcessorImpl>(host))
{
if (_pimpl->initFace())
_pimpl->runFace();
else
throw std::runtime_error("couldn't initialize face object");
}
FaceProcessor::~FaceProcessor() {
_pimpl->stop();
_pimpl.reset();
}
void FaceProcessor::start() { _pimpl->start(); }
void FaceProcessor::stop() { _pimpl->stop(); }
bool FaceProcessor::isProcessing() { return _pimpl->isProcessing(); }
void FaceProcessor::dispatchSynchronized(function<void (shared_ptr<Face>)> dispatchBlock)
{
return _pimpl->dispatchSynchronized(dispatchBlock);
}
void FaceProcessor::performSynchronized(function<void (shared_ptr<Face>)> dispatchBlock)
{
return _pimpl->performSynchronized(dispatchBlock);
}
//******************************************************************************
FaceProcessorImpl::FaceProcessorImpl(std::string host):host_(host)
{
}
FaceProcessorImpl::~FaceProcessorImpl()
{
stop();
}
void FaceProcessorImpl::start()
{
if (!isRunningFace_)
if (initFace())
runFace();
}
void FaceProcessorImpl::stop()
{
if (isRunningFace_)
{
isRunningFace_ = false;
face_->shutdown();
std::cout << "work reset" << std::endl;
ioWork_.reset();
std::cout << "t join" << std::endl;
t_.join();
std::cout << "stopped" << std::endl;
}
}
bool FaceProcessorImpl::isProcessing()
{
return isRunningFace_;
}
void FaceProcessorImpl::dispatchSynchronized(boost::function<void (boost::shared_ptr<ndn::Face>)> dispatchBlock)
{
if (isRunningFace_)
{
shared_ptr<Face> f = face_;
io_.dispatch([dispatchBlock, f](){
dispatchBlock(f);
});
}
}
void FaceProcessorImpl::performSynchronized(boost::function<void (boost::shared_ptr<ndn::Face>)> dispatchBlock)
{
if (isRunningFace_)
{
if (this_thread::get_id() == t_.get_id())
dispatchBlock(face_);
else
{
mutex m;
unique_lock<mutex> lock(m);
condition_variable isDone;
atomic<bool> doneFlag(false);
shared_ptr<Face> face = face_;
io_.dispatch([dispatchBlock, face, &isDone, &doneFlag](){
dispatchBlock(face);
doneFlag = true;
isDone.notify_one();
});
isDone.wait(lock, [&doneFlag](){ return doneFlag.load(); });
}
}
}
bool FaceProcessorImpl::initFace()
{
try {
if (host_ == "localhost")
face_ = make_shared<ThreadsafeFace>(io_);
else
face_ = make_shared<ThreadsafeFace>(io_, host_.c_str());
}
catch(std::exception &e)
{
// notify about error
return false;
}
return true;
}
void FaceProcessorImpl::runFace()
{
ioWork_ = make_shared<io_service::work>(io_);
isRunningFace_ = false;
shared_ptr<FaceProcessorImpl> self = shared_from_this();
t_ = thread([self](){
self->isRunningFace_ = true;
while (self->isRunningFace_)
{
try {
std::cout << "io run" << std::endl;
self->io_.run();
std::cout << "io run completed" << std::endl;
self->isRunningFace_ = false;
}
catch (std::exception &e) {
// notify about error and try to recover
if (!self->initFace())
self->isRunningFace_ = false;
}
}
});
while (!isRunningFace_) ;
}
As can be seen, the wrapper hides io_service-related complexity, allowing client code to simply create processor, call start and stop at will in order to execute runloop of io_service. Whenever created, FaceProcessor starts running automatically, and stops itself upon destruction. Stopping is performed in, what I believe, a graceful manner - no calls to io_service::stop (as it may prevent scheduled handlers from being executed), but instead io_service::work is reset and thread::join() is invoked in order to wait for full thread completion.
Now, to the question.
The test code itself is fairly simple:
{
boost::shared_ptr<FaceProcessor> f = FaceProcessor::forLocalhost();
sleep(3);
}
which means, that FaceProcessor should finish in graceful manner with no problems. One can try out this code from the latest commit of this branch of the library (please, see instructions on how to build the library, it should be fairly straightforward) and executing bin/test-get-async-threadsafe example.
Now, the problem is, when I test the exact same code in Objective-C, it deadlocks, i.e. for some reason, io_service::run never returns. I use Xcode Unit Tests for testing this code and the test case looks like this:
FaceProcessorTests.mm:
#include "face-processor.hpp"
using namespace boost;
using namespace ndn;
#interface FaceProcessorTests : XCTestCase
#end
#implementation FaceProcessorTests
-(void)testTestProcessor
{
shared_ptr<FaceProcessor> f = FaceProcessor::forLocalhost();
sleep(3);
}
#end
My original FaceProcessor class was written in Objective-C++ (Objective-C with boost::asio::io_service and boost::thread) and I literally spent a week debugging this case, adding debug output to io_service (BOOST_ASIO_ENABLE_HANDLER_TRACKING) and underlying ThreadsafeFace object from the library. When I realized that exact same code works in C++, my final effort was to port FaceProcessor code over to pure C++ and use this class in Objective-C wrapper class. However this didn't help - the code still deadlocks, io_service::run() does not return in Objective-C environment.
I can't figure out why is this happening. Quite possibly, I'm just missing something fundamentally simple. One thing I noticed - when I comment out all code from FaceProcessor related to face_, nothing deadlocks in Objective-C. Since Face uses just socket underneath (in fact, it's boost::asio::local::stream_protocol::socket) - could it be that sockets may work slightly differently in Objective-C runtime?
The environment is:
MacBook Pro (Retina, 15-inch, early 2013)
MacOS Sierra 10.12.3
Xcode Version 8.2.1 (8C1002)
The project is desktop application (not iOS)
Can anybody explain me why this program does not terminate (see the comments)?
#include <boost/asio/io_service.hpp>
#include <boost/asio.hpp>
#include <memory>
#include <cstdio>
#include <iostream>
#include <future>
class Service {
public:
~Service() {
std::cout << "Destroying...\n";
io_service.post([this]() {
std::cout << "clean and stop\n"; // does not get called
// do some cleanup
// ...
io_service.stop();
std::cout << "Bye!\n";
});
std::cout << "...destroyed\n"; // last printed line, blocks
}
void operator()() {
io_service.run();
std::cout << "run completed\n";
}
private:
boost::asio::io_service io_service;
boost::asio::io_service::work work{io_service};
};
struct Test {
void start() {
f = std::async(std::launch::async, [this]() { service(); std::cout << "exiting thread\n";});
}
std::future<void> f;
Service service;
};
int main(int argc, char* argv[]) {
{
Test test;
test.start();
std::string exit;
std::cin >> exit;
}
std::cout << "exiting program\n"; // never printed
}
The real issue is that destruction of io_service is (obviously) not thread-safe.
Just reset the work and join the thread. Optionally, set a flag so your IO operations know shutdown is in progress.
You Test and Service classes are trying to share responsibility for the IO service, that doesn't work. Here's much simplified, merging the classes and dropping the unused future.
Live On Coliru
The trick was to make the work object optional<>:
#include <boost/asio.hpp>
#include <boost/optional.hpp>
#include <iostream>
#include <thread>
struct Service {
~Service() {
std::cout << "clean and stop\n";
io_service.post([this]() {
work.reset(); // let io_service run out of work
});
if (worker.joinable())
worker.join();
}
void start() {
assert(!worker.joinable());
worker = std::thread([this] { io_service.run(); std::cout << "exiting thread\n";});
}
private:
boost::asio::io_service io_service;
std::thread worker;
boost::optional<boost::asio::io_service::work> work{io_service};
};
int main() {
{
Service test;
test.start();
std::cin.ignore(1024, '\n');
std::cout << "Start shutdown\n";
}
std::cout << "exiting program\n"; // never printed
}
Prints
Start shutdown
clean and stop
exiting thread
exiting program
See here: boost::asio hangs in resolver service destructor after throwing out of io_service::run()
I think the trick here is to destroy the worker (the work member) before calling io_service.stop(). I.e. in this case the work could be an unique_ptr, and call reset() explicitly before stopping the service.
EDIT: The above helped me some time ago in my case, where the ioservice::stop didn't stop and was waiting for some dispatching events which never happened.
However I reproduced the problem you have on my machine and this seems to be a race condition inside ioservice, a race between ioservice::post() and the ioservice destruction code (shutdown_service). In particular, if the shutdown_service() is triggered before the post() notification wakes up the other thread, the shutdown_service() code removes the operation from the queue (and "destroys" it instead of calling it), therefore the lambda is never called then.
For now it seems to me that you'd need to call the io_service.stop() directly in the destructor, not postponed via the post() as that apparently doest not work here because of the race.
I was able to fix the problem by rewriting your code like so:
class Service {
public:
~Service() {
std::cout << "Destroying...\n";
work.reset();
std::cout << "...destroyed\n"; // last printed line, blocks
}
void operator()() {
io_service.run();
std::cout << "run completed\n";
}
private:
boost::asio::io_service io_service;
std::unique_ptr<boost::asio::io_service::work> work = std::make_unique<boost::asio::io_service::work>(io_service);
};
However, this is largely a bandaid solution.
The problem lies in your design ethos; specifically, in choosing not to tie the lifetime of the executing thread directly to the io_service object:
struct Test {
void start() {
f = std::async(std::launch::async, [this]() { service(); std::cout << "exiting thread\n";});
}
std::future<void> f; //Constructed First, deleted last
Service service; //Constructed second, deleted first
};
In this particular scenario, the thread is going to continue to attempt to execute io_service.run() past the lifetime of the io_service object itself. If more than the basic work object were executing on the service, you very quickly begin to deal with undefined behavior with calling member functions of deleted objects.
You could reverse the order of the member objects in Test:
struct Test {
void start() {
f = std::async(std::launch::async, [this]() { service(); std::cout << "exiting thread\n";});
}
Service service;
std::future<void> f;
};
But it still represents a significant design flaw.
The way that I usually implement anything which uses io_service is to tie its lifetime to the threads that are actually going to be executing on it.
class Service {
public:
Service(size_t num_of_threads = 1) :
work(std::make_unique<boost::asio::io_service::work>(io_service))
{
for (size_t thread_index = 0; thread_index < num_of_threads; thread_index++) {
threads.emplace_back([this] {io_service.run(); });
}
}
~Service() {
work.reset();
for (std::thread & thread : threads)
thread.join();
}
private:
boost::asio::io_service io_service;
std::unique_ptr<boost::asio::io_service::work> work;
std::vector<std::thread> threads;
};
Now, if you have any infinite loops active on any of these threads, you'll still need to make sure you properly clean those up, but at least the code specific to the operation of this io_service is cleaned up correctly.