Skip to content

Instantly share code, notes, and snippets.

@rukletsov
Created February 1, 2017 02:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rukletsov/4ba5a70e74c50ae8229a19f7558055ff to your computer and use it in GitHub Desktop.
Save rukletsov/4ba5a70e74c50ae8229a19f7558055ff to your computer and use it in GitHub Desktop.
A test demonstrating a deadlock triggered by removing callbacks from a satisfied future.
#include <iostream>
#include <process/clock.hpp>
#include <process/future.hpp>
#include <process/id.hpp>
#include <process/process.hpp>
#include <process/shared.hpp>
#include <stout/os.hpp>
class SatisfierProcess : public process::Process<SatisfierProcess>
{
public:
SatisfierProcess() : ProcessBase(process::ID::generate("__satisfier__")) {}
virtual void finalize() { promise.discard(); }
Future<Nothing> future() { return promise.future(); }
void satisfy()
{
std::cout << " >> SatisfierProcess: satisfying the promise" << std::endl;
promise.set(Nothing());
}
process::Promise<Nothing> promise;
};
class Satisfier
{
public:
Satisfier()
{
process = new SatisfierProcess();
process::spawn(process);
}
~Satisfier()
{
std::cout << " > ~Satisfier()" << std::endl;
process::terminate(process);
process::wait(process);
delete process;
}
Future<Nothing> future() const { return dispatch(process, &SatisfierProcess::future); }
void satisfy() const { dispatch(process->self(), &SatisfierProcess::satisfy); }
SatisfierProcess* process;
};
TEST(CircularDependencyTest, FutureCallbackRemovalTriggersDeadlock)
{
{
// This shared pointer will go out of scope and will
// only be referenced from the `.onAny` callback.
process::Shared<Satisfier> s(new Satisfier);
s->future()
// Callback with a circular dependency. When it finishes,
// the only reference to `s` is in the callback itself.
.onAny(process::defer([s](const Future<Nothing>&) {
std::cout << " | First callback finished" << std::endl;
return;
}))
// Callback that takes some time to process. Note the absence
// of `defer` here: this callback is executed in the same context
// where the promise was set, i.e. `s->process->self()` and ensures
// the first callback has already finished when `clearAllCallbacks`
// is called. `clearAllCallbacks` removes the last reference to the
// `Satisfier` instance and hence calls its d-tor, i.e. `Satisfier`'s
// d-tor is called from the `Satisfier` context => deadlock.
.onAny([](const Future<Nothing>&) {
std::cout << " | Second callback started" << std::endl;
os::sleep(Seconds(2));
std::cout << " | Second callback finished" << std::endl;
return;
});
s->satisfy();
}
// Wait for all actors to process messages in their queues. Hint:
// this will not happen because one actor is waits on himself.
process::Clock::pause();
process::Clock::settle();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment