Skip to content

Commit

Permalink
Merge pull request #81 from leapmotion/feature-autorestart
Browse files Browse the repository at this point in the history
Add an AutoRestarter
  • Loading branch information
gtremper committed Aug 26, 2014
2 parents aad15dd + 119610b commit ce334e3
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 2 deletions.
175 changes: 175 additions & 0 deletions autowiring/AutoRestarter.h
@@ -0,0 +1,175 @@
#pragma once
#include "atomic_object.h"
#include "CoreRunnable.h"
#include "ExceptionFilter.h"

struct AutoRestarterConfig
{
AutoRestarterConfig(void) :
restartOnException(false),
restartOnShutdown(false),
startWhenCreated(false)
{}

// Restart the context on exception
bool restartOnException;

// Restart the context if it terminates through ordinary behavior. This will continue
// until the AutoRestarter's own exterior context has been terminated.
//
// If the context is manually shut down, it then the AutoRestarter will return nullptr
// in response to GetContext until Regenerate is called
bool restartOnShutdown;

// Once the context is created (or recreated), start it up
bool startWhenCreated;
};

/// <summary>
/// Holds a shared pointer to a CoreContext and monitors that context for termination
/// </summary>
/// <remarks>
/// The behavior of this type is configurable on construction. This type always creates
/// the Sigil-holding context in its constructor, but will wait until the outer scope is
/// initialized before attempting to start the child context up.
///
/// This type does not prevent other contexts with a matching Sigil from being created
/// in the current context.
/// </remarks>
template<class Sigil>
class AutoRestarter:
public CoreRunnable,
public ExceptionFilter
{
public:
AutoRestarter(AutoRestarterConfig& config):
config(config)
{
GenerateContext();
}

const AutoRestarterConfig config;

// CoreRunnable overrides:
bool Start(std::shared_ptr<Object> outstanding) override {
// Start the enclosed context, do nothing else
auto ctxt = GetContext();
if(ctxt && config.startWhenCreated)
ctxt->Initiate();
return true;
}

void Stop(bool graceful) override {
std::lock_guard<std::mutex> lk(m_lock);
m_context.reset();
}
bool IsRunning(void) const override { return false; }
bool ShouldStop(void) const override { return true; }
void Wait(void) override {}

private:
mutable std::mutex m_lock;
std::shared_ptr<CoreContext> m_context;

class Monitor:
public ContextMember,
public CoreRunnable
{
public:
Monitor(AutoRestarter<Sigil>& ar) :
ar(ar)
{}

// Parent restarter, we hand control here when we're stopped
AutoRestarter<Sigil>& ar;

bool Start(std::shared_ptr<Object> outstanding) override {
m_outstanding = outstanding;
return true;
}

void Stop(bool graceful) override {
ar.OnContextStopped(*this);
m_outstanding.reset();
}

std::shared_ptr<Object> m_outstanding;

bool IsRunning(void) const override { return false; }
bool ShouldStop(void) const override { return false; }
void Wait(void) {}
};

protected:
void GenerateContext(void) {
// Create the new context before releasing the old one
AutoCreateContextT<Sigil> context;
AutoRequired<Monitor> arm(context, *this);

// Swap over our shared pointer
bool bIsRestarting;
{
std::lock_guard<std::mutex> lk(m_lock);
bIsRestarting = m_context != nullptr;
m_context = context;
}

// Start up only if Generate is being called in a regenerative setting, otherwise
// we don't want to try to initiate before our own enclosing context is started.
if(bIsRestarting && config.startWhenCreated)
context->Initiate();
}

/// <summary>
/// Called by the monitor context member when a context has been stopped
/// </summary>
virtual void OnContextStopped(const ContextMember& monitor) {
{
std::lock_guard<std::mutex> lk(m_lock);
if(m_context != monitor.GetContext())
// Only reset if we need to
return;
m_context.reset();
}

if(config.restartOnShutdown)
GenerateContext();
}

void Filter(void) override {
if(config.restartOnException)
GenerateContext();
}

void Filter(const JunctionBoxBase* pJunctionBox, Object* pRecipient) override {
Filter();
}

public:
/// <returns>
/// A valid context pointer to a context with sigil type Sigil
/// </returns>
/// <remarks>
/// Bolt users beware: This method will always return an out-of-date value, or possibly even
/// nullptr, because bolts are fired before the context pointer is returned to the creating
/// entity.
///
/// This will return nullptr if the current context has been terminated
/// </remarks>
std::shared_ptr<CoreContext> GetContext(void) const {
return std::lock_guard<std::mutex>(m_lock), m_context;
}

/// <summary>
/// Shuts down the attached interior context and creates a new one
/// </summary>
void Regenerate(void) {
auto ctxt = GetContext();
if(ctxt)
ctxt->SignalShutdown();

if(!config.restartOnShutdown)
// Won't restart automatically, we have to do things ourselves
GenerateContext();
}
};
5 changes: 3 additions & 2 deletions src/autowiring/CMakeLists.txt
Expand Up @@ -6,8 +6,7 @@ set(Autowiring_SRCS
AnySharedPointer.h
AutoAnchor.h
AutoCheckout.h
NewAutoFilter.h
NewAutoFilter.cpp
AutoRestarter.h
AutoFuture.h
AutoFuture.cpp
CoreJob.h
Expand Down Expand Up @@ -94,6 +93,8 @@ set(Autowiring_SRCS
SatCounter.h
MicroAutoFilter.h
MicroBolt.h
NewAutoFilter.h
NewAutoFilter.cpp
SharedPointerSlot.h
SlotInformation.h
SlotInformation.cpp
Expand Down
97 changes: 97 additions & 0 deletions src/autowiring/test/AutoRestarterTest.cpp
@@ -0,0 +1,97 @@
// Copyright (C) 2012-2014 Leap Motion, Inc. All rights reserved.
#include "stdafx.h"
#include "TestFixtures/SimpleObject.hpp"
#include <autowiring/autowiring.h>
#include <autowiring/AutoRestarter.h>
#include <autowiring/CoreThread.h>

class AutoRestarterTest : public testing::Test {};

class RestartingSigil {};

class CreationDetectionBolt:
public Bolt<RestartingSigil>
{
public:
CreationDetectionBolt(void):
called(false),
nContextsCreated(0)
{}

void ContextCreated(void) override {
called = true;
nContextsCreated++;
}

size_t nContextsCreated;
bool called;
};

class ThrowsAnExceptionFirstTime:
public CoreThread
{
public:
void Run(void) override {
AutowiredFast<CreationDetectionBolt> cdb;
if(cdb)
if(!cdb->nContextsCreated++)
throw std::runtime_error("Crashing for no reason!");
}
};

TEST_F(AutoRestarterTest, RestarterCanExistInStoppedContext) {
AutoRestarterConfig cfg;
cfg.startWhenCreated = true;
AutoConstruct<AutoRestarter<RestartingSigil>> restarter(cfg);
}

TEST_F(AutoRestarterTest, RestartsOnException) {
AutoCurrentContext()->Initiate();

// Use a bolt facility to attach one of our text fixtures:
AutoCurrentContext()->BoltTo<ThrowsAnExceptionFirstTime, RestartingSigil>();

// Create our bolt and the restarter
AutoRequired<CreationDetectionBolt> bolt;

AutoRestarterConfig cfg;
cfg.restartOnException = true;
cfg.restartOnShutdown = false;
cfg.startWhenCreated = true;
AutoConstruct<AutoRestarter<RestartingSigil>> restarter(cfg);

// Verify the bolt got called:
ASSERT_TRUE(bolt->called) << "Bolt was not called even though a context restarter was present in the current context";

// Verify subcontext properties:
auto subCtxt = restarter->GetContext();
ASSERT_TRUE(subCtxt != nullptr) << "Restarter did not correctly create a subcontext";
ASSERT_TRUE(subCtxt->Is<RestartingSigil>()) << "Generated subcontext was not marked with the right sigil";
ASSERT_TRUE(subCtxt->IsInitiated()) << "Generated subcontext should have been prospectively started, but was not";

// Terminate the subcontext directly:
subCtxt->SignalShutdown();

// Verify that this causes the restarter to release its hold on the subcontext:
auto newSubCtxt = restarter->GetContext();
ASSERT_NE(subCtxt, newSubCtxt) << "Restarter did not release a terminated subcontext";
ASSERT_FALSE(newSubCtxt) << "Restarter should not have attempted to create any new contexts on teardown";
}

TEST_F(AutoRestarterTest, RestartsOnShutdown) {
AutoCurrentContext()->Initiate();

// Create the restarter
AutoRestarterConfig cfg;
cfg.restartOnShutdown = true;
cfg.startWhenCreated = true;
AutoConstruct<AutoRestarter<RestartingSigil>> restarter(cfg);

// Terminate the restarter's subcontext:
auto subCtxt = restarter->GetContext();
subCtxt->SignalShutdown();

// New context should be created immediately
ASSERT_NE(subCtxt, restarter->GetContext()) << "Restarter incorrectly held original context beyond shutdown";
ASSERT_TRUE(restarter->GetContext() != nullptr) << "Restarter did not correctly generate a new context after termination";
}
7 changes: 7 additions & 0 deletions src/autowiring/test/CMakeLists.txt
Expand Up @@ -5,6 +5,7 @@ set(AutowiringTest_SRCS
AutoFilterTest.cpp
AutoInjectableTest.cpp
AutoPacketFactoryTest.cpp
AutoRestarterTest.cpp
AutowiringTest.cpp
AutowiringUtilitiesTest.cpp
BasicThreadTest.cpp
Expand Down Expand Up @@ -68,6 +69,12 @@ set_property(TARGET AutowiringFixture PROPERTY FOLDER "Autowiring")
ADD_MSVC_PRECOMPILED_HEADER("stdafx.h" "stdafx.cpp" AutowiringTest_SRCS)
add_executable(AutowiringTest ${AutowiringTest_SRCS})
target_link_libraries(AutowiringTest Autowiring AutowiringFixture AutoTesting)

# Link AutoNet if we've got it
if(AUTOWIRING_BUILD_AUTONET)
target_link_libraries(AutowiringTest AutoNet)
endif()

set_property(TARGET AutowiringTest PROPERTY FOLDER "Autowiring")

# This is a unit test, let CMake know this
Expand Down

0 comments on commit ce334e3

Please sign in to comment.