Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #81 from leapmotion/feature-autorestart
Add an AutoRestarter
- Loading branch information
Showing
4 changed files
with
282 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters