diff --git a/AutowiringConfig.h.in b/AutowiringConfig.h.in index 30dcab4bc..43d230fc6 100644 --- a/AutowiringConfig.h.in +++ b/AutowiringConfig.h.in @@ -8,6 +8,9 @@ // Are we building autonet? #cmakedefine01 AUTOWIRING_BUILD_AUTONET +// Building for ARM? +#cmakedefine01 autowiring_BUILD_ARM + // Are we linking with C++11 STL? #cmakedefine01 autowiring_USE_LIBCXX #if autowiring_USE_LIBCXX diff --git a/Doxyfile b/Doxyfile index 9f7e2e134..8bea9e696 100644 --- a/Doxyfile +++ b/Doxyfile @@ -32,7 +32,7 @@ PROJECT_NAME = Autowiring # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = "0.4.1" +PROJECT_NUMBER = "0.4.2" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer @@ -706,6 +706,7 @@ FILE_PATTERNS = "at_exit.h" \ "ContextCreator.h" \ "ContextCreatorBase.h" \ "ContextEnumerator.h" \ + ContextMap.h \ "ContextMember.h" \ "CoreContext.h" \ "CoreJob.h" \ diff --git a/autowiring/AutoPacket.h b/autowiring/AutoPacket.h index 5831a3aea..be79fb5a8 100644 --- a/autowiring/AutoPacket.h +++ b/autowiring/AutoPacket.h @@ -106,9 +106,9 @@ class AutoPacket: /// The decoration which was just added to this packet /// /// This method results in a call to the AutoFilter method on any subscribers which are - /// satisfied by this decoration. + /// satisfied by this decoration. This method must be called with m_lock held. /// - void UpdateSatisfaction(const DecorationKey& info); + void UpdateSatisfactionUnsafe(std::unique_lock&& lk, const DecorationDisposition& disposition); /// /// Performs a "satisfaction pulse", which will avoid notifying any deferred filters @@ -126,7 +126,7 @@ class AutoPacket: /// /// Performs a decoration operation but does not attach priors to successors. /// - void DecorateUnsafeNoPriors(const AnySharedPointer& ptr, const DecorationKey& key); + void DecorateNoPriors(const AnySharedPointer& ptr, DecorationKey key); /// Runtime counterpart to Decorate void Decorate(const AnySharedPointer& ptr, DecorationKey key); @@ -185,7 +185,7 @@ class AutoPacket: template bool Has(int tshift=0) const { std::lock_guard lk(m_lock); - return HasUnsafe(DecorationKey(auto_id::key(), tshift)); + return HasUnsafe(DecorationKey(auto_id::key(), true, tshift)); } /// @@ -197,7 +197,7 @@ class AutoPacket: const T* retVal; if (!Get(retVal, tshift)) - ThrowNotDecoratedException(DecorationKey(auto_id::key(), tshift)); + ThrowNotDecoratedException(DecorationKey(auto_id::key(), false, tshift)); return *retVal; } @@ -210,7 +210,7 @@ class AutoPacket: /// template bool Get(const T*& out, int tshift=0) const { - const DecorationDisposition* pDisposition = GetDisposition(DecorationKey(auto_id::key(), tshift)); + const DecorationDisposition* pDisposition = GetDisposition(DecorationKey(auto_id::key(), false, tshift)); if (pDisposition) { if (pDisposition->m_decorations.size() == 1) { out = static_cast(pDisposition->m_decorations[0]->ptr()); @@ -242,7 +242,7 @@ class AutoPacket: template bool Get(const std::shared_ptr*& out, int tshift=0) const { // Decoration must be present and the shared pointer itself must also be present - const DecorationDisposition* pDisposition = GetDisposition(DecorationKey(auto_id::key(), tshift)); + const DecorationDisposition* pDisposition = GetDisposition(DecorationKey(auto_id::key(), true, tshift)); if (!pDisposition || pDisposition->m_decorations.size() != 1) { out = nullptr; return false; @@ -262,7 +262,7 @@ class AutoPacket: template bool Get(std::shared_ptr& out, int tshift = 0) const { std::lock_guard lk(m_lock); - auto deco = m_decorations.find(DecorationKey(auto_id::key(), tshift)); + auto deco = m_decorations.find(DecorationKey(auto_id::key(), true, tshift)); if(deco != m_decorations.end() && deco->second.m_state == DispositionState::Satisfied) { auto& disposition = deco->second; if(disposition.m_decorations.size() == 1) { @@ -274,11 +274,14 @@ class AutoPacket: return false; } + /// + /// The shared pointer decoration for the specified type and time shift, or nullptr if no such decoration exists + /// template - const std::shared_ptr& GetShared(int tshift = 0) const { + const std::shared_ptr* GetShared(int tshift = 0) const { const std::shared_ptr* retVal; Get(retVal, tshift); - return *retVal; + return retVal; } /// @@ -291,7 +294,7 @@ class AutoPacket: template const T** GetAll(int tshift = 0) const { std::lock_guard lk(m_lock); - auto q = m_decorations.find(DecorationKey(auto_id::key(), tshift)); + auto q = m_decorations.find(DecorationKey(auto_id::key(), true, tshift)); // If decoration doesn't exist, return empty null-terminated buffer if (q == m_decorations.end()) { @@ -328,22 +331,8 @@ class AutoPacket: /// template void Unsatisfiable(void) { - DecorationKey key(auto_id::key()); - { - // Insert a null entry at this location: - std::lock_guard lk(m_lock); - auto& entry = m_decorations[key]; - entry.SetKey(key); // Ensure correct type if instantiated here - if(entry.m_state == DispositionState::PartlySatisfied || - entry.m_state == DispositionState::Satisfied) - throw std::runtime_error("Cannot mark a decoration as unsatisfiable when that decoration is already present on this packet"); - - // Mark the entry as permanently checked-out - entry.m_state = DispositionState::Unsatisfiable; - } - - // Now trigger a rescan: - MarkUnsatisfiable(key); + MarkUnsatisfiable(DecorationKey(auto_id::key(), false, 0)); + MarkUnsatisfiable(DecorationKey(auto_id::key(), true, 0)); } /// @@ -356,11 +345,12 @@ class AutoPacket: /// template const T& Decorate(T t) { - DecorationKey key(auto_id::key()); - // Create a copy of the input, put the copy in a shared pointer auto ptr = std::make_shared(std::forward(t)); - Decorate(AnySharedPointer(ptr), key); + Decorate( + AnySharedPointer(ptr), + DecorationKey(auto_id::key(), true, 0) + ); return *ptr; } @@ -375,7 +365,7 @@ class AutoPacket: /// template void Decorate(std::shared_ptr ptr) { - DecorationKey key(auto_id::key()); + DecorationKey key(auto_id::key(), true, 0); // We don't want to see this overload used on a const T static_assert(!std::is_const::value, "Cannot decorate a shared pointer to const T with this overload"); @@ -425,8 +415,8 @@ class AutoPacket: // Perform standard decoration with a short initialization: std::unique_lock lk(m_lock); DecorationDisposition* pTypeSubs[1 + sizeof...(Ts)] = { - &DecorateImmediateUnsafe(DecorationKey(auto_id::key()), &immed), - &DecorateImmediateUnsafe(DecorationKey(auto_id::key()), &immeds)... + &DecorateImmediateUnsafe(DecorationKey(auto_id::key(), false, 0), &immed), + &DecorateImmediateUnsafe(DecorationKey(auto_id::key(), false, 0), &immeds)... }; lk.unlock(); @@ -442,12 +432,14 @@ class AutoPacket: // Now trigger a rescan to hit any deferred, unsatisfiable entries: #if autowiring_USE_LIBCXX - for (const std::type_info* ti : {&auto_id::key(), &auto_id::key()...}) - MarkUnsatisfiable(DecorationKey(*ti)); + for (const std::type_info* ti : {&auto_id::key(), &auto_id::key()...}) { + MarkUnsatisfiable(DecorationKey(*ti, true, 0)); + MarkUnsatisfiable(DecorationKey(*ti, false, 0)); + } #else bool dummy[] = { - (MarkUnsatisfiable(DecorationKey(auto_id::key())), false), - (MarkUnsatisfiable(DecorationKey(auto_id::key())), false)... + (MarkUnsatisfiable(DecorationKey(auto_id::key(), false, 0)), false), + (MarkUnsatisfiable(DecorationKey(auto_id::key(), false, 0)), false)... }; (void)dummy; #endif @@ -505,7 +497,9 @@ class AutoPacket: /// True if the indicated type has been requested for use by some consumer template bool HasSubscribers(void) const { - return HasSubscribers(DecorationKey(auto_id::key())); + return + HasSubscribers(DecorationKey(auto_id::key(), false, 0)) || + HasSubscribers(DecorationKey(auto_id::key(), true, 0)); } struct SignalStub { @@ -597,7 +591,7 @@ template bool AutoPacket::Get(const std::shared_ptr*& out) const { static_assert(!std::is_const::value, "Overload resolution selected an incorrect version of Get"); - const DecorationDisposition* pDisposition = GetDisposition(DecorationKey(auto_id::key())); + const DecorationDisposition* pDisposition = GetDisposition(DecorationKey(auto_id::key(), true, 0)); if (!pDisposition || pDisposition->m_decorations.size() != 1) { out = nullptr; return false; diff --git a/autowiring/AutoPacketFactory.h b/autowiring/AutoPacketFactory.h index 07fa3af59..e40b30042 100644 --- a/autowiring/AutoPacketFactory.h +++ b/autowiring/AutoPacketFactory.h @@ -75,6 +75,7 @@ class AutoPacketFactory: bool OnStart(void) override; void OnStop(bool graceful) override; void DoAdditionalWait(void) override; + bool DoAdditionalWait(std::chrono::nanoseconds timeout) override; /// /// Causes this AutoPacketFactory to release all of its packet subscribers diff --git a/autowiring/AutoPacketGraph.h b/autowiring/AutoPacketGraph.h index 1d97767e5..345b330a4 100644 --- a/autowiring/AutoPacketGraph.h +++ b/autowiring/AutoPacketGraph.h @@ -98,7 +98,7 @@ class AutoPacketGraph: /// AutowiringEvents overrides virtual void NewContext(CoreContext&) override {} virtual void ExpiredContext(CoreContext&) override {} - virtual void NewObject(CoreContext&, const ObjectTraits&) override; + virtual void NewObject(CoreContext&, const CoreObjectDescriptor&) override; /// CoreRunnable overrides virtual bool OnStart(void) override; diff --git a/autowiring/AutowirableSlot.h b/autowiring/AutowirableSlot.h index ec9ef1e3c..69d76ef92 100644 --- a/autowiring/AutowirableSlot.h +++ b/autowiring/AutowirableSlot.h @@ -11,25 +11,24 @@ class DeferrableAutowiring; class GlobalCoreContext; class CoreObject; -// Utility routine, for users who need a function that does nothing -template -void NullOp(T) {} - /// /// Strategy class for performing unsynchronized operations on an autowirable slot /// /// /// The DeferrableAutowiring base class' SatisfyAutowiring routine is guaranteed to be run in a /// synchronized context--IE, a call to CancelAutowiringNotification will block until the above -/// routines return. Unfortunately, this lock also excludes many other types of operations, such -/// as type search operations, which means that some of the work associated with cleaning up after -/// an autowiring has been satisfied will take place in an unsynchronized context. This means -/// that virtual function calls are generally unsafe on the member being autowired when they are -/// made without a lock being held. +/// routine returns. Unfortunately, this lock also excludes many other types of operations, such +/// as CoreContext::Inject and CoreContext::FindByType, which means that handing control to a user +/// specified callback is unsafe. /// -/// To mitigate this problem, instead of performing a virtual call through the original object, a -/// strategy type is provided by the DeferrableAutowiring while the lock is held, and then later the -/// strategy is employed to clean up the object, if necessary. +/// Exacerbating the problem is the fact that the original DeferrableAutowiring may refer to an +/// object on the stack or whose destruction cannot otherwise be delayed. As soon as the synchronized +/// context is exited, the object could already be in a teardown pathway, which means we can't invoke +/// any kind of virtual function call on the object. +/// +/// Thus, the Finalize operation is only supported on objects whose lifetimes can be externally +/// guaranteed. Currently, only AutowirableSlotFn supports this behavior, and it is accessable via +/// CoreContext::NotifyWhenAutowired and Autowired::NotifyWhenAutowired. /// class DeferrableUnsynchronizedStrategy { public: @@ -43,7 +42,7 @@ class DeferrableUnsynchronizedStrategy { /// outside of the context of a lock. Once this method returns, this object is guaranteed never /// to be referred to again by CoreContext. /// - virtual void Finalize(DeferrableAutowiring* pSlot) const = 0; + virtual void Finalize(void) = 0; }; /// @@ -103,7 +102,7 @@ class DeferrableAutowiring: /// /// If no custom strategy is required, this method may return null /// - virtual const DeferrableUnsynchronizedStrategy* GetStrategy(void) { return nullptr; } + virtual DeferrableUnsynchronizedStrategy* GetStrategy(void) { return nullptr; } /// /// @@ -204,24 +203,12 @@ class AutowirableSlot: /// template class AutowirableSlotFn: - public AutowirableSlot + public AutowirableSlot, + public DeferrableUnsynchronizedStrategy { static_assert(!std::is_same::value, "Do not attempt to autowire CoreContext. Instead, use AutoCurrentContext or AutoCreateContext"); static_assert(!std::is_same::value, "Do not attempt to autowire GlobalCoreContext. Instead, use AutoGlobalContext"); - class Strategy: - public DeferrableUnsynchronizedStrategy - { - public: - Strategy(void) {} - - void Finalize(DeferrableAutowiring* pfn) const override { - ((AutowirableSlotFn*) pfn)->Finalize(); - } - }; - - static const Strategy s_strategy; - public: AutowirableSlotFn(const std::shared_ptr& ctxt, Fn&& fn) : AutowirableSlot(ctxt), @@ -239,7 +226,7 @@ class AutowirableSlotFn: /// /// Finalization routine, called by our strategy /// - void Finalize(void) { + void Finalize(void) override { // Let the lambda execute as it sees fit: CallThroughObj(fn, &Fn::operator()); @@ -255,8 +242,6 @@ class AutowirableSlotFn: ); } - const DeferrableUnsynchronizedStrategy* GetStrategy(void) override { return &s_strategy; } + DeferrableUnsynchronizedStrategy* GetStrategy(void) override { return this; } }; -template -const typename AutowirableSlotFn::Strategy AutowirableSlotFn::s_strategy; diff --git a/autowiring/Autowired.h b/autowiring/Autowired.h index c0f0bb3ba..ec13671c7 100644 --- a/autowiring/Autowired.h +++ b/autowiring/Autowired.h @@ -338,7 +338,8 @@ class AutowiredFast: /// Enables the specified type to be "bolted" to the current context. /// /// -/// +/// Used to enable a boltable class in a context and can be used even if the context has not been created yet. In +/// this case, the class will be constructed and enabled when the context is created /// template class AutoEnable diff --git a/autowiring/AutowiringEvents.h b/autowiring/AutowiringEvents.h index d1edba9ce..91f223242 100644 --- a/autowiring/AutowiringEvents.h +++ b/autowiring/AutowiringEvents.h @@ -3,7 +3,7 @@ #include "EventRegistry.h" #include TYPE_INDEX_HEADER -struct ObjectTraits; +struct CoreObjectDescriptor; class CoreContext; /// @@ -17,7 +17,7 @@ class AutowiringEvents { virtual void NewContext(CoreContext&)=0; virtual void ExpiredContext(CoreContext&)=0; - virtual void NewObject(CoreContext&, const ObjectTraits&)=0; + virtual void NewObject(CoreContext&, const CoreObjectDescriptor&)=0; }; // Extern explicit template instantiation declarations added to prevent diff --git a/autowiring/BasicThread.h b/autowiring/BasicThread.h index a2a9927ac..d9a11a686 100644 --- a/autowiring/BasicThread.h +++ b/autowiring/BasicThread.h @@ -76,14 +76,17 @@ class BasicThread: // we're actually signalling this event after we free ourselves. const std::shared_ptr m_state; + // Flag indicating that this thread was started at some point + bool m_wasStarted; + // Flag indicating that we need to stop right now bool m_stop; // Run condition: bool m_running; - // Completion condition, true when this thread is no longer running and has run at least once - bool m_completed; + // Legacy field, some clients still refer to this + bool& DEPRECATED_MEMBER(m_completed, "Use IsCompleted instead"); // The current thread priority ThreadPriority m_priority; @@ -220,9 +223,15 @@ class BasicThread: void OnStop(bool graceful) override; - void DoAdditionalWait() override; + void DoAdditionalWait(void) override; + bool DoAdditionalWait(std::chrono::nanoseconds timeout) override; public: + /// + /// True if this thread has transitioned to a completed state + /// + bool IsCompleted(void) const; + /// /// Begins thread execution. /// diff --git a/autowiring/BasicThreadStateBlock.h b/autowiring/BasicThreadStateBlock.h index 05f801d69..44213d9f7 100644 --- a/autowiring/BasicThreadStateBlock.h +++ b/autowiring/BasicThreadStateBlock.h @@ -7,7 +7,8 @@ struct BasicThreadStateBlock: std::enable_shared_from_this { - ~BasicThreadStateBlock(); + BasicThreadStateBlock(void); + ~BasicThreadStateBlock(void); // General purpose thread lock and update condition for the lock std::mutex m_lock; @@ -15,4 +16,7 @@ struct BasicThreadStateBlock: // The current thread, if running std::thread m_thisThread; + + // Completion condition, true when this thread is no longer running and has run at least once + bool m_completed; }; diff --git a/autowiring/C++11/boost_atomic.h b/autowiring/C++11/boost_atomic.h index f10b09cf5..02ef8a6ca 100644 --- a/autowiring/C++11/boost_atomic.h +++ b/autowiring/C++11/boost_atomic.h @@ -5,6 +5,7 @@ namespace std { using autoboost::atomic; using autoboost::atomic_flag; + using autoboost::atomic_thread_fence; using autoboost::memory_order_relaxed; using autoboost::memory_order_consume; using autoboost::memory_order_acquire; diff --git a/autowiring/C++11/boost_type_traits.h b/autowiring/C++11/boost_type_traits.h index dc0aa2467..540caccea 100644 --- a/autowiring/C++11/boost_type_traits.h +++ b/autowiring/C++11/boost_type_traits.h @@ -19,10 +19,12 @@ #include #include #include +#include namespace std { using autoboost::conditional; using autoboost::decay; + using autoboost::declval; using autoboost::false_type; using autoboost::has_trivial_constructor; using autoboost::integral_constant; diff --git a/autowiring/C++11/chrono_with_profiling_clock.h b/autowiring/C++11/chrono_with_profiling_clock.h index ed7312455..172990a03 100644 --- a/autowiring/C++11/chrono_with_profiling_clock.h +++ b/autowiring/C++11/chrono_with_profiling_clock.h @@ -27,11 +27,11 @@ extern "C" { } namespace { - const long long g_Frequency = []() + const double g_nanosecs_per_tic = []() { int64_t frequency; QueryPerformanceFrequency(&reinterpret_cast(frequency)); - return frequency; + return 1e9 / static_cast(frequency); }(); } @@ -48,7 +48,7 @@ namespace std { static time_point now() { int64_t count; QueryPerformanceCounter(&reinterpret_cast(count)); - return time_point(duration(count * static_cast(period::den) / g_Frequency)); + return time_point(duration(static_cast(static_cast(count) * g_nanosecs_per_tic))); } }; } diff --git a/autowiring/C++11/cpp11.h b/autowiring/C++11/cpp11.h index 007bd67b4..8efa94198 100644 --- a/autowiring/C++11/cpp11.h +++ b/autowiring/C++11/cpp11.h @@ -326,20 +326,21 @@ * Deprecation convenience macro *********************/ #ifndef _DEBUG - -#ifdef _MSC_VER -#define DEPRECATED(signature, msg) __declspec(deprecated(msg)) signature -#define DEPRECATED_CLASS(classname, msg) __declspec(deprecated(msg)) classname + #ifdef _MSC_VER + #define DEPRECATED(signature, msg) __declspec(deprecated(msg)) signature + #define DEPRECATED_CLASS(classname, msg) __declspec(deprecated(msg)) classname + #define DEPRECATED_MEMBER(member, msg) member + #else + #define DEPRECATED(signature, msg) signature __attribute__((deprecated)) + #define DEPRECATED_CLASS(classname, msg) classname + #define DEPRECATED_MEMBER(member, msg) DEPRECATED(member, msg) + #endif #else -#define DEPRECATED(signature, msg) signature __attribute__((deprecated)) -#define DEPRECATED_CLASS(classname, msg) + #define DEPRECATED(signature, msg) signature + #define DEPRECATED_CLASS(classname, msg) classname + #define DEPRECATED_MEMBER(member, msg) member #endif -#else -// In release mode we don't bother to deprecate -#define DEPRECATED(signature, msg) signature -#define DEPRECATED_CLASS(classname, msg) -#endif /********************* * Enum forward declaration convenience macro - VS2010 has a bad implementation diff --git a/autowiring/ContextCreator.h b/autowiring/ContextCreator.h index 78569027d..8a92aec5d 100644 --- a/autowiring/ContextCreator.h +++ b/autowiring/ContextCreator.h @@ -9,6 +9,7 @@ /// /// The sigil type that will be used for created contexts /// A key type used to identify this context +/// This class is obsolete, make use of instead /// /// This class helps manage the creation of contexts with global names. When the new child context /// is created, a notification is broadcast throughout the entire current context to any registered @@ -20,7 +21,7 @@ /// All static member functions are thread-safe, other members are not thread-safe. /// template -class ContextCreator: +class ContextCreator : public ContextCreatorBase { protected: @@ -32,6 +33,9 @@ class ContextCreator: typedef Key t_callbackHandle; public: + DEPRECATED(ContextCreator(void), "This type is deprecated, use manual context creation and ContextMap instead") + {} + // Accessor methods: size_t GetSize(void) const {return m_contexts.size();} diff --git a/autowiring/CoreContext.h b/autowiring/CoreContext.h index 3b8419c33..9cb862fba 100644 --- a/autowiring/CoreContext.h +++ b/autowiring/CoreContext.h @@ -16,7 +16,7 @@ #include "InvokeRelay.h" #include "JunctionBoxManager.h" #include "member_new_type.h" -#include "ObjectTraits.h" +#include "CoreObjectDescriptor.h" #include "result_or_default.h" #include "TeardownNotifier.h" #include "TypeRegistry.h" @@ -162,7 +162,7 @@ class CoreContext: DeferrableAutowiring* pFirst; // A back reference to the concrete type from which this memo was generated: - const ObjectTraits* pObjTraits; + const CoreObjectDescriptor* pObjTraits; // Once this memo entry is satisfied, this will contain the AnySharedPointer instance that performs // the satisfaction @@ -230,7 +230,7 @@ class CoreContext: class AutoFactoryFn; // This is a list of concrete types, indexed by the true type of each element. - std::list m_concreteTypes; + std::list m_concreteTypes; // This is a memoization map used to memoize any already-detected interfaces. mutable std::unordered_map m_typeMemos; @@ -247,14 +247,13 @@ class CoreContext: t_rcvrSet m_delayedEventReceivers; // Context members from other contexts that have snooped this context - std::set m_snoopers; + std::set m_snoopers; // Manages events for this context. One JunctionBoxManager is shared between peer contexts const std::shared_ptr m_junctionBoxManager; // Actual core threads: - typedef std::list t_threadList; - t_threadList m_threads; + std::list m_threads; // Clever use of shared pointer to expose the number of outstanding CoreRunnable instances. // Destructor does nothing; this is by design. @@ -328,7 +327,7 @@ class CoreContext: /// /// Invokes all deferred autowiring fields, generally called after a new member has been added /// - void UpdateDeferredElements(std::unique_lock&& lk, const ObjectTraits& entry); + void UpdateDeferredElements(std::unique_lock&& lk, const CoreObjectDescriptor& entry); /// \internal /// @@ -399,7 +398,7 @@ class CoreContext: /// /// Internal type introduction routine /// - void AddInternal(const ObjectTraits& traits); + void AddInternal(const CoreObjectDescriptor& traits); /// \internal /// @@ -433,13 +432,13 @@ class CoreContext: /// /// Adds a snooper to the snoopers set /// - void InsertSnooper(std::shared_ptr snooper); + void InsertSnooper(const AnySharedPointer& snooper); /// \internal /// /// Removes a snooper to the snoopers set /// - void RemoveSnooper(std::shared_ptr snooper); + void RemoveSnooper(const AnySharedPointer& snooper); /// \internal /// @@ -449,13 +448,13 @@ class CoreContext: /// This method has no effect if the passed value is presently a snooper in this context; the /// snooper collection must therefore be updated prior to the call to this method. /// - void UnsnoopEvents(CoreObject* snooper, const JunctionBoxEntry& traits); + void UnsnoopEvents(const AnySharedPointer& snooper, const JunctionBoxEntry& traits); /// \internal /// /// Forwarding routine, only removes from this context /// - void UnsnoopAutoPacket(const ObjectTraits& traits); + void UnsnoopAutoPacket(const CoreObjectDescriptor& traits); /// \internal /// @@ -517,6 +516,15 @@ class CoreContext: /// A shared reference to the parent context of this context. const std::shared_ptr& GetParentContext(void) const { return m_pParent; } + /// + /// Returns a copy of the list of runnables + /// + /// + /// This list is intended primarily for diagnostic purposes. The pointers are dumb pointers, and may be + /// invalidated if the caller is not careful to hold a shared pointer to the context. + /// + std::vector GetRunnables(void) const; + /// True if the sigil type of this CoreContext matches the specified sigil type. template bool Is(void) const { return GetSigilType() == typeid(Sigil); } @@ -663,7 +671,7 @@ class CoreContext: try { // Pass control to the insertion routine, which will handle injection from this point: - AddInternal(ObjectTraits(retVal, (T*)nullptr)); + AddInternal(CoreObjectDescriptor(retVal, (T*)nullptr)); } catch(autowiring_error&) { // We know why this exception occurred. It's because, while we were constructing our @@ -929,6 +937,11 @@ class CoreContext: /// The recipient of the event void FilterFiringException(const JunctionBoxBase* pProxy, CoreObject* pRecipient); + /// + /// Runtime version of Snoop + /// + void Snoop(const CoreObjectDescriptor& traits); + /// /// Registers the specified event receiver to receive messages broadcast within this context. /// @@ -942,18 +955,7 @@ class CoreContext: /// template void Snoop(const std::shared_ptr& pSnooper) { - const ObjectTraits traits(pSnooper, (T*)nullptr); - - // Add to collections of snoopers - InsertSnooper(pSnooper); - - // Add EventReceiver - if(traits.receivesEvents) - AddEventReceiver(JunctionBoxEntry(this, traits.pCoreObject)); - - // Add PacketSubscriber; - if(!traits.subscriber.empty()) - AddPacketSubscriber(traits.subscriber); + Snoop(CoreObjectDescriptor(pSnooper, (T*)nullptr)); } /// @@ -961,9 +963,19 @@ class CoreContext: /// template void Snoop(const Autowired& snooper) { - return Snoop(static_cast&>(snooper)); + Snoop( + CoreObjectDescriptor( + static_cast&>(snooper), + (T*)nullptr + ) + ); } + /// + /// Runtime version of Unsnoop + /// + void Unsnoop(const CoreObjectDescriptor& traits); + /// /// Unregisters an event receiver previously registered to receive snooped events /// @@ -972,19 +984,7 @@ class CoreContext: /// template void Unsnoop(const std::shared_ptr& pSnooper) { - const ObjectTraits traits(pSnooper, (T*)nullptr); - - RemoveSnooper(pSnooper); - - auto oSnooper = std::static_pointer_cast(pSnooper); - - // Cleanup if its an EventReceiver - if(traits.receivesEvents) - UnsnoopEvents(oSnooper.get(), JunctionBoxEntry(this, traits.pCoreObject)); - - // Cleanup if its a packet listener - if(!traits.subscriber.empty()) - UnsnoopAutoPacket(traits); + Unsnoop(CoreObjectDescriptor(pSnooper, (T*)nullptr)); } /// @@ -992,7 +992,12 @@ class CoreContext: /// template void Unsnoop(const Autowired& snooper) { - return Unsnoop(static_cast&>(snooper)); + Unsnoop( + CoreObjectDescriptor( + static_cast&>(snooper), + (T*)nullptr + ) + ); } /// diff --git a/autowiring/CoreJob.h b/autowiring/CoreJob.h index 8e4f9576c..b61ece362 100644 --- a/autowiring/CoreJob.h +++ b/autowiring/CoreJob.h @@ -43,4 +43,5 @@ class CoreJob: bool OnStart(void) override; void OnStop(bool graceful) override; void DoAdditionalWait(void) override; + bool DoAdditionalWait(std::chrono::nanoseconds timeout) override; }; diff --git a/autowiring/ObjectTraits.h b/autowiring/CoreObjectDescriptor.h similarity index 84% rename from autowiring/ObjectTraits.h rename to autowiring/CoreObjectDescriptor.h index a0d3499e4..b5629d179 100644 --- a/autowiring/ObjectTraits.h +++ b/autowiring/CoreObjectDescriptor.h @@ -15,9 +15,15 @@ /// /// Mapping and extraction structure used to provide a runtime version of an Object-implementing shared pointer /// -struct ObjectTraits { - template - ObjectTraits(const std::shared_ptr& value, T*) : +/// +/// This type is used to describe an object that is a member of a context. This descriptor structure performs +/// all of the type rule induction work that CoreContext performs, and provides users with a way to provide a +/// runtime description of a type. This is critical in cases where a type is not available at compile time-- +/// for instance, if that type is provided in another module that is dynamically linked. +/// +struct CoreObjectDescriptor { + template + CoreObjectDescriptor(const std::shared_ptr& value, T*) : type(typeid(T)), actual_type(typeid(*value)), stump(SlotInformationStump::s_stump), @@ -48,10 +54,18 @@ struct ObjectTraits { (void) autowiring::fast_pointer_cast_initializer::sc_init; } + /// + /// Special case where the declared type is also the true type + /// + template + CoreObjectDescriptor(const std::shared_ptr& value) : + CoreObjectDescriptor(value, static_cast(nullptr)) + {} + // The type of the passed pointer const std::type_info& type; - // The "actual type" used by Autowiring. This type may differ from ObjectTraits::type in cases + // The "actual type" used by Autowiring. This type may differ from CoreObjectDescriptor::type in cases // where a type unifier is used, or if the concrete type is defined in an external module--for // instance, by a class factory. const std::type_info& actual_type; diff --git a/autowiring/CoreRunnable.h b/autowiring/CoreRunnable.h index 63ee78e3f..a60c697f8 100644 --- a/autowiring/CoreRunnable.h +++ b/autowiring/CoreRunnable.h @@ -7,7 +7,7 @@ class CoreObject; /// -/// Base class for objects that run threads. +/// Provides the interface for threads that should receive start and stop notifications in a context /// /// /// Users of Autowiring will typically use BasicThread or CoreThread instead of @@ -62,13 +62,22 @@ class CoreRunnable { virtual void OnStop(bool graceful) {}; /// - /// Invoked when a Wait() call has been made. Override this method to perform - /// any actions required before this runnable enters the waiting state. + /// Invoked just before control is returned to the user. /// + /// The maximum amount of time to wait + /// True if the wait succeeded, false if a timeout occurred /// - /// This call should not block for an extended period of time. + /// This virtual method provides implementors with a way to add further constraints to the wait operation + /// beyond the condition variable held internally by this CoreRunnable. + /// + /// This method must return true if the timeout is indefinite. /// - virtual void DoAdditionalWait() {}; + virtual bool DoAdditionalWait(std::chrono::nanoseconds timeout) { return true; } + + /// + /// Untimed variant of DoAdditionalWait + /// + virtual void DoAdditionalWait(void) { } public: // Accessor methods: diff --git a/autowiring/DecorationDisposition.h b/autowiring/DecorationDisposition.h index 0aecc8274..d3ad221fa 100644 --- a/autowiring/DecorationDisposition.h +++ b/autowiring/DecorationDisposition.h @@ -9,25 +9,36 @@ struct SatCounter; struct DecorationKey { + DecorationKey(void) : + ti(nullptr), + is_shared(false), + tshift(-1) + {} + DecorationKey(const DecorationKey& rhs) : ti(rhs.ti), + is_shared(rhs.is_shared), tshift(rhs.tshift) {} - explicit DecorationKey(const std::type_info& ti, int tshift = 0) : - ti(ti), + explicit DecorationKey(const std::type_info& ti, bool is_shared, int tshift) : + ti(&ti), + is_shared(is_shared), tshift(tshift) {} // The type index - const std::type_info& ti; + const std::type_info* ti; + + // True if this decoration can be used with AutoFilters that accept a shared_ptr input type + bool is_shared; // Zero refers to a decoration created on this packet, a positive number [tshift] indicates // a decoration attached [tshift] packets ago. int tshift; bool operator==(const DecorationKey& rhs) const { - return ti == rhs.ti && tshift == rhs.tshift; + return ti == rhs.ti && is_shared == rhs.is_shared && tshift == rhs.tshift; } }; @@ -35,10 +46,13 @@ namespace std { template<> struct hash { size_t operator()(const DecorationKey& key) const { + return + key.tshift + + (key.is_shared ? 0x80000 : 0x70000) + #if AUTOWIRING_USE_LIBCXX - return key.ti.hash_code() + key.tshift; + key.ti->hash_code(); #else - return std::type_index(key.ti).hash_code() + key.tshift; + std::type_index(*key.ti).hash_code(); #endif } }; @@ -46,9 +60,20 @@ namespace std { // The possible states for a DecorationDisposition enum class DispositionState { + // No decorations attached Unsatisfied, + + // Some decorations present, but not all of them. Cannot proceed. PartlySatisfied, + + // Everything attached, ready to go Satisfied, + + // Unsatisfiable, and the callers on this decoration cannot accept a non-null + // entry--IE, they accept const references as inputs. + UnsatisfiableNoCall, + + // This decoration will never be satisfied. Calls are generated to the Unsatisfiable }; @@ -57,62 +82,26 @@ enum class DispositionState { /// struct DecorationDisposition { -#if AUTOWIRING_USE_LIBCXX - DecorationDisposition(DecorationDisposition&&) = delete; - void operator=(DecorationDisposition&&) = delete; -#else - // The methods below are needed for c++98 builds - DecorationDisposition(DecorationDisposition&& source) : - m_decorations(source.m_decorations), - m_pImmediate(source.m_pImmediate), - m_publishers(source.m_publishers), - m_state(source.m_state) - {} - DecorationDisposition& operator=(DecorationDisposition&& source) { - m_decorations = std::move(source.m_decorations); - m_pImmediate = source.m_pImmediate; - source.m_pImmediate = nullptr; - m_publishers = std::move(source.m_publishers); - m_state = source.m_state; - return *this; - } -#endif //AUTOWIRING_USE_LIBCXX - DecorationDisposition(void) : - m_type(nullptr), m_pImmediate(nullptr), m_state(DispositionState::Unsatisfied) {} DecorationDisposition(const DecorationDisposition& source) : - m_type(source.m_type), + m_decorations(source.m_decorations), m_pImmediate(source.m_pImmediate), m_publishers(source.m_publishers), m_subscribers(source.m_subscribers), m_state(source.m_state) {} - - DecorationDisposition& operator=(const DecorationDisposition& source) { - m_type = source.m_type; - m_pImmediate = source.m_pImmediate; - m_publishers = source.m_publishers; - m_subscribers = source.m_subscribers; - m_state = source.m_state; - return *this; - } - -private: - // Destructured key for this decoration. Use accessor functions to access - // This is needed because DecorationKey doesn't have a default constructor - const std::type_info* m_type; - int m_tshift; -public: // The decoration proper--potentially, this decoration might be from a prior execution of this // packet. In the case of immediate decorations, this value will be invalid. + // Valid if and only if is_shared is false. std::vector m_decorations; - // A pointer to the immediate decorations, if one is specified, or else nullptr + // A pointer to the immediate decorations, if one is specified, or else nullptr. + // Valid if and only if is_shared is true. const void* m_pImmediate; // Providers for this decoration, where it can be statically inferred. Note that a provider for @@ -126,20 +115,17 @@ struct DecorationDisposition // The current state of this disposition DispositionState m_state; - // Set the key that identifies this decoration - void SetKey(const DecorationKey& key) { - m_type = &key.ti; - m_tshift = key.tshift; - } - - // Get the key that identifies this decoration - DecorationKey GetKey(void) const { - assert(m_type); - return DecorationKey(*m_type, m_tshift); - } - - operator bool() { - return !!m_type; + /// + /// True if all publishers have run on this disposition + /// + /// + /// 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. + /// + bool IsPublicationComplete(void) const { + return + !m_decorations.empty() && + m_decorations.size() >= m_publishers.size(); } void Reset(void) { diff --git a/autowiring/DispatchQueue.h b/autowiring/DispatchQueue.h index c76b14e54..f9cef3d71 100644 --- a/autowiring/DispatchQueue.h +++ b/autowiring/DispatchQueue.h @@ -63,7 +63,11 @@ class DispatchQueue { /// /// Moves all ready events from the delayed queue into the dispatch queue /// - void PromoteReadyEventsUnsafe(void); + /// True if at least one dispatcher was promoted + bool PromoteReadyDispatchersUnsafe(void); + + // Identical to PromoteReadyDispatchersUnsafe, invoke that method instead + void DEPRECATED(PromoteReadyEventsUnsafe(void), "Superceded by PromoteReadyDispatchersUnsafe") { PromoteReadyDispatchersUnsafe(); } /// /// Similar to DispatchEvent, except assumes that the dispatch lock is currently held @@ -132,32 +136,22 @@ class DispatchQueue { /// Similar to WaitForEvent, but does not block /// /// True if an event was dispatched, false if the queue was empty when checked + /// + /// If the dispatch queue is empty, this method will check the delayed dispatch queue. + /// bool DispatchEvent(void); /// /// Similar to DispatchEvent, but will attempt to dispatch all events currently queued /// /// The total number of events dispatched - int DispatchAllEvents(void) { - int retVal = 0; - while(DispatchEvent()) - retVal++; - return retVal; - } + int DispatchAllEvents(void); public: /// /// Explicit overload for already-constructed dispatch thunk types /// - void AddExisting(DispatchThunkBase* pBase) { - std::unique_lock lk(m_dispatchLock); - if(m_dispatchQueue.size() >= m_dispatchCap) - return; - - m_dispatchQueue.push_back(pBase); - m_queueUpdated.notify_all(); - OnPended(std::move(lk)); - } + void AddExisting(DispatchThunkBase* pBase); /// /// Recommends a point in time to wake up to check for events @@ -216,9 +210,7 @@ class DispatchQueue { /// /// Overload for absolute-time based delayed dispatch thunk /// - DispatchThunkDelayedExpression operator+=(std::chrono::steady_clock::time_point rhs) { - return DispatchThunkDelayedExpression(this, rhs); - } + DispatchThunkDelayedExpression operator+=(std::chrono::steady_clock::time_point rhs); /// /// Directly pends a delayed dispatch thunk @@ -226,18 +218,7 @@ class DispatchQueue { /// /// This overload will always succeed and does not consult the dispatch cap /// - void operator+=(DispatchThunkDelayed&& rhs) { - std::lock_guard lk(m_dispatchLock); - - m_delayedQueue.push(std::forward(rhs)); - if( - m_delayedQueue.top().GetReadyTime() == rhs.GetReadyTime() && - m_dispatchQueue.empty() - ) - // We're becoming the new next-to-execute entity, dispatch queue currently empty, trigger wakeup - // so our newly pended delay thunk is eventually processed. - m_queueUpdated.notify_all(); - } + void operator+=(DispatchThunkDelayed&& rhs); /// /// Generic overload which will pend an arbitrary dispatch type @@ -256,4 +237,3 @@ class DispatchQueue { OnPended(std::move(lk)); } }; - diff --git a/autowiring/SharedPointerSlot.h b/autowiring/SharedPointerSlot.h index 915836b30..d7412af1f 100644 --- a/autowiring/SharedPointerSlot.h +++ b/autowiring/SharedPointerSlot.h @@ -135,16 +135,20 @@ struct SharedPointerSlot { /// virtual void reset(void) {} + template + static const std::shared_ptr& null(void) { + static const std::shared_ptr s_empty; + return s_empty; + } + /// /// Attempts to coerce this type to the specified type /// template const std::shared_ptr& as(void) const { - static const std::shared_ptr s_empty; - if (type() == typeid(void)) // This is allowed, we always permit null to be cast to the requested type. - return s_empty; + return null(); if (type() != typeid(auto_id)) throw std::runtime_error("Attempted to obtain a shared pointer for an unrelated type"); diff --git a/autowiring/auto_arg.h b/autowiring/auto_arg.h index c95e7f1d9..b7c054701 100644 --- a/autowiring/auto_arg.h +++ b/autowiring/auto_arg.h @@ -76,7 +76,10 @@ class auto_arg> template static const std::shared_ptr& arg(C& packet) { - return packet.template GetShared(); + auto retVal = packet.template GetShared(); + if (!retVal) + return SharedPointerSlot::null(); + return *retVal; } }; @@ -203,7 +206,7 @@ class auto_arg> static const bool is_input = true; static const bool is_output = false; - static const bool is_shared = false; + static const bool is_shared = true; static const bool is_multi = false; static const int tshift = N; diff --git a/autowiring/auto_future.h b/autowiring/auto_future.h new file mode 100644 index 000000000..e21d176cb --- /dev/null +++ b/autowiring/auto_future.h @@ -0,0 +1,9 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once +#include + +#ifdef _MSC_VER +#include "auto_future_win.h" +#elif defined(__APPLE__) +#include "auto_future_mac.h" +#endif diff --git a/autowiring/auto_future_mac.h b/autowiring/auto_future_mac.h new file mode 100644 index 000000000..2fe08af86 --- /dev/null +++ b/autowiring/auto_future_mac.h @@ -0,0 +1,16 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once +#include +#include + +namespace autowiring { + /// + /// Platform-specific operation appending routine + /// + /// The future to be appended to + /// The lambda to be executed after the future is ready + template + void then(std::future& future, Fn&& fn) { + + } +} \ No newline at end of file diff --git a/autowiring/auto_future_win.h b/autowiring/auto_future_win.h new file mode 100644 index 000000000..041d9d125 --- /dev/null +++ b/autowiring/auto_future_win.h @@ -0,0 +1,53 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once +#include +#include + +namespace autowiring { + template + class _Packaged_state_unwrap: + public std::_Associated_state<_Ret*> + { + public: + std::function<_Ret(_ArgTypes...)> _Fn; + }; + + class _Task_async_state_unwrap: + public std::_Packaged_state + { + public: + ::Concurrency::task _Task; + }; + + /// + /// Platform-specific operation appending routine + /// + /// The future to be appended to + /// The lambda to be executed after the future is ready + template + void then(std::future& future, Fn&& fn) { + // Need a pointer to the underlying state block so we can decide what to do + auto ptr = future._Ptr(); + + if (auto* taskAsync = dynamic_cast*>(ptr)) { + auto* unwrap = reinterpret_cast<_Task_async_state_unwrap*>(taskAsync); + unwrap->_Task.then(fn); + } else if (auto* deferredAsync = dynamic_cast*>(ptr)) { + auto* packagedState = static_cast*>(deferredAsync); + auto* unwrap = reinterpret_cast<_Packaged_state_unwrap*>(packagedState); + + // New function which consists of a call to the original then a call to the continuation + unwrap->_Fn = std::bind( + [] (const std::function& orig, const Fn& fn) { + auto rv = orig(); + fn(); + return rv; + }, + std::move(unwrap->_Fn), + std::forward(fn) + ); + } + else + throw std::runtime_error("Unrecognized future type"); + } +} \ No newline at end of file diff --git a/autowiring/fast_pointer_cast.h b/autowiring/fast_pointer_cast.h index d012f49ac..2e86c2519 100644 --- a/autowiring/fast_pointer_cast.h +++ b/autowiring/fast_pointer_cast.h @@ -79,7 +79,7 @@ namespace autowiring { /// template struct fast_pointer_cast_blind { - std::shared_ptr cast(const std::shared_ptr& rhs) { + static std::shared_ptr cast(const std::shared_ptr& rhs) { return rhs; } }; @@ -108,6 +108,12 @@ namespace autowiring { static const fast_pointer_cast_initializer sc_init; }; + // Trivial case specialization + template + struct fast_pointer_cast_initializer { + static const fast_pointer_cast_initializer sc_init; + }; + template const fast_pointer_cast_initializer fast_pointer_cast_initializer::sc_init; } diff --git a/autowiring/has_static_new.h b/autowiring/has_static_new.h index 442181a6b..f597c4db9 100644 --- a/autowiring/has_static_new.h +++ b/autowiring/has_static_new.h @@ -1,6 +1,7 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once #include TYPE_TRAITS_HEADER +#include RVALUE_HEADER /// /// Utility helper structure for types which have a factory New routine @@ -11,7 +12,11 @@ template struct has_well_formed_static_new { static const bool value = std::is_convertible< - decltype(T::New(*(typename std::remove_reference::type*)nullptr...)), + decltype( + T::New( + std::declval()... + ) + ), T* >::value; }; @@ -24,11 +29,8 @@ struct has_well_formed_static_new { template struct has_static_new { - template - struct unnamed_constant; - template - static std::true_type select(decltype(U::New(*(typename std::remove_reference::type*)nullptr...))*); + static std::true_type select(decltype(U::New(std::forward(*(typename std::remove_reference::type*)nullptr)...))*); template static std::false_type select(...); diff --git a/nuget/tools/autowiring.natvis b/nuget/tools/autowiring.natvis index bbc235cd8..9f55a963c 100644 --- a/nuget/tools/autowiring.natvis +++ b/nuget/tools/autowiring.natvis @@ -40,7 +40,7 @@ - + [size = {_Mylast - _Myfirst}] _Mylast - _Myfirst @@ -51,7 +51,7 @@ - + [{((char*)type._M_data + 6 + (*(char*)type._M_data == 's')),sb}, uses = {((std::shared_ptr<Object>*)((SharedPointerSlot*)value.m_space)->m_space)->_Rep->_Uses}] ? diff --git a/publicDoxyfile.conf b/publicDoxyfile.conf index 673058421..a5dfcb4b8 100644 --- a/publicDoxyfile.conf +++ b/publicDoxyfile.conf @@ -38,7 +38,7 @@ PROJECT_NAME = Autowiring # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.4.1 +PROJECT_NUMBER = 0.4.2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/scripts/buildpublicdocs.sh b/scripts/buildpublicdocs.sh index 926b86b3e..10597555b 100755 --- a/scripts/buildpublicdocs.sh +++ b/scripts/buildpublicdocs.sh @@ -4,9 +4,9 @@ # Run doxygen on the source and copy graphics # -cd .. +pushd $(dirname "$0")/.. scripts/processcodeexamples.sh doxygen publicDoxyFile.conf -echo "Copy svg files" +echo "Copying svg files" cp -v devguide/diagrams/*.svg docs/html -cd scripts +popd diff --git a/src/autonet/AutoNetServerImpl.cpp b/src/autonet/AutoNetServerImpl.cpp index fddf4e81f..123f9ebbc 100644 --- a/src/autonet/AutoNetServerImpl.cpp +++ b/src/autonet/AutoNetServerImpl.cpp @@ -5,7 +5,7 @@ #include "autowiring.h" #include "AutoNetTransportHttp.hpp" #include "demangle.h" -#include "ObjectTraits.h" +#include "CoreObjectDescriptor.h" #include "EventRegistry.h" #include "TypeRegistry.h" #include @@ -170,7 +170,7 @@ void AutoNetServerImpl::ExpiredContext(CoreContext& oldCtxt){ }; } -void AutoNetServerImpl::NewObject(CoreContext& ctxt, const ObjectTraits& object){ +void AutoNetServerImpl::NewObject(CoreContext& ctxt, const CoreObjectDescriptor& object){ int contextID = ResolveContextID(&ctxt); *this += [this, object, contextID]{ diff --git a/src/autonet/AutoNetServerImpl.hpp b/src/autonet/AutoNetServerImpl.hpp index d4a0488d8..132a0dc85 100644 --- a/src/autonet/AutoNetServerImpl.hpp +++ b/src/autonet/AutoNetServerImpl.hpp @@ -14,7 +14,7 @@ #define BOOST_NO_CXX11_HDR_INITIALIZER_LIST #endif -struct ObjectTraits; +struct CoreObjectDescriptor; struct TypeIdentifierBase; // Protocol layer for AutoNet @@ -70,7 +70,7 @@ class AutoNetServerImpl: /// /// Context containing the object /// The object - virtual void NewObject(CoreContext& ctxt, const ObjectTraits& obj) override; + virtual void NewObject(CoreContext& ctxt, const CoreObjectDescriptor& obj) override; protected: /// diff --git a/src/autowiring/AutoPacket.cpp b/src/autowiring/AutoPacket.cpp index 71e754157..c2b8ab395 100644 --- a/src/autowiring/AutoPacket.cpp +++ b/src/autowiring/AutoPacket.cpp @@ -10,6 +10,7 @@ #include "SatCounter.h" #include #include +#include RVALUE_HEADER using namespace autowiring; @@ -28,12 +29,9 @@ AutoPacket::~AutoPacket(void) { // Mark decorations of successor packets that use decorations // originating from this packet as unsatisfiable - for (auto& pair : m_decorations) { - if (!pair.first.tshift && - pair.second.m_state != DispositionState::Satisfied) { - MarkSuccessorsUnsatisfiable(DecorationKey(pair.first.ti, 0)); - } - } + for (auto& pair : m_decorations) + if (!pair.first.tshift && pair.second.m_state != DispositionState::Satisfied) + MarkSuccessorsUnsatisfiable(DecorationKey(*pair.first.ti, pair.first.is_shared, 0)); // Needed for the AutoPacketGraph NotifyTeardownListeners(); @@ -65,9 +63,6 @@ DecorationDisposition& AutoPacket::DecorateImmediateUnsafe(const DecorationKey& // Obtain the decoration disposition of the entry we will be returning DecorationDisposition& dec = m_decorations[key]; - // Ensure correct key if instantiated here - dec.SetKey(key); - if (dec.m_state != DispositionState::Unsatisfied) { std::stringstream ss; ss << "Cannot perform immediate decoration with type " << autowiring::demangle(key.ti) @@ -83,10 +78,8 @@ DecorationDisposition& AutoPacket::DecorateImmediateUnsafe(const DecorationKey& void AutoPacket::AddSatCounter(SatCounter& satCounter) { for(auto pCur = satCounter.GetAutoFilterInput(); *pCur; pCur++) { - DecorationKey key(*pCur->ti, pCur->tshift); - + DecorationKey key(*pCur->ti, pCur->is_shared, pCur->tshift); DecorationDisposition& entry = m_decorations[key]; - entry.SetKey(key); // Decide what to do with this entry: if (pCur->is_input) { @@ -97,8 +90,14 @@ void AutoPacket::AddSatCounter(SatCounter& satCounter) { } entry.m_subscribers.push_back(&satCounter); - if (entry.m_state == DispositionState::Satisfied) + switch (entry.m_state) { + case DispositionState::Satisfied: + case DispositionState::Unsatisfiable: satCounter.Decrement(); + break; + default: + break; + } } if (pCur->is_output) { if(!entry.m_publishers.empty()) @@ -114,24 +113,27 @@ void AutoPacket::AddSatCounter(SatCounter& satCounter) { } // Make sure decorations exist for timeshifts less that key's timeshift - for (int tshift = 0; tshift < key.tshift; ++tshift) { - DecorationKey shiftKey(key.ti, tshift); - m_decorations[shiftKey].SetKey(shiftKey); - } + for (int tshift = 0; tshift < key.tshift; ++tshift) + m_decorations[DecorationKey(*key.ti, key.is_shared, tshift)]; } } void AutoPacket::MarkUnsatisfiable(const DecorationKey& key) { - // Perform unsatisfaction logic - if (key.tshift) { - { - std::lock_guard lk(m_lock); - auto& disp = m_decorations[key]; - disp.m_state = DispositionState::Satisfied; - disp.m_decorations.clear(); - } - UpdateSatisfaction(key); - } + // Ensure correct type if instantiated here + std::unique_lock lk(m_lock); + auto& entry = m_decorations[key]; + + if (entry.m_state == DispositionState::PartlySatisfied || entry.m_state == DispositionState::Satisfied) + throw std::runtime_error("Cannot mark a decoration as unsatisfiable when that decoration is already present on this packet"); + + // Mark the entry as permanently unsatisfiable: + entry.m_state = + key.is_shared ? + DispositionState::Unsatisfiable : + DispositionState::UnsatisfiableNoCall; + + // Notify all consumers + UpdateSatisfactionUnsafe(std::move(lk), entry); } void AutoPacket::MarkSuccessorsUnsatisfiable(DecorationKey key) { @@ -150,24 +152,24 @@ void AutoPacket::MarkSuccessorsUnsatisfiable(DecorationKey key) { } } -void AutoPacket::UpdateSatisfaction(const DecorationKey& info) { +void AutoPacket::UpdateSatisfactionUnsafe(std::unique_lock&& lk, const DecorationDisposition& disposition) { std::vector callQueue; - { - std::lock_guard lk(m_lock); - auto dFind = m_decorations.find(info); - if(dFind == m_decorations.end()) - // Trivial return, there's no subscriber to this decoration and so we have nothing to do - return; - - // Update satisfaction inside of lock - DecorationDisposition& decoration = dFind->second; - for(SatCounter* satCounter : decoration.m_subscribers) - if(satCounter->Decrement()) + // Update satisfaction inside of lock + switch (disposition.m_state) { + case DispositionState::Unsatisfiable: + case DispositionState::Satisfied: + for (SatCounter* satCounter : disposition.m_subscribers) + if (satCounter->Decrement()) callQueue.push_back(satCounter); + break; + default: + // Nothing to do + return; } // Make calls outside of lock, to avoid deadlock from decorations in methods + lk.unlock(); for (SatCounter* call : callQueue) call->GetCall()(call->GetAutoFilter(), *this); } @@ -178,17 +180,13 @@ void AutoPacket::PulseSatisfaction(DecorationDisposition* pTypeSubs[], size_t nI // First pass, decrement what we can: { std::lock_guard lk(m_lock); - for(size_t i = nInfos; i--;) { + for(size_t i = nInfos; i--;) for(SatCounter* cur : pTypeSubs[i]->m_subscribers) { if( // We only care about sat counters that aren't deferred--skip everyone else // Deferred calls will be too late. !cur->IsDeferred() && - // We cannot satisfy shared_ptr arguments, since the implied existence - // guarantee of shared_ptr is violated - !cur->GetArgumentType(&pTypeSubs[i]->GetKey().ti)->is_shared && - // Now do the decrementation and proceed even if optional > 0, // since this is the only opportunity to fulfill the arguments cur->Decrement() @@ -196,7 +194,6 @@ void AutoPacket::PulseSatisfaction(DecorationDisposition* pTypeSubs[], size_t nI // Finally, queue a call for this type callQueue.push_back(cur); } - } } // Make calls outside of lock, to avoid deadlock from decorations in methods @@ -219,47 +216,73 @@ bool AutoPacket::HasUnsafe(const DecorationKey& key) const { return q->second.m_state == DispositionState::Satisfied; } -void AutoPacket::DecorateUnsafeNoPriors(const AnySharedPointer& ptr, const DecorationKey& key) { - auto& entry = m_decorations[key]; - entry.SetKey(key); // Ensure correct key if instantiated here +void AutoPacket::DecorateNoPriors(const AnySharedPointer& ptr, DecorationKey key) { + DecorationDisposition* dispositionA; + DecorationDisposition* dispositionB; + { + std::lock_guard lk(m_lock); - if (entry.m_state == DispositionState::Satisfied) { - std::stringstream ss; - ss << "Cannot decorate this packet with type " << autowiring::demangle(ptr) - << ", the requested decoration is already satisfied"; - throw std::runtime_error(ss.str()); - } - if (entry.m_state == DispositionState::Unsatisfiable) { - std::stringstream ss; - ss << "Cannot check out decoration of type " << autowiring::demangle(ptr) - << ", it has been marked unsatisfiable"; - throw std::runtime_error(ss.str()); - } + auto transition = [&](DecorationKey& key){ + DecorationDisposition& disposition = m_decorations[key]; + switch (disposition.m_state) { + case DispositionState::Satisfied: + { + std::stringstream ss; + ss << "Cannot decorate this packet with type " << autowiring::demangle(ptr) + << ", the requested decoration is already satisfied"; + throw std::runtime_error(ss.str()); + } + break; + case DispositionState::Unsatisfiable: + case DispositionState::UnsatisfiableNoCall: + { + std::stringstream ss; + ss << "Cannot check out decoration of type " << autowiring::demangle(ptr) + << ", it has been marked unsatisfiable"; + throw std::runtime_error(ss.str()); + } + break; + default: + break; + } - entry.m_decorations.push_back(ptr); + // Decoration attaches here + disposition.m_decorations.push_back(ptr); + return &disposition; + }; - if (entry.m_decorations.size() >= entry.m_publishers.size()) { - entry.m_state = DispositionState::Satisfied; - } else { - entry.m_state = DispositionState::PartlySatisfied; + key.is_shared = false; + dispositionA = transition(key); + key.is_shared = true; + dispositionB = transition(key); } - // This allows us to retrieve correct entries for decorated input requests - AnySharedPointer decoration(entry.m_decorations.back()); + // Uniformly advance state: + switch (dispositionA->m_state) { + case DispositionState::Unsatisfied: + case DispositionState::PartlySatisfied: + // Permit a transition to another state + if (dispositionA->IsPublicationComplete() && dispositionB->IsPublicationComplete()) { + dispositionA->m_state = dispositionB->m_state = DispositionState::Satisfied; + + UpdateSatisfactionUnsafe(std::unique_lock(m_lock), *dispositionA); + UpdateSatisfactionUnsafe(std::unique_lock(m_lock), *dispositionB); + } + else + dispositionA->m_state = dispositionB->m_state = DispositionState::PartlySatisfied; + break; + default: + // Do nothing, no advancing to any states from here + break; + } } void AutoPacket::Decorate(const AnySharedPointer& ptr, DecorationKey key) { auto cur = shared_from_this(); do { - // Decorate, first, while holding down a lock - (std::lock_guard)cur->m_lock, - cur->DecorateUnsafeNoPriors(ptr, key); - - // Now update satisfaction set on this entry - if (m_decorations[key].m_state == DispositionState::Satisfied) { - cur->UpdateSatisfaction(key); - } + // Update satisfaction set on this entry + cur->DecorateNoPriors(ptr, key); // Obtain the successor cur = cur->Successor(); @@ -317,28 +340,22 @@ void AutoPacket::ForwardAll(std::shared_ptr recipient) const { // Copy decorations into an internal decorations maintenance collection. The values // in this collection are guaranteed to be stable in memory, and there are stable states // that can be relied upon without synchronization. - std::vector> dd; + std::vector> dd; { std::lock_guard lk(m_lock); for (const auto& decoration : m_decorations) - // Only fully satisfied decorations are considered for propagation - if (decoration.second.m_state == DispositionState::Satisfied) - for (const auto& single : decoration.second.m_decorations) - dd.push_back(std::make_pair(&decoration.second, single)); + // Only fully satisfied shared decorations are considered for propagation + if ( + decoration.first.is_shared && + decoration.second.m_state == DispositionState::Satisfied + ) + dd.push_back(decoration); } - // Lock down recipient colleciton while we go through and attach decorations: - for (auto& cur : dd) { - { - std::lock_guard lk(recipient->m_lock); - if (recipient->HasUnsafe(cur.first->GetKey())) - // Key already present, circle around - continue; - } - - // Decorate while unsynchronized: - recipient->Decorate(cur.second, cur.first->GetKey()); - } + // Lock down recipient collection while we go through and attach decorations: + for (auto& cur : dd) + for (const auto& decoration : cur.second.m_decorations) + recipient->Decorate(decoration, cur.first); } const SatCounter* AutoPacket::AddRecipient(const AutoFilterDescriptor& descriptor) { diff --git a/src/autowiring/AutoPacketFactory.cpp b/src/autowiring/AutoPacketFactory.cpp index 584fa4e8f..54994596e 100644 --- a/src/autowiring/AutoPacketFactory.cpp +++ b/src/autowiring/AutoPacketFactory.cpp @@ -128,6 +128,17 @@ void AutoPacketFactory::DoAdditionalWait(void) { ); } +bool AutoPacketFactory::DoAdditionalWait(std::chrono::nanoseconds timeout) { + std::unique_lock lk(m_lock); + return m_stateCondition.wait_for( + lk, + timeout, + [this]{ + return ShouldStop() && m_outstandingInternal.expired(); + } + ); +} + void AutoPacketFactory::Clear(void) { // Simple handoff to Stop is sufficient Stop(false); diff --git a/src/autowiring/AutoPacketGraph.cpp b/src/autowiring/AutoPacketGraph.cpp index ae4933be5..087b7cd95 100644 --- a/src/autowiring/AutoPacketGraph.cpp +++ b/src/autowiring/AutoPacketGraph.cpp @@ -67,7 +67,7 @@ void AutoPacketGraph::RecordDelivery(const std::type_info* ti, const AutoFilterD itr->second++; } -void AutoPacketGraph::NewObject(CoreContext&, const ObjectTraits&) { +void AutoPacketGraph::NewObject(CoreContext&, const CoreObjectDescriptor&) { LoadEdges(); } @@ -81,7 +81,7 @@ void AutoPacketGraph::AutoFilter(AutoPacket& packet) { packet.AddTeardownListener([this, &packet] () { for (auto& cur : packet.GetDecorations()) { auto& decoration = cur.second; - auto type = &decoration.GetKey().ti; + auto type = cur.first.ti; for (auto& publisher : decoration.m_publishers) { if (!publisher->remaining) { diff --git a/src/autowiring/AutoPacketInternal.cpp b/src/autowiring/AutoPacketInternal.cpp index 4556942b3..525aaddcb 100644 --- a/src/autowiring/AutoPacketInternal.cpp +++ b/src/autowiring/AutoPacketInternal.cpp @@ -21,16 +21,14 @@ void AutoPacketInternal::Initialize(bool isFirstPacket) { // Find all subscribers with no required or optional arguments: std::vector callCounters; for (auto* satCounter = m_firstCounter; satCounter; satCounter = satCounter->flink) { - // Prime the satisfaction graph for element: AddSatCounter(*satCounter); - if (!satCounter->remaining) callCounters.push_back(satCounter); } // Mark timeshifted decorations as unsatisfiable on the first packet - if (isFirstPacket) { + if (isFirstPacket) for (auto& dec : m_decorations) { auto& key = dec.first; if (key.tshift) { @@ -38,7 +36,6 @@ void AutoPacketInternal::Initialize(bool isFirstPacket) { MarkSuccessorsUnsatisfiable(key); } } - } // Call all subscribers with no required or optional arguments: // NOTE: This may result in decorations that cause other subscribers to be called. @@ -46,7 +43,22 @@ void AutoPacketInternal::Initialize(bool isFirstPacket) { call->GetCall()(call->GetAutoFilter(), *this); // First-call indicated by argumument type AutoPacket&: - UpdateSatisfaction(DecorationKey(typeid(auto_arg::id_type))); +#if autowiring_USE_LIBCXX + for (bool is_shared : {false, true}) { +#else + for (int num = 0; num < 2; ++num) { + bool is_shared = (bool)num; +#endif + std::unique_lock lk(m_lock); + + // Don't modify the decorations set if nobody expects an AutoPacket input + auto q = m_decorations.find(DecorationKey(typeid(auto_arg::id_type), is_shared, 0)); + if (q == m_decorations.end()) + continue; + + q->second.m_state = DispositionState::Satisfied; + UpdateSatisfactionUnsafe(std::move(lk), q->second); + } } std::shared_ptr AutoPacketInternal::SuccessorInternal(void) { diff --git a/src/autowiring/BasicThread.cpp b/src/autowiring/BasicThread.cpp index 561be85e2..687c0c936 100644 --- a/src/autowiring/BasicThread.cpp +++ b/src/autowiring/BasicThread.cpp @@ -10,9 +10,10 @@ BasicThread::BasicThread(const char* pName): ContextMember(pName), m_state(std::make_shared()), + m_wasStarted(false), m_stop(false), m_running(false), - m_completed(false), + m_completed(m_state->m_completed), m_priority(ThreadPriority::Default) {} @@ -70,14 +71,6 @@ void BasicThread::DoRunLoopCleanup(std::shared_ptr&& ctxt, std::sha m_stop = true; m_running = false; - // Need to ensure that "stop" and "running" are actually updated in memory before we mark "complete" -#if autowiring_USE_LIBCXX - std::atomic_thread_fence(std::memory_order_release); -#else - (std::lock_guard)state->m_lock; -#endif - m_completed = true; - // Tell our CoreRunnable parent that we're done to ensure that our reference count will be cleared. Stop(false); @@ -101,6 +94,7 @@ void BasicThread::DoRunLoopCleanup(std::shared_ptr&& ctxt, std::sha // Notify other threads that we are done. At this point, any held references that might still exist // notification must happen from a synchronized level in order to ensure proper ordering. std::lock_guard lk(state->m_lock); + state->m_completed = true; state->m_stateCondition.notify_all(); } @@ -132,8 +126,9 @@ bool BasicThread::OnStart(void) { if(!context) return false; - // Currently running: + // Currently running and started: m_running = true; + m_wasStarted = true; // Place the new thread entity directly in the space where it goes to avoid // any kind of races arising from asynchronous access to this space @@ -149,9 +144,8 @@ bool BasicThread::OnStart(void) { void BasicThread::OnStop(bool graceful) { // If we were never started, we need to set our completed flag to true - if (!m_running) { - m_completed = true; - } + if (!m_wasStarted) + m_state->m_completed = true; // Always invoke stop handler: OnStop(); @@ -162,10 +156,24 @@ void BasicThread::DoAdditionalWait(void) { std::unique_lock lk(m_state->m_lock); m_state->m_stateCondition.wait( lk, - [this] {return this->m_completed; } + [this] { return m_state->m_completed; } + ); +} + +bool BasicThread::DoAdditionalWait(std::chrono::nanoseconds timeout) { + // Wait for the run loop cleanup to happen in DoRunLoopCleanup + std::unique_lock lk(m_state->m_lock); + return m_state->m_stateCondition.wait_for( + lk, + timeout, + [this] { return m_state->m_completed; } ); } +bool BasicThread::IsCompleted(void) const { + return m_state->m_completed; +} + void BasicThread::ForceCoreThreadReidentify(void) { for(const auto& ctxt : ContextEnumerator(AutoGlobalContext())) { for(const auto& thread : ctxt->CopyBasicThreadList()) diff --git a/src/autowiring/BasicThreadStateBlock.cpp b/src/autowiring/BasicThreadStateBlock.cpp index 1b42237ab..6b28dbe4c 100644 --- a/src/autowiring/BasicThreadStateBlock.cpp +++ b/src/autowiring/BasicThreadStateBlock.cpp @@ -2,4 +2,9 @@ #include "stdafx.h" #include "BasicThreadStateBlock.h" -BasicThreadStateBlock::~BasicThreadStateBlock(){} +BasicThreadStateBlock::BasicThreadStateBlock(void): + m_completed(false) +{} + +BasicThreadStateBlock::~BasicThreadStateBlock(void) +{} diff --git a/src/autowiring/CMakeLists.txt b/src/autowiring/CMakeLists.txt index 83d646693..f502fdc18 100644 --- a/src/autowiring/CMakeLists.txt +++ b/src/autowiring/CMakeLists.txt @@ -15,6 +15,7 @@ set(Autowiring_SRCS atomic_object.h at_exit.h auto_id.h + auto_future.h AutoConfig.cpp AutoConfig.h AutoConfigManager.cpp @@ -132,7 +133,7 @@ set(Autowiring_SRCS ObjectPool.h ObjectPoolMonitor.cpp ObjectPoolMonitor.h - ObjectTraits.h + CoreObjectDescriptor.h SatCounter.h SatCounter.cpp SharedPointerSlot.h @@ -172,12 +173,14 @@ add_conditional_sources( ) add_windows_sources(Autowiring_SRCS + auto_future_win.h CoreThreadWin.cpp InterlockedExchangeWin.cpp thread_specific_ptr_win.h ) add_mac_sources(Autowiring_SRCS + auto_future_mac.h CoreThreadMac.cpp ) diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index b3941a104..651dd54fa 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -145,6 +145,11 @@ size_t CoreContext::GetChildCount(void) const { return m_children.size(); } +std::vector CoreContext::GetRunnables(void) const { + std::lock_guard lk(m_stateBlock->m_lock); + return std::vector(m_threads.begin(), m_threads.end()); +} + std::shared_ptr CoreContext::FirstChild(void) const { std::lock_guard lk(m_stateBlock->m_lock); @@ -189,7 +194,7 @@ const std::type_info& CoreContext::GetAutoTypeId(const AnySharedPointer& ptr) co if (q == m_typeMemos.end() || !q->second.pObjTraits) throw autowiring_error("Attempted to obtain the true type of a shared pointer that was not a member of this context"); - const ObjectTraits* pObjTraits = q->second.pObjTraits; + const CoreObjectDescriptor* pObjTraits = q->second.pObjTraits; return pObjTraits->type; } @@ -232,7 +237,7 @@ std::shared_ptr CoreContext::IncrementOutstandingThreadCount(void) { return retVal; } -void CoreContext::AddInternal(const ObjectTraits& traits) { +void CoreContext::AddInternal(const CoreObjectDescriptor& traits) { { std::unique_lock lk(m_stateBlock->m_lock); @@ -319,7 +324,7 @@ CoreContext::MemoEntry& CoreContext::FindByTypeUnsafe(AnySharedPointer& referenc } // Resolve based on iterated dynamic casts for each concrete type: - const ObjectTraits* pObjTraits = nullptr; + const CoreObjectDescriptor* pObjTraits = nullptr; for(const auto& type : m_concreteTypes) { if(!reference->try_assign(*type.value)) // No match, try the next entry @@ -381,8 +386,6 @@ void CoreContext::Initiate(void) { // Get the beginning of the thread list that we have at the time of lock acquisition // New threads are added to the front of the thread list, which means that objects // after this iterator are the ones that will need to be started - t_threadList::iterator beginning; - std::unique_lock lk(m_stateBlock->m_lock); // Now we can transition to initiated or running: @@ -431,7 +434,7 @@ void CoreContext::Initiate(void) { } // Now we can recover the first thread that will need to be started - beginning = m_threads.begin(); + auto beginning = m_threads.begin(); // Start our threads before starting any child contexts: lk.unlock(); @@ -452,7 +455,7 @@ void CoreContext::InitiateCoreThreads(void) { void CoreContext::SignalShutdown(bool wait, ShutdownMode shutdownMode) { // As we signal shutdown, there may be a CoreRunnable that is in the "running" state. If so, // then we will skip that thread as we signal the list of threads to shutdown. - t_threadList::iterator firstThreadToStop; + std::list::iterator firstThreadToStop; // Trivial return check if (IsShutdown()) @@ -647,7 +650,7 @@ void CoreContext::CancelAutowiringNotification(DeferrableAutowiring* pDeferrable // Always finalize this entry: auto strategy = pDeferrable->GetStrategy(); if(strategy) - strategy->Finalize(pDeferrable); + strategy->Finalize(); // Stores the immediate predecessor of the node we will linearly scan for in our // linked list. @@ -733,9 +736,9 @@ void CoreContext::BroadcastContextCreationNotice(const std::type_info& sigil) co m_pParent->BroadcastContextCreationNotice(sigil); } -void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, const ObjectTraits& entry) { +void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, const CoreObjectDescriptor& entry) { // Collection of satisfiable lists: - std::vector> satisfiable; + std::vector satisfiable; // Notify any autowired field whose autowiring was deferred. We do this by processing each entry // in the entire type memos collection. These entries are keyed on the type of the memo, and the @@ -790,9 +793,7 @@ void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, cons // are identified by an empty strategy, and we just skip them. auto strategy = pNext->GetStrategy(); if(strategy) - satisfiable.push_back( - std::make_pair(strategy, pNext) - ); + satisfiable.push_back(strategy); } } } @@ -816,7 +817,7 @@ void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, cons // Run through everything else and finalize it all: for(const auto& cur : satisfiable) - cur.first->Finalize(cur.second); + cur->Finalize(); } void CoreContext::AddEventReceiver(const JunctionBoxEntry& entry) { @@ -870,13 +871,13 @@ void CoreContext::RemoveEventReceivers(const t_rcvrSet& receivers) { m_pParent->RemoveEventReceivers(receivers); } -void CoreContext::UnsnoopEvents(CoreObject* oSnooper, const JunctionBoxEntry& receiver) { +void CoreContext::UnsnoopEvents(const AnySharedPointer& snooper, const JunctionBoxEntry& receiver) { { std::lock_guard lk(m_stateBlock->m_lock); if( // If the passed value is currently a snooper, then the caller has snooped a context and also // one of its parents. End here. - m_snoopers.count(oSnooper) || + m_snoopers.count(snooper) || // If we are an event receiver in this context, then the caller has snooped a context and // is also a proper EventReceiver in one of that context's ancestors--this is a fairly @@ -893,7 +894,7 @@ void CoreContext::UnsnoopEvents(CoreObject* oSnooper, const JunctionBoxEntryUnsnoopEvents(oSnooper, receiver); + m_pParent->UnsnoopEvents(snooper, receiver); } void CoreContext::FilterException(void) { @@ -937,6 +938,31 @@ void CoreContext::FilterFiringException(const JunctionBoxBase* pProxy, CoreObjec } } +void CoreContext::Snoop(const CoreObjectDescriptor& traits) { + // Add to collections of snoopers + InsertSnooper(traits.value); + + // Add EventReceiver + if (traits.receivesEvents) + AddEventReceiver(JunctionBoxEntry(this, traits.pCoreObject)); + + // Add PacketSubscriber; + if (!traits.subscriber.empty()) + AddPacketSubscriber(traits.subscriber); +} + +void CoreContext::Unsnoop(const CoreObjectDescriptor& traits) { + RemoveSnooper(traits.value); + + // Cleanup if its an EventReceiver + if (traits.receivesEvents) + UnsnoopEvents(traits.value, JunctionBoxEntry(this, traits.pCoreObject)); + + // Cleanup if its a packet listener + if (!traits.subscriber.empty()) + UnsnoopAutoPacket(traits); +} + void CoreContext::AddDeferredUnsafe(DeferrableAutowiring* deferrable) { // Determine whether a type memo exists right now for the thing we're trying to defer. If it doesn't // exist, we need to inject one in order to allow deferred satisfaction to know what kind of type we @@ -955,14 +981,14 @@ void CoreContext::AddDeferredUnsafe(DeferrableAutowiring* deferrable) { entry.pFirst = deferrable; } -void CoreContext::InsertSnooper(std::shared_ptr snooper) { +void CoreContext::InsertSnooper(const AnySharedPointer& snooper) { (std::lock_guard)m_stateBlock->m_lock, - m_snoopers.insert(snooper.get()); + m_snoopers.insert(snooper); } -void CoreContext::RemoveSnooper(std::shared_ptr snooper) { +void CoreContext::RemoveSnooper(const AnySharedPointer& snooper) { (std::lock_guard)m_stateBlock->m_lock, - m_snoopers.erase(snooper.get()); + m_snoopers.erase(snooper); } void CoreContext::AddContextMember(const std::shared_ptr& ptr) { @@ -1044,13 +1070,13 @@ void CoreContext::TryTransitionChildrenState(void) { lk.unlock(); } -void CoreContext::UnsnoopAutoPacket(const ObjectTraits& traits) { +void CoreContext::UnsnoopAutoPacket(const CoreObjectDescriptor& traits) { { std::lock_guard lk(m_stateBlock->m_lock); // If the passed value is currently a snooper, then the caller has snooped a context and also // one of its parents. End here. - if ( m_snoopers.count(traits.pCoreObject.get()) ) + if (m_snoopers.count(traits.value)) return; } diff --git a/src/autowiring/CoreJob.cpp b/src/autowiring/CoreJob.cpp index 6b884ce5c..619e08874 100644 --- a/src/autowiring/CoreJob.cpp +++ b/src/autowiring/CoreJob.cpp @@ -4,6 +4,18 @@ #include "CoreContext.h" #include FUTURE_HEADER +// Arm doesn't have std::future, but does have std::chrono. We need to convert from std::chrono +// to autoboost::chrono when passing arguments to "std::future"(alias to autoboost::future) on arm. +#if autowiring_BUILD_ARM +autoboost::chrono::nanoseconds NanosecondsForFutureWait(const std::chrono::nanoseconds& time) { + return autoboost::chrono::nanoseconds(time.count()); +} +#else +std::chrono::nanoseconds NanosecondsForFutureWait(const std::chrono::nanoseconds& time) { + return time; +} +#endif + CoreJob::CoreJob(const char* name) : ContextMember(name), m_running(false), @@ -118,3 +130,14 @@ void CoreJob::DoAdditionalWait(void) { m_curEvent = nullptr; } } + +bool CoreJob::DoAdditionalWait(std::chrono::nanoseconds timeout) { + if (!m_curEvent) + return true; + + std::future* ptr = static_cast*>(m_curEvent); + auto status = ptr->wait_for(NanosecondsForFutureWait(timeout)); + delete ptr; + m_curEvent = nullptr; + return status == std::future_status::ready; +} \ No newline at end of file diff --git a/src/autowiring/CoreRunnable.cpp b/src/autowiring/CoreRunnable.cpp index dbcb42b6f..730d360f6 100644 --- a/src/autowiring/CoreRunnable.cpp +++ b/src/autowiring/CoreRunnable.cpp @@ -58,26 +58,28 @@ void CoreRunnable::Stop(bool graceful) { } void CoreRunnable::Wait(void) { - std::unique_lock lk(m_lock); - m_cv.wait(lk, [this](){ return ShouldStop() && !IsRunning(); }); + { + std::unique_lock lk(m_lock); + m_cv.wait(lk, [this] { return ShouldStop() && !IsRunning(); }); + } DoAdditionalWait(); } bool CoreRunnable::WaitFor(std::chrono::nanoseconds timeout) { - std::unique_lock lk(m_lock); - if (m_cv.wait_for(lk, timeout, [this](){ return ShouldStop() && !IsRunning(); })) { - DoAdditionalWait(); - return true; + { + std::unique_lock lk(m_lock); + if (!m_cv.wait_for(lk, timeout, [this] { return ShouldStop() && !IsRunning(); })) + return false; } - return false; + return DoAdditionalWait(timeout); } template bool CoreRunnable::WaitUntil(TimeType timepoint) { - std::unique_lock lk(m_lock); - if (m_cv.wait_until(lk, timepoint, [this](){ return ShouldStop() && !IsRunning(); })) { - DoAdditionalWait(); - return true; + { + std::unique_lock lk(m_lock); + if (!m_cv.wait_until(lk, timepoint, [this] { return ShouldStop() && !IsRunning(); })) + return false; } - return false; + return DoAdditionalWait(timepoint - TimeType::now());; } diff --git a/src/autowiring/CoreThread.cpp b/src/autowiring/CoreThread.cpp index 3f748494d..2c857e1b2 100644 --- a/src/autowiring/CoreThread.cpp +++ b/src/autowiring/CoreThread.cpp @@ -84,11 +84,8 @@ bool CoreThread::WaitForEventUnsafe(std::unique_lock& lk, std::chron if(m_aborted) throw dispatch_aborted_exception(); - // Pull over any ready events: - PromoteReadyEventsUnsafe(); - - // Dispatch events if the queue is now non-empty: - if(!m_dispatchQueue.empty()) + if (PromoteReadyDispatchersUnsafe()) + // Dispatcher is ready to run! Exit our loop and dispatch an event break; if(status == std::cv_status::timeout) diff --git a/src/autowiring/DispatchQueue.cpp b/src/autowiring/DispatchQueue.cpp index cdf26e125..a9c8e6f9d 100644 --- a/src/autowiring/DispatchQueue.cpp +++ b/src/autowiring/DispatchQueue.cpp @@ -2,6 +2,7 @@ #include "stdafx.h" #include "DispatchQueue.h" #include "at_exit.h" +#include dispatch_aborted_exception::dispatch_aborted_exception(void){} dispatch_aborted_exception::~dispatch_aborted_exception(void){} @@ -23,41 +24,9 @@ DispatchQueue::~DispatchQueue(void) { } } -void DispatchQueue::Abort(void) { - std::lock_guard lk(m_dispatchLock); - m_aborted = true; - - // Do not permit any more lambdas to be pended to our queue: - m_dispatchCap = 0; - - // Destroy the whole dispatch queue: - while(!m_dispatchQueue.empty()) { - delete m_dispatchQueue.front(); - m_dispatchQueue.pop_front(); - } - - // Wake up anyone who is still waiting: - m_queueUpdated.notify_all(); -} - -std::chrono::steady_clock::time_point -DispatchQueue::SuggestSoonestWakeupTimeUnsafe(std::chrono::steady_clock::time_point latestTime) const { - return - m_delayedQueue.empty() ? - - // Nothing in the queue, no way to suggest a shorter time - latestTime : - - // Return the shorter of the maximum wait time and the time of the queue ready--we don't want to tell the - // caller to wait longer than the limit of their interest. - std::min( - m_delayedQueue.top().GetReadyTime(), - latestTime - ); -} - -void DispatchQueue::PromoteReadyEventsUnsafe(void) { +bool DispatchQueue::PromoteReadyDispatchersUnsafe(void) { // Move all ready elements out of the delayed queue and into the dispatch queue: + size_t nInitial = m_delayedQueue.size(); for( auto now = std::chrono::steady_clock::now(); !m_delayedQueue.empty() && m_delayedQueue.top().GetReadyTime() < now; @@ -65,6 +34,9 @@ void DispatchQueue::PromoteReadyEventsUnsafe(void) { ) // This item's ready time has elapsed, we can add it to our dispatch queue now: m_dispatchQueue.push_back(m_delayedQueue.top().Get()); + + // Something was promoted if the dispatch queue size is different + return nInitial != m_delayedQueue.size(); } void DispatchQueue::DispatchEventUnsafe(std::unique_lock& lk) { @@ -75,7 +47,7 @@ void DispatchQueue::DispatchEventUnsafe(std::unique_lock& lk) { m_dispatchQueue.pop_front(); bool wasEmpty = m_dispatchQueue.empty(); lk.unlock(); - + MakeAtExit( [this, wasEmpty] { // If we emptied the queue, we'd like to reobtain the lock and tell everyone @@ -87,12 +59,83 @@ void DispatchQueue::DispatchEventUnsafe(std::unique_lock& lk) { (*thunk)(); } +void DispatchQueue::Abort(void) { + std::lock_guard lk(m_dispatchLock); + m_aborted = true; + + // Do not permit any more lambdas to be pended to our queue: + m_dispatchCap = 0; + + // Destroy the whole dispatch queue: + while(!m_dispatchQueue.empty()) { + delete m_dispatchQueue.front(); + m_dispatchQueue.pop_front(); + } + + // Wake up anyone who is still waiting: + m_queueUpdated.notify_all(); +} + bool DispatchQueue::DispatchEvent(void) { std::unique_lock lk(m_dispatchLock); - if(m_dispatchQueue.empty()) + + // If the queue is empty and we fail to promote anything, return here + // Note that, due to short-circuiting, promotion will not take place if the queue is not empty. + // This behavior is by design. + if (m_dispatchQueue.empty() && !PromoteReadyDispatchersUnsafe()) return false; + assert(!m_dispatchQueue.empty()); DispatchEventUnsafe(lk); return true; } +int DispatchQueue::DispatchAllEvents(void) { + int retVal = 0; + while(DispatchEvent()) + retVal++; + return retVal; +} + +void DispatchQueue::AddExisting(DispatchThunkBase* pBase) { + std::unique_lock lk(m_dispatchLock); + if(m_dispatchQueue.size() >= m_dispatchCap) + return; + + m_dispatchQueue.push_back(pBase); + m_queueUpdated.notify_all(); + OnPended(std::move(lk)); +} + +std::chrono::steady_clock::time_point +DispatchQueue::SuggestSoonestWakeupTimeUnsafe(std::chrono::steady_clock::time_point latestTime) const { + return + m_delayedQueue.empty() ? + + // Nothing in the queue, no way to suggest a shorter time + latestTime : + + // Return the shorter of the maximum wait time and the time of the queue ready--we don't want to tell the + // caller to wait longer than the limit of their interest. + std::min( + m_delayedQueue.top().GetReadyTime(), + latestTime + ); +} + +DispatchQueue::DispatchThunkDelayedExpression DispatchQueue::operator+=(std::chrono::steady_clock::time_point rhs) { + return DispatchThunkDelayedExpression(this, rhs); +} + +void DispatchQueue::operator+=(DispatchThunkDelayed&& rhs) { + std::lock_guard lk(m_dispatchLock); + + m_delayedQueue.push(std::forward(rhs)); + if( + m_delayedQueue.top().GetReadyTime() == rhs.GetReadyTime() && + m_dispatchQueue.empty() + ) + // We're becoming the new next-to-execute entity, dispatch queue currently empty, trigger wakeup + // so our newly pended delay thunk is eventually processed. + m_queueUpdated.notify_all(); +} diff --git a/src/autowiring/JunctionBoxManager.cpp b/src/autowiring/JunctionBoxManager.cpp index d955247db..7ed8fc499 100644 --- a/src/autowiring/JunctionBoxManager.cpp +++ b/src/autowiring/JunctionBoxManager.cpp @@ -16,8 +16,7 @@ JunctionBoxManager::JunctionBoxManager(void) { m_junctionBoxes[p->ti] = p->NewJunctionBox(); // Make sure AutowiringEvents is in EventRegistry - assert(m_junctionBoxes.find(typeid(AutowiringEvents)) != m_junctionBoxes.end() - && "AutowiringEvents wasn't added to the event registry"); + m_junctionBoxes[typeid(AutowiringEvents)] = std::make_shared>(); // Always allow internal events m_junctionBoxes[typeid(AutowiringEvents)]->Initiate(); diff --git a/src/autowiring/test/AnySharedPointerTest.cpp b/src/autowiring/test/AnySharedPointerTest.cpp index b54d12184..ac74e4e1b 100644 --- a/src/autowiring/test/AnySharedPointerTest.cpp +++ b/src/autowiring/test/AnySharedPointerTest.cpp @@ -191,3 +191,20 @@ TEST_F(AnySharedPointerTest, VoidReturnExpected) { // Validate equivalence of the void operator: ASSERT_EQ(v.get(), slot->ptr()) << "Shared pointer slot did not return a void* with an expected value"; } + +TEST_F(AnySharedPointerTest, CanHoldCoreObject) { + auto co = std::make_shared(); + + AnySharedPointer x = co; + ASSERT_EQ(co, x) << "Held CoreObject was not equivalent to constructed instance"; +} + +TEST_F(AnySharedPointerTest, CanFastCastToSelf) { + autowiring::fast_pointer_cast_initializer::sc_init; + + auto co = std::make_shared(); + ASSERT_EQ( + co, + autowiring::fast_pointer_cast(co) + ) << "Could not cast a CoreObject instance to itself"; +} \ No newline at end of file diff --git a/src/autowiring/test/ArgumentTypeTest.cpp b/src/autowiring/test/ArgumentTypeTest.cpp index 3f98fd989..020881872 100644 --- a/src/autowiring/test/ArgumentTypeTest.cpp +++ b/src/autowiring/test/ArgumentTypeTest.cpp @@ -87,7 +87,7 @@ TEST_F(ArgumentTypeTest, TestAutoIn) { // Deduced Type const auto& arg = t_argShared::arg(*packet); - ASSERT_TRUE(arg.unique()) << "AutoPacket should store the sole shared pointer reference"; + ASSERT_EQ(2UL, arg.use_count()) << "AutoPacket should store exactly two shared pointer references"; } TEST_F(ArgumentTypeTest, TestAutoOut) { @@ -113,6 +113,6 @@ TEST_F(ArgumentTypeTest, TestAutoOut) { const Argument<0>* arg = nullptr; ASSERT_TRUE(packet->Get(arg)) << "Missing output"; - ASSERT_EQ(a0, packet->GetShared>()) << "Shared pointer copied incorrectly"; + ASSERT_EQ(a0, *packet->GetShared>()) << "Shared pointer copied incorrectly"; ASSERT_EQ(1, arg->i) << "Output was not copied"; } diff --git a/src/autowiring/test/AutoFilterCollapseRulesTest.cpp b/src/autowiring/test/AutoFilterCollapseRulesTest.cpp index 7ce36aab9..6c1cc8894 100644 --- a/src/autowiring/test/AutoFilterCollapseRulesTest.cpp +++ b/src/autowiring/test/AutoFilterCollapseRulesTest.cpp @@ -83,7 +83,7 @@ TEST_F(AutoFilterCollapseRulesTest, SharedPtrCollapse) { auto packet = factory->NewPacket(); packet->DecorateImmediate(constr_int); ASSERT_EQ(1, constr_filter->m_called) << "Called const reference method " << constr_filter->m_called << " times"; - ASSERT_EQ(0, shared_filter->m_called) << "Called shared pointer method " << shared_filter->m_called << " times"; + ASSERT_EQ(1, shared_filter->m_called) << "Called shared pointer method " << shared_filter->m_called << " times"; } constr_filter->m_called = 0; shared_filter->m_called = 0; @@ -129,14 +129,21 @@ TEST_F(AutoFilterCollapseRulesTest, ConstCollapse) { TEST_F(AutoFilterCollapseRulesTest, SharedPointerAliasingRules) { AutoRequired factory; - AutoRequired>> genFilter1; - AutoRequired> genFilter2; + bool gen1Called = false; + bool gen2Called = false; + + *factory += [&gen1Called](AutoPacket&, std::shared_ptr) { + gen1Called = true; + }; + *factory += [&gen2Called](AutoPacket&, int) { + gen2Called = true; + }; auto packet = factory->NewPacket(); packet->Decorate(55); - ASSERT_EQ(1UL, genFilter1->m_called) << "AutoFilter accepting a shared pointer was not called as expected"; - ASSERT_EQ(1UL, genFilter2->m_called) << "AutoFilter accepting a decorated type was not called as expected"; + ASSERT_TRUE(gen1Called) << "AutoFilter accepting a shared pointer was not called as expected"; + ASSERT_TRUE(gen2Called) << "AutoFilter accepting a decorated type was not called as expected"; } class ProducesSharedPointer { @@ -207,3 +214,23 @@ TEST_F(AutoFilterCollapseRulesTest, CtorRequiredWPI) { AutoRequired(); AutoRequired()->NewPacket(); } + +TEST_F(AutoFilterCollapseRulesTest, UnsatisfiableDecoration) { + AutoRequired factory; + + bool bInvoked = false; + *factory += [&](std::shared_ptr> dec) { + ASSERT_FALSE(dec) << "Decoration was attached to the packet when it should not have been"; + bInvoked = true; + }; + *factory += [](const Decoration<0>&) { + FAIL() << "A filter was invoked when it should have been unsatisfiable"; + }; + + // Kick off what should be a launch of the shared pointer-accepting input + auto packet = factory->NewPacket(); + packet->Decorate(std::shared_ptr>()); + + // Ensure the correct decoration was invoked + ASSERT_TRUE(bInvoked) << "AutoFilter was not invoked in an unsatisfiable case as expected"; +} \ No newline at end of file diff --git a/src/autowiring/test/AutoFutureTest.cpp b/src/autowiring/test/AutoFutureTest.cpp new file mode 100644 index 000000000..a0cadaa61 --- /dev/null +++ b/src/autowiring/test/AutoFutureTest.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include +#include +#include + +class AutoFutureTest: + public testing::Test +{}; + +TEST_F(AutoFutureTest, VerifyFutureExtraction) { + auto f = std::async( + std::launch::async, + [] { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + return 5; + } + ); + + auto pr = std::make_shared>(); + autowiring::then( + f, + [pr] { pr->set_value(201);} + ); + + // Wait for our async launch to conclude + f.get(); + + // Continuation task checks + auto future = pr->get_future(); + ASSERT_EQ( + std::future_status::ready, + future.wait_for(std::chrono::seconds(5)) + ) << "Continuation task did not launch as expected when the main task concluded"; + ASSERT_EQ(201, future.get()) << "Future value not propagated correctly back to the origin"; +} + +TEST_F(AutoFutureTest, CorrectDestructionTest) { + auto captured = std::make_shared(false); + { + auto f = std::async( + std::launch::deferred, + [] { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + return 5; + } + ); + + autowiring::then(f, [captured] { + *captured = true; + }); + f.get(); + ASSERT_FALSE(captured.unique()) << "Lambda was destroyed prematurely"; + } + + ASSERT_TRUE(*captured) << "Continuation lambda did not run as expected"; + ASSERT_TRUE(captured.unique()) << "Continuation lambda leaked memory"; +} \ No newline at end of file diff --git a/src/autowiring/test/AutowiringTest.cpp b/src/autowiring/test/AutowiringTest.cpp index 81a961836..48181d0b0 100644 --- a/src/autowiring/test/AutowiringTest.cpp +++ b/src/autowiring/test/AutowiringTest.cpp @@ -111,3 +111,70 @@ TEST_F(AutowiringTest, CanAutowireCompletelyUndefinedType) { Autowired cut; ASSERT_EQ(nullptr, cut.get_unsafe()) << "Autowiring of a completely undefined type succeeded unexpectedly"; } + +class StaticNewInt; + +StaticNewInt* CreateStaticNewIntImpl(); +StaticNewInt* CreateStaticNewIntImpl(std::unique_ptr); + +class StaticNewInt: + public CoreObject +{ +protected: + StaticNewInt(void){} +public: + static StaticNewInt* New(std::unique_ptr value) { + return CreateStaticNewIntImpl(std::move(value)); + } + + static StaticNewInt* New(void) { + return CreateStaticNewIntImpl(); + } + + virtual int getValue(void) = 0; +}; + +class StaticNewIntImpl: + public StaticNewInt +{ +public: + StaticNewIntImpl(void): + m_value(new int(42)) + {} + StaticNewIntImpl(std::unique_ptr val): + m_value(std::move(val)) + {} + + int getValue(void) override { + return *m_value; + } + + std::unique_ptr m_value; +}; + +StaticNewInt* CreateStaticNewIntImpl(){ + return new StaticNewIntImpl(); +} +StaticNewInt* CreateStaticNewIntImpl(std::unique_ptr val){ + return new StaticNewIntImpl(std::move(val)); +} + +TEST_F(AutowiringTest, StaticNewWithArgs) { + const bool static_new = has_static_new>::value; + ASSERT_TRUE(static_new) << "has_static_new didn't correctly identify static New()"; + + { + AutoCreateContext ctxt; + ASSERT_NO_THROW(ctxt->Inject()) << "Exception throws while injecting member"; + AutowiredFast obj(ctxt); + EXPECT_EQ(42, obj->getValue()) << "Wrong constructor called"; + ctxt->SignalShutdown(true); + } + { + AutoCreateContext ctxt; + ASSERT_NO_THROW(auto obj = ctxt->Inject(std::unique_ptr(new int(1337)))) << "Exception throws while injecting member"; + AutowiredFast obj(ctxt); + EXPECT_EQ(1337, obj->getValue()) << "Wrong constructor called"; + ctxt->SignalShutdown(true); + } +} diff --git a/src/autowiring/test/CMakeLists.txt b/src/autowiring/test/CMakeLists.txt index 1dfd5b536..ef348d204 100644 --- a/src/autowiring/test/CMakeLists.txt +++ b/src/autowiring/test/CMakeLists.txt @@ -45,6 +45,7 @@ set(AutowiringTest_SRCS SelfSelectingFixtureTest.cpp TeardownNotifierTest.cpp TypeRegistryTest.cpp + SatisfiabilityTest.cpp ScopeTest.cpp SnoopTest.cpp TupleTest.cpp @@ -60,6 +61,11 @@ set(AutowiringTest_SRCS UuidTest.cpp ) +add_windows_sources( + AutowiringTest_SRCS + AutoFutureTest.cpp +) + if(autowiring_USE_LIBCXX) set(AutowiringTest_SRCS ${AutowiringTest_SRCS} AutoFilterFunctionTest.cpp diff --git a/src/autowiring/test/ContextCleanupTest.cpp b/src/autowiring/test/ContextCleanupTest.cpp index 675a2a913..f796121f5 100644 --- a/src/autowiring/test/ContextCleanupTest.cpp +++ b/src/autowiring/test/ContextCleanupTest.cpp @@ -74,7 +74,7 @@ TEST_F(ContextCleanupTest, VerifyContextDtor) { AutoRequired simple; objVerifier = simple; - // Each ObjectTraits instance holds 2 strong references to SimpleObject, as CoreObject type and as ContextMember type. + // Each CoreObjectDescriptor instance holds 2 strong references to SimpleObject, as CoreObject type and as ContextMember type. // One instance is held in CoreContext::m_concreteTypes and the other in the CoreContext::m_typeMemos. // Finally, once more reference is held by the shared_ptr inheritance of simple. EXPECT_EQ(5, objVerifier.use_count()) << "Unexpected number of references to a newly constructed object"; @@ -153,7 +153,9 @@ TEST_F(ContextCleanupTest, VerifyGracefulThreadCleanup) { *ct += [called] { *called = true; }; // Verify that a graceful shutdown ensures both lambdas are called: + ASSERT_FALSE(ctxt->IsShutdown()) << "Context shut down prematurely"; ctxt->SignalShutdown(true, ShutdownMode::Graceful); + ASSERT_FALSE(ct->IsRunning()) << "Thread still reported as running even after a wait concluded"; ASSERT_TRUE(*called) << "Graceful shutdown did not correctly run down all lambdas"; } diff --git a/src/autowiring/test/CoreThreadTest.cpp b/src/autowiring/test/CoreThreadTest.cpp index 78cfcf627..b98281288 100644 --- a/src/autowiring/test/CoreThreadTest.cpp +++ b/src/autowiring/test/CoreThreadTest.cpp @@ -516,4 +516,91 @@ TEST_F(CoreThreadTest, LightsOutPassiveCall) { // Verify no bad things happened: ASSERT_FALSE(mpc->bStartExcepted) << "Exception occurred during an attempt to elevate to synchronized level from CoreThread::OnStart"; ASSERT_FALSE(mpc->bStopExcepted) << "Exception occurred during an attempt to elevate to synchronized level from CoreThread::OnStop"; +} + +class CoreThreadExtraction: + public CoreThread +{ +public: + using CoreThread::m_queueUpdated; +}; + +TEST_F(CoreThreadTest, SpuriousWakeupTest) { + AutoCurrentContext()->Initiate(); + AutoRequired extraction; + + std::mutex lock; + std::condition_variable cv; + bool ready = false; + + auto wakeFn = [&] { + std::lock_guard lk(lock); + ready = true; + cv.notify_all(); + }; + + // Add a delayed lambda that we know won't launch and another one that should launch right away + *extraction += wakeFn; + *extraction += std::chrono::hours(1), [] {}; + + // Delay until the extraction lambda has run: + std::unique_lock lk(lock); + ASSERT_TRUE(cv.wait_for(lk, std::chrono::seconds(5), [&] { return ready; })); + + // Now force a spurious wakeup--this shouldn't technically be a problem + extraction->m_queueUpdated.notify_all(); + + // Delayed wake function, block for this to happen: + ready = false; + *extraction += std::chrono::milliseconds(1), wakeFn; + ASSERT_TRUE(cv.wait_for(lk, std::chrono::seconds(5), [&] { return ready; })); + + ASSERT_EQ(1UL, extraction->GetDispatchQueueLength()) << "Dispatch queue changed size under a spurious wakeup condition"; +} + +class BlocksInOnStop: + public CoreThread +{ +public: + bool is_waiting = false; + bool signal = false; + + void Run(void) { + // Let the run loop return. This triggers cleanup operations and ultimately causes OnStop + // to get called in our own thread context. + } + + bool Block(std::chrono::nanoseconds timeout) { + std::unique_lock lk(m_lock); + return m_cv.wait_for(lk, timeout, [this] {return is_waiting; }); + } + + void Continue(void) { + std::lock_guard lk(m_lock); + signal = true; + m_cv.notify_all(); + } + + void OnStop(void) override { + std::unique_lock lk(this->m_lock); + is_waiting = true; + m_cv.notify_all(); + m_cv.wait_for(lk, std::chrono::seconds(5), [this] { return signal; }); + } +}; + +TEST_F(CoreThreadTest, ContextWaitTimesOutInOnStop) { + AutoCurrentContext ctxt; + AutoRequired bios; + ctxt->Initiate(); + + // Wait for our pathological case to be waiting before we try shutting down the context + ASSERT_TRUE(bios->Block(std::chrono::seconds(5))) << "Blocking class failed to enter a blocked condition as expected"; + + ctxt->SignalShutdown(); + ASSERT_FALSE(bios->WaitFor(std::chrono::milliseconds(1))) << "Timed wait on a misbehaving CoreThread did not time out as expected"; + ASSERT_FALSE(ctxt->Wait(std::chrono::milliseconds(1))) << "Context wait routine did not timeout as expected"; + + // Let BIOS back out now: + bios->Continue(); } \ No newline at end of file diff --git a/src/autowiring/test/DecoratorTest.cpp b/src/autowiring/test/DecoratorTest.cpp index 3ded3b1cb..9c3822894 100644 --- a/src/autowiring/test/DecoratorTest.cpp +++ b/src/autowiring/test/DecoratorTest.cpp @@ -68,6 +68,8 @@ TEST_F(DecoratorTest, VerifySimplePacketDecoration) { } TEST_F(DecoratorTest, VerifyDecoratorAwareness) { + auto filter = [](const Decoration<0>& zero, const Decoration<1>& one) {}; + // Create a packet while the factory has no subscribers: AutoRequired factory; auto packet1 = factory->NewPacket(); @@ -76,17 +78,17 @@ TEST_F(DecoratorTest, VerifyDecoratorAwareness) { ASSERT_FALSE(packet1->HasSubscribers>()) << "Subscription exists where one should not have existed"; // Create another packet where a subscriber exists: - AutoRequired filterA; + *factory += filter; auto packet2 = factory->NewPacket(); // Verify the first packet still does not have subscriptions: - ASSERT_THROW(packet1->GetSatisfaction(), autowiring_error) << "Subscription was incorrectly, retroactively added to a packet"; + ASSERT_THROW(packet1->GetSatisfaction(), autowiring_error) << "Subscription was incorrectly, retroactively added to a packet"; ASSERT_FALSE(packet1->HasSubscribers>()) << "Subscription was incorrectly, retroactively added to a packet"; ASSERT_EQ(0UL, packet1->GetDecorationTypeCount()) << "Subscription was incorrectly, retroactively added to a packet"; ASSERT_FALSE(packet1->HasSubscribers>()) << "Subscription was incorrectly, retroactively added to a packet"; // Verify the second one does: - ASSERT_THROW(packet2->GetSatisfaction(), autowiring_error) << "Packet lacked an expected subscription"; + ASSERT_THROW(packet2->GetSatisfaction(), autowiring_error) << "Packet lacked an expected subscription"; ASSERT_EQ(2UL, packet2->GetDecorationTypeCount()) << "Incorrect count of expected decorations"; ASSERT_TRUE(packet2->HasSubscribers>()) << "Packet lacked an expected subscription"; } diff --git a/src/autowiring/test/DtorCorrectnessTest.cpp b/src/autowiring/test/DtorCorrectnessTest.cpp index adf4049db..6e30b5316 100644 --- a/src/autowiring/test/DtorCorrectnessTest.cpp +++ b/src/autowiring/test/DtorCorrectnessTest.cpp @@ -114,8 +114,8 @@ TEST_F(DtorCorrectnessTest, VerifyDeferringDtors) { listener2->Wait(); // Verify that we actually hit something: - EXPECT_TRUE(listener1->m_hitDeferred) << "Failed to hit a listener's deferred call"; - EXPECT_TRUE(listener2->m_hitDeferred) << "Failed to hit a listener's deferred call"; + EXPECT_TRUE(listener1->m_hitDeferred) << "Failed to hit listener #1's deferred call"; + EXPECT_TRUE(listener2->m_hitDeferred) << "Failed to hit listener #2's deferred call"; // Release all of our pointers: listener1.reset(); diff --git a/src/autowiring/test/FactoryTest.cpp b/src/autowiring/test/FactoryTest.cpp index 70f6efdf1..db43199be 100644 --- a/src/autowiring/test/FactoryTest.cpp +++ b/src/autowiring/test/FactoryTest.cpp @@ -155,3 +155,29 @@ TEST_F(FactoryTest, VerifyCanAutowireActualType) { ASSERT_TRUE(concrete.IsAutowired()) << "Failed to find the concrete derived type in a factory new construction in a case where it is known to exist"; } + +class AcceptsString: + public Object +{ +public: + AcceptsString(const char* str) : + str(str) + {} + + static AcceptsString* New(const char* pstrNamespace) { + return new AcceptsString(pstrNamespace); + } + + const char* const str; +}; + +TEST_F(FactoryTest, StringLiteralCheck) { + // This case can sometimes fail to compile because of the way we check for static new. + // String literals are interpreted to be a reference to an array, and taking the address of such a type can + // cause problems. + AutoCurrentContext()->Inject("Abcd"); + Autowired as; + + ASSERT_TRUE(as.IsAutowired()) << "Standard injection with an argument did not correctly forward the argument"; + ASSERT_STREQ("Abcd", as->str) << "Constructed type was not constructed properly"; +} \ No newline at end of file diff --git a/src/autowiring/test/ObjectPoolTest.cpp b/src/autowiring/test/ObjectPoolTest.cpp index fcf7f45f6..6f01c815a 100644 --- a/src/autowiring/test/ObjectPoolTest.cpp +++ b/src/autowiring/test/ObjectPoolTest.cpp @@ -367,6 +367,8 @@ TEST_F(ObjectPoolTest, MovableObjectPoolAysnc) { *AutoRequired() += [objs] {}; } + ASSERT_EQ(0, from.GetCached()) << "Initial pool received cached entities unexpectedly"; + // Kick off threads, then immediately and asynchronously move the pool: AutoCurrentContext()->Initiate(); ObjectPool to = std::move(from); diff --git a/src/autowiring/test/SatisfiabilityTest.cpp b/src/autowiring/test/SatisfiabilityTest.cpp new file mode 100644 index 000000000..931799734 --- /dev/null +++ b/src/autowiring/test/SatisfiabilityTest.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include +#include "TestFixtures/Decoration.hpp" + +class SatisfiabilityTest: + public testing::Test +{ +public: + SatisfiabilityTest(void) { + AutoCurrentContext()->Initiate(); + } + + AutoRequired factory; +}; + +TEST_F(SatisfiabilityTest, 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> dec) { bSharedPtrCalled = true; }; + + packet->Unsatisfiable>(); + + 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"; +} \ No newline at end of file diff --git a/src/autowiring/test/SnoopTest.cpp b/src/autowiring/test/SnoopTest.cpp index 8e17c4102..473019e97 100644 --- a/src/autowiring/test/SnoopTest.cpp +++ b/src/autowiring/test/SnoopTest.cpp @@ -319,7 +319,29 @@ TEST_F(SnoopTest, CanSnoopAutowired) { AutoCurrentContext ctxt; ctxt->Inject(); - // Now autowire what we injeced and verify we can snoop this directly + // Now autowire what we injected and verify we can snoop this directly Autowired so; ctxt->Snoop(so); } + +TEST_F(SnoopTest, RuntimeSnoopCall) { + AutoCurrentContext ctxt; + ctxt->Initiate(); + + auto x = std::make_shared(); + CoreObjectDescriptor traits(x); + + // Try to snoop and verify that the snooped member gets an event: + ctxt->Snoop(x); + + AutoFired ubl; + ubl(&UpBroadcastListener::SimpleCall)(); + + ASSERT_TRUE(x->m_simpleCall) << "Runtime snoop call did not correctly register a child context member"; + ASSERT_EQ(1UL, x->m_callCount) << "Call count to a child member was incorrect"; + + // And the alternative variant next: + ctxt->Unsnoop(x); + ubl(&UpBroadcastListener::SimpleCall)(); + ASSERT_EQ(1UL, x->m_callCount) << "Snoop method was invoked after the related type was removed"; +} \ No newline at end of file diff --git a/version.cmake b/version.cmake index 2ecac9f14..116908e6e 100644 --- a/version.cmake +++ b/version.cmake @@ -1 +1 @@ -set(autowiring_VERSION 0.4.3) +set(autowiring_VERSION 0.4.4)