Skip to content

Commit

Permalink
Merge pull request #866 from leapmotion/fix-transientunsatisfy
Browse files Browse the repository at this point in the history
Fix transitive unsatisfiability defect
  • Loading branch information
Veronica Zheng committed Mar 28, 2016
2 parents 1cc8da6 + 800c876 commit cdf6675
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 72 deletions.
39 changes: 13 additions & 26 deletions src/autowiring/AutoPacket.cpp
Expand Up @@ -349,11 +349,10 @@ void AutoPacket::UpdateSatisfactionUnsafe(std::unique_lock<std::mutex> lk, const
// Mark all unsatisfiable output types
for (auto unsatOutputArg : unsatOutputArgs) {
// One more producer run, even though we couldn't attach any new decorations
auto& entry = m_decoration_map[DecorationKey{unsatOutputArg->id, 0}];
entry.m_nProducersRun++;

// Now recurse on this entry
UpdateSatisfactionUnsafe(std::unique_lock<std::mutex>{m_lock}, entry);
auto& disposition = m_decoration_map[DecorationKey{unsatOutputArg->id, 0}];
if(disposition.IncProducerCount())
// Recurse on this entry
UpdateSatisfactionUnsafe(std::unique_lock<std::mutex>{m_lock}, disposition);
}
}

Expand Down Expand Up @@ -440,26 +439,11 @@ void AutoPacket::DecorateNoPriors(const AnySharedPointer& ptr, DecorationKey key
break;
}

// Decoration attaches here
disposition->m_decorations.push_back(ptr);
disposition->m_nProducersRun++;

// Uniformly advance state:
switch (disposition->m_state) {
case DispositionState::Unsatisfied:
case DispositionState::PartlySatisfied:
// Permit a transition to another state
if (disposition->IsPublicationComplete()) {
disposition->m_state = DispositionState::Complete;
UpdateSatisfactionUnsafe(std::move(lk), *disposition);
}
else
disposition->m_state = DispositionState::PartlySatisfied;
break;
default:
// Do nothing, no advancing to any states from here
break;
}
// Decoration attaches here, if it is non-null
if(ptr)
disposition->m_decorations.push_back(ptr);
if(disposition->IncProducerCount())
UpdateSatisfactionUnsafe(std::move(lk), *disposition);
}

void AutoPacket::Decorate(const AnySharedPointer& ptr, DecorationKey key) {
Expand Down Expand Up @@ -544,10 +528,13 @@ bool AutoPacket::IsUnsatisfiable(const auto_id& id) const
{
const DecorationDisposition* pDisposition = GetDisposition(DecorationKey{ id, 0 });
if (!pDisposition)
// We have never heard of this type
return false;
if (!pDisposition->m_decorations.empty())
// We have some actual decorations, we know this is not satisfiable
return false;
if (pDisposition->m_nProducersRun == pDisposition->m_publishers.size())
if (pDisposition->m_nProducersRun != pDisposition->m_publishers.size())
// Some producers have not yet run, we could still feasibly get a decoration back
return false;
return true;
}
Expand Down
13 changes: 5 additions & 8 deletions src/autowiring/AutoPacket.h
Expand Up @@ -134,7 +134,7 @@ class AutoPacket:
/// Marks the specified entry as being unsatisfiable
/// </summary>
void MarkUnsatisfiable(const DecorationKey& key);

/// <summary>
/// Marks timeshifted decorations on successor packets as unsatisfiable
/// </summary>
Expand Down Expand Up @@ -471,18 +471,15 @@ class AutoPacket:
template<class T>
void Decorate(const std::shared_ptr<T>& ptr) {
DecorationKey key(auto_id_t<T>{}, 0);
// We don't want to see this overload used on a const T
static_assert(!std::is_const<T>::value, "Cannot decorate a shared pointer to const T with this overload");
// Injunction to prevent existential loops:
static_assert(!std::is_same<T, AutoPacket>::value, "Cannot decorate a packet with another packet");
// Either decorate, or prevent anyone from decorating
if (ptr)
Decorate(AnySharedPointer(ptr), key);
else
MarkUnsatisfiable(key);
// Either decorate or abdicate
Decorate(AnySharedPointer(ptr), key);
}
/// <summary>
Expand Down Expand Up @@ -777,7 +774,7 @@ class AutoPacket:
template<typename Fx, typename... InArgs, typename... Outputs>
void Call(Fx&& fx, void (Fx::*memfn)(InArgs...) const, Outputs&... outputs) {
typedef typename make_index_tuple<Decompose<decltype(&Fx::operator())>::N>::type t_index;
// Completely unnecessary. Call will avoid making unneeded copies, and this is guaranteed
// by a unit test. Dereference your shared pointers before passing them in.
static_assert(
Expand Down
1 change: 1 addition & 0 deletions src/autowiring/AutoPacketGraph.cpp
Expand Up @@ -6,6 +6,7 @@
#include "demangle.h"
#include "SatCounter.h"
#include <algorithm>
#include <cassert>
#include <fstream>
#include <string>
#include <sstream>
Expand Down
1 change: 1 addition & 0 deletions src/autowiring/BasicThread.cpp
Expand Up @@ -9,6 +9,7 @@
#include "dispatch_aborted_exception.h"
#include "GlobalCoreContext.h"
#include "fast_pointer_cast.h"
#include <cassert>
#include ATOMIC_HEADER

static auto mainTID = std::this_thread::get_id();
Expand Down
2 changes: 1 addition & 1 deletion src/autowiring/CallExtractor.h
Expand Up @@ -121,7 +121,7 @@ struct CE<void (T::*)(Args...) const, index_tuple<N...>> :
{
typedef CESetup<Args...> t_ceSetup;
static const bool deferred = false;

template<void(T::*memFn)(Args...) const>
static void Call(const void* pObj, AutoPacket& packet) {
// Extract, call, commit
Expand Down
1 change: 1 addition & 0 deletions src/autowiring/CoreContext.cpp
Expand Up @@ -11,6 +11,7 @@
#include "NullPool.h"
#include "SystemThreadPool.h"
#include "thread_specific_ptr.h"
#include <cassert>
#include <sstream>
#include <stdexcept>

Expand Down
33 changes: 29 additions & 4 deletions src/autowiring/DecorationDisposition.h
Expand Up @@ -3,7 +3,7 @@
#include "AutowiringConfig.h"
#include "altitude.h"
#include "AnySharedPointer.h"
#include <cassert>
#include <atomic>
#include <set>
#include <vector>

Expand Down Expand Up @@ -126,15 +126,40 @@ struct DecorationDisposition
// The current state of this disposition
DispositionState m_state = DispositionState::Unsatisfied;

/// <summary>
/// Increments the number of producers run by one
/// </summary>
/// <returns>True if the decoration has become satisfied as a result of this call</returns>
bool IncProducerCount(void) {
auto producersRun = ++m_nProducersRun;

// Uniformly advance state:
switch (m_state) {
case DispositionState::Unsatisfied:
case DispositionState::PartlySatisfied:
// Permit a transition to another state
if (producersRun >= m_publishers.size()) {
m_state = DispositionState::Complete;
return true;
}

m_state = DispositionState::PartlySatisfied;
break;
default:
// Do nothing, no advancing to any states from here
break;
}
return false;
}

/// <returns>
/// True if all publishers have run on this disposition
/// </summary>
/// <remarks>
/// Publication is complete automatically on this type if there are no statically known publishers,
/// and at least one decoration has been attached to this disposition.
/// Publication is complete whenever all producers have run on the decoration.
/// </remarks>
bool IsPublicationComplete(void) const {
return !m_decorations.empty() && m_nProducersRun >= m_publishers.size();
return m_nProducersRun >= m_publishers.size();
}

void Reset(void) {
Expand Down
63 changes: 63 additions & 0 deletions src/autowiring/test/AutoFilterSatisfiabilityTest.cpp
@@ -0,0 +1,63 @@
// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved.
#include "stdafx.h"
#include <autowiring/autowiring.h>
#include "TestFixtures/Decoration.hpp"

class AutoFilterSatisfiabilityTest :
public testing::Test
{
public:
AutoFilterSatisfiabilityTest(void) {
AutoCurrentContext()->Initiate();
}

AutoRequired<AutoPacketFactory> factory;
};

TEST_F(AutoFilterSatisfiabilityTest, MarkUnsatisfiableCalls) {
auto packet = factory->NewPacket();

// This filter shouldn't be called, because it expects a reference as an input
bool bRefCalled = false;
*packet += [&bRefCalled](const Decoration<0>& dec) { bRefCalled = true; };

// This one should be called because the input is a shared pointer
bool bSharedPtrCalled = false;
*packet += [&bSharedPtrCalled](std::shared_ptr<const Decoration<0>> dec) { bSharedPtrCalled = true; };

packet->MarkUnsatisfiable<Decoration<0>>();

ASSERT_FALSE(bRefCalled) << "Reference version should not have been called";
ASSERT_TRUE(bSharedPtrCalled) << "Shared pointer version should have been called as a result of Unsatisfiable";
}

TEST_F(AutoFilterSatisfiabilityTest, TransitiveUnsatisfiability) {
// Set up the filter configuration that will create the transitive condition
auto called0 = std::make_shared<bool>(false);
*factory += [called0](Decoration<0> in, std::shared_ptr<Decoration<1>>& out) {
*called0 = true;
};

// This filter accepts Decoration<1> as an optional input argument. It should be called
// with this value set to nullptr.
auto called1 = std::make_shared<bool>(false);
*factory += [called1](std::shared_ptr<const Decoration<1>> in) {
*called1 = true;
};

// This filter won't be called at all
*factory += [] (Decoration<1>, Decoration<2>&) {};

// This verifies that we do have correct transitive unsatisfiability behavior
auto called2 = std::make_shared<bool>(false);
*factory += [called2] (std::shared_ptr<const Decoration<2>>) {
*called2 = true;
};

auto packet = factory->NewPacket();
packet->Decorate(Decoration<0>{});
ASSERT_TRUE(packet->IsUnsatisfiable<Decoration<1>>());
ASSERT_TRUE(*called1);
ASSERT_TRUE(packet->IsUnsatisfiable<Decoration<2>>());
ASSERT_TRUE(*called2);
}
2 changes: 1 addition & 1 deletion src/autowiring/test/CMakeLists.txt
Expand Up @@ -12,6 +12,7 @@ set(AutowiringTest_SRCS
AutoFilterFunctionTest.cpp
AutoFilterMultiDecorateTest.cpp
AutoFilterRvalueTest.cpp
AutoFilterSatisfiabilityTest.cpp
AutoFilterSequencing.cpp
AutoFilterTest.cpp
AutoIDTest.cpp
Expand Down Expand Up @@ -54,7 +55,6 @@ set(AutowiringTest_SRCS
SpinLockTest.cpp
TeardownNotifierTest.cpp
TypeRegistryTest.cpp
SatisfiabilityTest.cpp
ScopeTest.cpp
SnoopTest.cpp
ThreadPoolTest.cpp
Expand Down
32 changes: 0 additions & 32 deletions src/autowiring/test/SatisfiabilityTest.cpp

This file was deleted.

0 comments on commit cdf6675

Please sign in to comment.