#pragma once #include <functional> #include "threadlocal.hpp" #include <atomic> #include "../memory/PoolAllocator.hpp" #include "../common/type_traits.hpp" #include "../common/errors.hpp" #ifdef _WIN32 # undef Yield #else #ifdef __APPLE__ #include <sys/ucontext.h> #else #include <ucontext.h> #endif #endif #ifndef SPRAWL_COROUTINE_SAFETY_CHECKS # define SPRAWL_COROUTINE_SAFETY_CHECKS SPRAWL_DEBUG #endif namespace sprawl { namespace threading { class CoroutineBase; class Coroutine; template<typename SendType, typename YieldType> class CoroutineWithChannel; template<typename ReturnType> using Generator = CoroutineWithChannel<void, ReturnType>; template<typename ReturnType, typename CoroutineType> class CoroutineIterator; enum class CoroutineState { Invalid, Created, Executing, Paused, Completed, }; enum class CoroutineType { Basic, SendOnly, ReceiveOnly, BiDirectional, }; } } //////////////////////////////////////////////////////////////////////////////// /// CoroutineBase //////////////////////////////////////////////////////////////////////////////// class sprawl::threading::CoroutineBase { public: static SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<void> Yield(); template<typename SendType, typename YieldType> static SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<SendType&> Receive(YieldType const& value); template<typename SendType, typename YieldType> static SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<SendType&> Receive(YieldType&& value, typename std::enable_if<!std::is_reference<YieldType>::value>::type* = 0); template<typename SendType> static SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<SendType&> Receive(); template<typename YieldType> static SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<void> Yield(YieldType const& value); template<typename YieldType> static SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<void> Yield(YieldType&& value, typename std::enable_if<!std::is_reference<YieldType>::value>::type* = 0); CoroutineBase(); CoroutineBase(CoroutineBase const& other); CoroutineBase& operator=(CoroutineBase const& other); virtual ~CoroutineBase(); void Start() { Resume(); } void Pause(); void Resume(); void Reset(); void operator()() { Resume(); } CoroutineState State(); size_t StackSize(); static CoroutineBase GetCurrentCoroutine(); CoroutineBase GetCallingCoroutine(); bool operator==(CoroutineBase const& other) const { return m_holder == other.m_holder; } bool operator!=(CoroutineBase const& other) const { return !operator==(other); } CoroutineType Type(); protected: template<typename YieldType, typename CoroutineType> friend class CoroutineIterator; static ThreadLocal<CoroutineBase*> ms_coroutineInitHelper; static ThreadLocal<CoroutineBase> ms_thisThreadCoroutine; static void entryPoint_(); void run_(); void reactivate_(); void releaseRef_(); template<typename ReturnType> struct Holder; explicit CoroutineBase(Holder<void>* holder); Holder<void>* m_holder; bool m_ownsHolder; }; template<typename ReturnType> struct sprawl::threading::CoroutineBase::Holder { inline void IncRef() { ++m_refCount; } inline bool DecRef() { return (--m_refCount == 0); } std::function<ReturnType ()> m_function; size_t m_stackSize; void* m_stack; void* m_stackPointer; CoroutineState m_state; #ifndef _WIN32 ucontext_t m_context; #endif std::atomic<int> m_refCount; CoroutineBase m_priorCoroutine; #if SPRAWL_EXCEPTIONS_ENABLED std::exception_ptr m_exception; #endif static inline Holder<ReturnType>* Create(std::function<ReturnType()> function, size_t stackSize) { typedef memory::PoolAllocator<sizeof(Holder)> holderAlloc; Holder* ret = (Holder*)holderAlloc::alloc(); new(ret)Holder(function, stackSize); return ret; } static inline Holder<ReturnType>* Create() { typedef memory::PoolAllocator<sizeof(Holder)> holderAlloc; Holder* ret = (Holder*)holderAlloc::alloc(); new(ret)Holder(); return ret; } virtual void Release() { typedef memory::PoolAllocator<sizeof(Holder)> holderAlloc; this->~Holder(); holderAlloc::free(this); } virtual CoroutineType Type() { return CoroutineType::Basic; } virtual size_t SizeOfSendType() { return 0; } virtual size_t SizeOfYieldType() { return 0; } virtual void RunFunction() { m_function(); } virtual ~Holder(); protected: Holder(); Holder(std::function<ReturnType ()> function, size_t stackSize); }; //////////////////////////////////////////////////////////////////////////////// /// CoroutineIterator class //////////////////////////////////////////////////////////////////////////////// template<typename YieldType, typename CoroutineType> class sprawl::threading::CoroutineIterator : public std::iterator<std::forward_iterator_tag, YieldType, std::ptrdiff_t, YieldType*, YieldType&> { public: CoroutineIterator(CoroutineBase& routine); CoroutineIterator(CoroutineBase& routine, CoroutineState state); CoroutineIterator& operator++(); bool operator==(CoroutineIterator const& other); bool operator!=(CoroutineIterator const& other); YieldType* operator->(); YieldType& operator*(); private: CoroutineBase& m_routine; YieldType m_value; CoroutineState m_state; }; //////////////////////////////////////////////////////////////////////////////// /// Coroutine - Basic start/pause Configuration //////////////////////////////////////////////////////////////////////////////// class sprawl::threading::Coroutine : public sprawl::threading::CoroutineBase { public: Coroutine() : CoroutineBase() { // } Coroutine(std::function<void()> function, size_t stackSize = 0) : CoroutineBase(CoroutineBase::Holder<void>::Create(function, stackSize)) { // } Coroutine(std::nullptr_t npt, size_t stackSize = 0) : CoroutineBase(CoroutineBase::Holder<void>::Create(npt, stackSize)) { // } Coroutine(CoroutineBase const& other) : CoroutineBase(other) { // } template< typename Callable, typename... Params, typename = typename std::enable_if< !type_traits::HasEmptyOperatorParens<Callable>::value && !std::is_base_of<CoroutineBase, typename std::remove_reference<Callable>::type>::value && !std::is_same<std::nullptr_t, Callable>::value >::type > Coroutine(Callable && callable, Params &&... params) : CoroutineBase(CoroutineBase::Holder<void>::Create(std::bind(std::forward<Callable>(callable), std::forward<Params>(params)...), 0)) { // } }; //////////////////////////////////////////////////////////////////////////////// /// CoroutineWithChannel<SendType, YieldType> - Bidirectional Configuration //////////////////////////////////////////////////////////////////////////////// template<typename SendType, typename YieldType> class sprawl::threading::CoroutineWithChannel : public sprawl::threading::CoroutineBase { public: CoroutineWithChannel(std::function<YieldType()> function, size_t stackSize = 0) : CoroutineBase(ChannelHolder::Create(function, stackSize)) { // NOP } CoroutineWithChannel(std::nullptr_t npt, size_t stackSize = 0) : CoroutineBase(ChannelHolder::Create(npt, stackSize)) { // NOP } CoroutineWithChannel(CoroutineBase const& other) : CoroutineBase(other) { // NOP } template< typename Callable, typename... Params, typename = typename std::enable_if< !type_traits::HasEmptyOperatorParens<Callable>::value && !std::is_base_of<CoroutineBase, typename std::remove_reference<Callable>::type>::value && !std::is_same<std::nullptr_t, Callable>::value >::type > CoroutineWithChannel(Callable && callable, Params &&... params) : CoroutineBase(ChannelHolder::Create(std::bind(std::forward<Callable>(callable), std::forward<Params>(params)...), 0)) { // } CoroutineWithChannel() : CoroutineBase() { // NOP } SendType& Receive(YieldType const& value) { reinterpret_cast<ChannelHolder*>(m_holder)->m_receivedValue = value; Pause(); return reinterpret_cast<ChannelHolder*>(m_holder)->m_sentValue; } YieldType& Send(SendType const& value) { reinterpret_cast<ChannelHolder*>(m_holder)->m_sentValue = value; Resume(); return reinterpret_cast<ChannelHolder*>(m_holder)->m_receivedValue; } SendType& Receive(YieldType&& value) { reinterpret_cast<ChannelHolder*>(m_holder)->m_receivedValue = std::move(value); Pause(); return reinterpret_cast<ChannelHolder*>(m_holder)->m_sentValue; } YieldType& Send(SendType&& value) { reinterpret_cast<ChannelHolder*>(m_holder)->m_sentValue = std::move(value); Resume(); return reinterpret_cast<ChannelHolder*>(m_holder)->m_receivedValue; } YieldType& Start() { Resume(); return reinterpret_cast<ChannelHolder*>(m_holder)->m_receivedValue; } YieldType& operator()() { return Start(); } YieldType& operator()(SendType const& value) { return Send(value); } YieldType& operator()(SendType&& value) { return Send(std::move(value)); } private: struct ChannelHolder : public CoroutineBase::Holder<YieldType> { static_assert(sizeof(CoroutineBase::Holder<void>) == sizeof(CoroutineBase::Holder<YieldType>), "Holder size must not change based on template type."); static CoroutineBase::Holder<void>* Create(std::function<YieldType()> function, size_t stackSize) { typedef memory::PoolAllocator<sizeof(ChannelHolder)> holderAlloc; ChannelHolder* ret = (ChannelHolder*)holderAlloc::alloc(); new(ret)ChannelHolder(function, stackSize); return reinterpret_cast<CoroutineBase::Holder<void>*>(ret); } virtual void Release() override { typedef memory::PoolAllocator<sizeof(ChannelHolder)> holderAlloc; this->~ChannelHolder(); holderAlloc::free(this); } virtual CoroutineType Type() override { return CoroutineType::BiDirectional; } virtual size_t SizeOfSendType() override { return sizeof(SendType); } virtual size_t SizeOfYieldType() override { return sizeof(YieldType); } virtual void RunFunction() override { m_receivedValue = this->m_function(); } ChannelHolder(std::function<YieldType ()> function, size_t stackSize) : Holder<YieldType>(function, stackSize) , m_sentValue() , m_receivedValue() { // NOP } SendType m_sentValue; YieldType m_receivedValue; }; }; //////////////////////////////////////////////////////////////////////////////// /// CoroutineWithChannel<SendType, void> - Send-Only Configuration //////////////////////////////////////////////////////////////////////////////// template<typename SendType> class sprawl::threading::CoroutineWithChannel<SendType, void> : public sprawl::threading::CoroutineBase { public: CoroutineWithChannel(std::function<void ()> function, size_t stackSize = 0) : CoroutineBase(ChannelHolder::Create(function, stackSize)) { // NOP } CoroutineWithChannel(std::nullptr_t npt, size_t stackSize = 0) : CoroutineBase(ChannelHolder::Create(npt, stackSize)) { // NOP } CoroutineWithChannel(CoroutineBase const& other) : CoroutineBase(other) { // NOP } CoroutineWithChannel() : CoroutineBase() { // NOP } template< typename Callable, typename... Params, typename = typename std::enable_if< !type_traits::HasEmptyOperatorParens<Callable>::value && !std::is_base_of<CoroutineBase, typename std::remove_reference<Callable>::type>::value && !std::is_same<std::nullptr_t, Callable>::value >::type > CoroutineWithChannel(Callable && callable, Params &&... params) : CoroutineBase(ChannelHolder::Create(std::bind(std::forward<Callable>(callable), std::forward<Params>(params)...), 0)) { // } SendType& Receive() { Pause(); return reinterpret_cast<ChannelHolder*>(m_holder)->m_sentValue; } void Send(SendType const& value) { reinterpret_cast<ChannelHolder*>(m_holder)->m_sentValue = value; Resume(); } void Send(SendType&& value) { reinterpret_cast<ChannelHolder*>(m_holder)->m_sentValue = std::move(value); Resume(); } using CoroutineBase::operator(); void operator()(SendType const& value) { Send(value); } void operator()(SendType&& value) { Send(std::move(value)); } private: struct ChannelHolder : public CoroutineBase::Holder<void> { static CoroutineBase::Holder<void>* Create(std::function<void()> function, size_t stackSize = 0) { typedef memory::PoolAllocator<sizeof(ChannelHolder)> holderAlloc; ChannelHolder* ret = (ChannelHolder*)holderAlloc::alloc(); new(ret) ChannelHolder(function, stackSize); return ret; } virtual void Release() override { typedef memory::PoolAllocator<sizeof(ChannelHolder)> holderAlloc; this->~ChannelHolder(); holderAlloc::free(this); } virtual CoroutineType Type() override { return CoroutineType::SendOnly; } virtual size_t SizeOfSendType() override { return sizeof(SendType); } ChannelHolder(std::function<void()> function, size_t stackSize) : Holder(function, stackSize) , m_sentValue() { // NOP } SendType m_sentValue; }; }; //////////////////////////////////////////////////////////////////////////////// /// CoroutineWithChannel<void, YieldType> - Yield-Only Configuration //////////////////////////////////////////////////////////////////////////////// template<typename YieldType> class sprawl::threading::CoroutineWithChannel<void, YieldType> : public sprawl::threading::CoroutineBase { public: typedef CoroutineIterator<YieldType, CoroutineWithChannel<void, YieldType>> iterator; CoroutineWithChannel(std::function<YieldType ()> function, size_t stackSize = 0) : CoroutineBase(ChannelHolder::Create(function, stackSize)) { // NOP } CoroutineWithChannel(std::nullptr_t npt, size_t stackSize = 0) : CoroutineBase(ChannelHolder::Create(npt, stackSize)) { // NOP } CoroutineWithChannel(CoroutineBase const& other) : CoroutineBase(other) { // NOP } CoroutineWithChannel() : CoroutineBase() { // NOP } template< typename Callable, typename... Params, typename = typename std::enable_if< !type_traits::HasEmptyOperatorParens<Callable>::value && !std::is_base_of<CoroutineBase, typename std::remove_reference<Callable>::type>::value && !std::is_same<std::nullptr_t, Callable>::value >::type > CoroutineWithChannel(Callable && callable, Params &&... params) : CoroutineBase(ChannelHolder::Create(std::bind(std::forward<Callable>(callable), std::forward<Params>(params)...), 0)) { // } void Yield(YieldType const& value) { reinterpret_cast<ChannelHolder*>(m_holder)->m_receivedValue = value; Pause(); } void Yield(YieldType&& value) { reinterpret_cast<ChannelHolder*>(m_holder)->m_receivedValue = std::move(value); Pause(); } YieldType& Resume() { CoroutineBase::Resume(); return reinterpret_cast<ChannelHolder*>(m_holder)->m_receivedValue; } YieldType& Start() { return Resume(); } YieldType& operator()() { return Resume(); } iterator begin() { return iterator(*this); } iterator end() { return iterator(*this, CoroutineState::Completed); } private: struct ChannelHolder : public CoroutineBase::Holder<YieldType> { static_assert(sizeof(CoroutineBase::Holder<void>) == sizeof(CoroutineBase::Holder<YieldType>), "Holder size must not change based on template type."); static CoroutineBase::Holder<void>* Create(std::function<YieldType()> function, size_t stackSize) { typedef memory::PoolAllocator<sizeof(ChannelHolder)> holderAlloc; ChannelHolder* ret = (ChannelHolder*)holderAlloc::alloc(); new(ret) ChannelHolder(function, stackSize); return reinterpret_cast<CoroutineBase::Holder<void>*>(ret); } virtual void Release() override { typedef memory::PoolAllocator<sizeof(ChannelHolder)> holderAlloc; this->~ChannelHolder(); holderAlloc::free(this); } virtual CoroutineType Type() override { return CoroutineType::ReceiveOnly; } virtual size_t SizeOfYieldType() override { return sizeof(YieldType); } virtual void RunFunction() override { m_receivedValue = this->m_function(); } ChannelHolder(std::function<YieldType ()> function, size_t stackSize) : Holder<YieldType>(function, stackSize) , m_receivedValue() { // NOP } YieldType m_receivedValue; }; }; //////////////////////////////////////////////////////////////////////////////// /// CoroutineWithChannel<void, void> - Invalid Configuration //////////////////////////////////////////////////////////////////////////////// template<> class sprawl::threading::CoroutineWithChannel<void, void> : public sprawl::threading::CoroutineBase { }; //////////////////////////////////////////////////////////////////////////////// /// Static Functions //////////////////////////////////////////////////////////////////////////////// template<typename SendType, typename YieldType> /*static*/ SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<SendType&> sprawl::threading::CoroutineBase::Receive(YieldType const& value) { CoroutineWithChannel<SendType, YieldType> withChannel = *CoroutineBase::ms_thisThreadCoroutine; #if SPRAWL_COROUTINE_SAFETY_CHECKS if(withChannel.m_holder->Type() != CoroutineType::BiDirectional) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidCoroutineType()); } if(withChannel.m_holder->SizeOfSendType() != sizeof(SendType)) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidSendType()); } if(withChannel.m_holder->SizeOfYieldType() != sizeof(YieldType)) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidYieldType()); } #endif withChannel.releaseRef_(); return withChannel.Receive(value); } template<typename SendType, typename YieldType> /*static*/ SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<SendType&> sprawl::threading::CoroutineBase::Receive(YieldType&& value, typename std::enable_if<!std::is_reference<YieldType>::value>::type*) { CoroutineWithChannel<SendType, YieldType> withChannel = *CoroutineBase::ms_thisThreadCoroutine; #if SPRAWL_COROUTINE_SAFETY_CHECKS if(withChannel.m_holder->Type() != CoroutineType::BiDirectional) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidCoroutineType()); } if(withChannel.m_holder->SizeOfSendType() != sizeof(SendType)) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidSendType()); } if(withChannel.m_holder->SizeOfYieldType() != sizeof(YieldType)) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidYieldType()); } #endif withChannel.releaseRef_(); return withChannel.Receive(std::move(value)); } template<typename SendType> /*static*/ SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<SendType&> sprawl::threading::CoroutineBase::Receive() { CoroutineWithChannel<SendType, void> withChannel = *CoroutineBase::ms_thisThreadCoroutine; #if SPRAWL_COROUTINE_SAFETY_CHECKS if(withChannel.m_holder->Type() != CoroutineType::SendOnly) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidCoroutineType()); } if(withChannel.m_holder->SizeOfSendType() != sizeof(SendType)) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidSendType()); } #endif withChannel.releaseRef_(); return withChannel.Receive(); } template<typename YieldType> /*static*/ SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<void> sprawl::threading::CoroutineBase::Yield(YieldType const& value) { CoroutineWithChannel<void, YieldType> withChannel = *CoroutineBase::ms_thisThreadCoroutine; #if SPRAWL_COROUTINE_SAFETY_CHECKS if(withChannel.m_holder->Type() != CoroutineType::ReceiveOnly) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidCoroutineType()); } if(withChannel.m_holder->SizeOfYieldType() != sizeof(YieldType)) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidYieldType()); } #endif withChannel.releaseRef_(); withChannel.Yield(value); return ErrorState<void>(); } template<typename YieldType> /*static*/ SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<void> sprawl::threading::CoroutineBase::Yield(YieldType&& value, typename std::enable_if<!std::is_reference<YieldType>::value>::type*) { CoroutineWithChannel<void, YieldType> withChannel = *CoroutineBase::ms_thisThreadCoroutine; #if SPRAWL_COROUTINE_SAFETY_CHECKS if(withChannel.m_holder->Type() != CoroutineType::ReceiveOnly) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidCoroutineType()); } if(withChannel.m_holder->SizeOfYieldType() != sizeof(YieldType)) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidYieldType()); } #endif withChannel.releaseRef_(); withChannel.Yield(std::move(value)); return ErrorState<void>(); } /*static*/ inline SPRAWL_WARN_UNUSED_RESULT sprawl::ErrorState<void> sprawl::threading::CoroutineBase::Yield() { CoroutineBase routine = *CoroutineBase::ms_thisThreadCoroutine; #if SPRAWL_COROUTINE_SAFETY_CHECKS if(routine.m_holder->Type() != CoroutineType::Basic) { SPRAWL_THROW_EXCEPTION(sprawl::InvalidCoroutineType()); } #endif routine.releaseRef_(); routine.Pause(); return ErrorState<void>(); } //////////////////////////////////////////////////////////////////////////////// /// CoroutineIterator functions //////////////////////////////////////////////////////////////////////////////// template<typename YieldType, typename CoroutineType> sprawl::threading::CoroutineIterator<YieldType, CoroutineType>::CoroutineIterator(CoroutineBase& routine) : m_routine(routine) , m_value(reinterpret_cast<CoroutineType&>(m_routine)()) , m_state(m_routine.State()) { } template<typename YieldType, typename CoroutineType> sprawl::threading::CoroutineIterator<YieldType, CoroutineType>::CoroutineIterator(CoroutineBase& routine, CoroutineState state) : m_routine(routine) , m_value() , m_state(state) { } template<typename YieldType, typename CoroutineType> sprawl::threading::CoroutineIterator<YieldType, CoroutineType>& sprawl::threading::CoroutineIterator<YieldType, CoroutineType>::operator++() { m_value = reinterpret_cast<CoroutineType&>(m_routine)(); m_state = m_routine.State(); return *this; } template<typename YieldType, typename CoroutineType> bool sprawl::threading::CoroutineIterator<YieldType, CoroutineType>::operator==(CoroutineIterator const& other) { return m_routine.m_holder == other.m_routine.m_holder && m_state == other.m_state; } template<typename YieldType, typename CoroutineType> bool sprawl::threading::CoroutineIterator<YieldType, CoroutineType>::operator!=(CoroutineIterator const& other) { return !operator==(other); } template<typename YieldType, typename CoroutineType> YieldType* sprawl::threading::CoroutineIterator<YieldType, CoroutineType>::operator->() { return &m_value; } template<typename YieldType, typename CoroutineType> YieldType& sprawl::threading::CoroutineIterator<YieldType, CoroutineType>::operator*() { return m_value; } //////////////////////////////////////////////////////////////////////////////// /// Macros //////////////////////////////////////////////////////////////////////////////// #ifndef SPRAWL_NO_COROUTINE_KEYWORDS # ifndef SPRAWL_NO_YIELD_KEWYORD # if defined(SPRAWL_COROUTINE_KEYWORDS_CRT_PREFIX) || defined(SPRAWL_YIELD_CRT_PREFIX) # define crt_yield(...) sprawl::threading::Coroutine::Yield(__VA_ARGS__) # else # define yield(...) sprawl::threading::Coroutine::Yield(__VA_ARGS__) # endif # endif # ifndef SPRAWL_NO_RECEIVE_KEYWORD # if defined(SPRAWL_COROUTINE_KEYWORDS_CRT_PREFIX) || defined(SPRAWL_RECEIVE_CRT_PREFIX) # define crt_receive(received) received = sprawl::threading::Coroutine::Receive<decltype(received)>(); # else # define receive(received) received = sprawl::threading::Coroutine::Receive<decltype(received)>(); # endif # endif # ifndef SPRAWL_NO_YIELD_RECEIVE_KEYWORD # if defined(SPRAWL_COROUTINE_KEYWORDS_CRT_PREFIX) || defined(SPRAWL_YIELD_RECEIVE_CRT_PREFIX) # define crt_yield_receive(yielded, received) received = sprawl::threading::Coroutine::Receive<decltype(received)>(yielded); # else # define yield_receive(yielded, received) received = sprawl::threading::Coroutine::Receive<decltype(received), decltype(yielded)>(yielded); # endif # endif #endif #ifdef _WIN32 # include "coroutine_windows.inl" #else # include "coroutine_linux.inl" #endif
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 23398 | ququlala | "Forking branch Mainline of shadauxcat-libsprawl to ququlala-libsprawl." | ||
//guest/ShadauxCat/Sprawl/Mainline/threading/coroutine.hpp | |||||
#12 | 16768 | ShadauxCat |
Improvements to error handling in builds with exceptions disabled: - In debug builds or with SPRAWL_ERRORSTATE_STRICT enabled, ErrorState will output a message to stderr and terminate if Get() is called when an error flag is set. (In release buils or with SPRAWL_ERRORSTATE_PERMISSIVE defined, Get() will return junk memory in this case.) - In debug builds or with SPRAWL_ERRORSTATE_STRICT enabled, ErrorState will output a message to stderr and terminate if its destructor is called without checking the errorstate if an error is present (equivalent to an exception terminating the application if no catch() block is present for it). - On linux builds and when running "Analyze" through visual studio, a warning will be issued if any function returning ErrorState has its return value ignored. (This only applies to builds with exceptions not enabled; when exceptions are enabled no warning is issued) - Many functions that could return ErrorState were having their return values silently ignored in internal sprawl code so the user would not find out about errors if exceptions are disabled; now anything in sprawl code that calls a function returning ErrorState will either handle the error, or (in most cases) surface it back up to the user. - As a positive side-effect of the warnings for ignoring ErrorState, several constructors that were capable of throwing exceptions are no longer capable of doing so. #review-16769 |
||
#11 | 16380 | ShadauxCat |
Missed a header, also corrected missing newline in exceptions.hpp, also corrected exceptions.hpp being exceptions.h. #review-16381 |
||
#10 | 16181 | ShadauxCat |
Fix build errors on linux, OS X #review-16182 |
||
#9 | 16179 | ShadauxCat |
- Added SPRAWL_DEBUG macro - Added additional runtime coroutine safety checks against not only wrong type of coroutine, but wrong send/receive type - Added ability to disable these additional runtime checks (and defaulted them to turned off in release builds) as they entail up to three virtual function calls and possibly an exception (when exceptions are enabled) - Added requirement that a coroutine that can yield a value must call a function that returns a value of the same type - this ensures that after the last value from the function has been received, the function doesn't have to be called again (with potentially unpredictable results...) to put it into the "Completed" state. Now the last value must be transmitted with a full return, so the last value and the "Completed" state come around at the same time. (This involves all sorts of ca-razy assumptions and reinterpret_casting... assumptions protected via static_assert) #review-16180 |
||
#8 | 16171 | ShadauxCat |
- Created type traits header including macros to make checks for types having any operator or member function, and seeded it with every operator operating on the same type. For function and/or type combinations not already handled, creating a new type traits struct is just a single line of code. - Updated coroutine to use the new type traits header and removed the explicit operator() check - Added alternative to SPRAWL_THROW_EXCEPTION - SPRAWL_THROW_EXCEPTION_OR_ABORT, which as it sounds, will either throw the exception if exceptions are enabled, otherwise will print the message and abort. This is for cases where returning a value reliably isn't possible, such as in constructors or code that returns a template type that may not be default constructible. - Added a layer of safety around coroutines; trying to call the various functions that pause the current coroutine will now throw an exception (if enabled) or abort (if not) if you try to call the wrong pause function (i.e., calling an empty yield() on a coroutine that's supposed to return a value) - Added ability to determine what type a CoroutineBase object actually is underneath - Added function in logging to flush one specific category instead of flushing everything #review-16172 |
||
#7 | 16107 | ShadauxCat |
- Added GetCurrentCoroutine() and GetCallingCoroutine() functions to obtain reference to the currently executing coroutine object and to the coroutine that was previously executing when this one began (i.e., the one that called Resume() / operator() on it) - Coroutines now surface exceptions - if a coroutine throws an exception that is unhandled, the exception will be caught and rethrown after the previous coroutine (or potentially 'main routine') resumes, allowing it to be caught and handled there. #review-16108 |
||
#6 | 16088 | ShadauxCat |
Coroutine improvements: - Yield-only coroutine (aka "generator") can now be iterated using range-based for. (Other coroutine types cannot as it makes no sense - they either do not return a value (nothing to iterate) or they require a value to be sent back (operator++ is insufficient and the call must be made explicitly by the user, not the compiler)) - Moved CoroutineState to a slightly more accessible location - Added optional preprocessor macros to simplify working with coroutines: yield(), receive(), and yield_receive() depending on the type of coroutine being used. - Added missing Yield() and Receive() static functions accepting move parameters. Coroutines had non-static functions for that, but the static ones were missing. #review-16089 |
||
#5 | 16052 | ShadauxCat |
- Changed default block size for concurrent queue to a more reasonable value - Changed some memory orders to memory_order_seq_cst when they don't actually need to be that to get around a bug in visual studio 2013 - debug builds assert when memory_order_acq_rel is used for a compare_exchange_strong (this is a standard library bug and is fixed in VS2015) - Added Event API - events are an alternative to condition variables that do not require a mutex and are guaranteed not to miss any signals, even if the signal comes while the thread is not listening for it. Unlike condition variables, however, they do not support broadcasting (and in fact, in general, are not safe to use with multiple threads listening for the same event simultaneously - though notifying on the same event is fine) - Rewrote ThreadManager around ConcurrentQueue and Event API so it is now lock-free. Also improved some behaviors of the staged thread manager operation so it now supports tasks that can be run on multiple stages via a bitmask. - Fixed an issue where the Coroutine copy constructor was calling the std::function constructor instead and another where initializing with a stack might try to call the wrong constructor and vice-versa - Fixed Coroutine never calling munmap() on its stack in linux and causing a memory leak - Added default arguments to time functions - Attempted to fix some issues with BinaryTree. Fixed some but not all. It's currently not suitable for use, sadly. - Logging Improvements: - - Added thread ID to logging - - Fixed some issues with category handlers - - Added backtraces - - Added the following additional log macros: - - - LOG_IF - - - LOG_EVERY_N - - - LOG_FIRST_N - - - LOG_IF_EVERY_N - - - LOG_IF_FIRST_N - - - LOG_ASSERT - - Added the ability to set extra info callbacks to get data such as script backtraces - - Removed the thread-related handlers and replaced them with RunHandler_Threaded and RunHandler_ThreadManager, which will enable any passed-in handler to be run in a threaded fashion - Removed StaticPoolAllocator and renamed DynamicPoolAllocator to PoolAllocator; adjusted unit tests accordingly - PoolAllocator now allocates its pool with mmap and VirtualAlloc, rather than with malloc - Fixed a bug with Vector copy assignment operator - Improved performance of StringBuilder considerably for cases where there are no modifier strings - Removed Copy-On-Write behavior of JSONToken as it was broken; copies are now performed with explicit DeepCopy() and ShallowCopy() functions - Fixed some parser bugs with JSONToken - Added iteration to JSONToken to iterate its children - Fixed crash when reading a negative number of bytes from a file - Changed StringBuilder to favor speed instead of memory by default - Added some performance unit tests for JSON token #review-16053 |
||
#4 | 14216 | ShadauxCat |
-Moved some global sprawl::Strings into local scope in json serialization test because of initialization order issues in the memory allocator on mac. This is a temporary fix, and a real fix will come by making the pool allocator work in explicitly-sized pieces and putting all the code for those pieces into a cpp file. -Fixed a large number of warnings on mac/linux that were exposed by fixes to csbuild -Fixed compile errors on mac due to malloc and alloca not being defined, fixed by #include <stdlib.h> in appropriate places -Fixed mac os x trying to link against pthread erroneously -Provided os x implementation of time library -Fixed compile errors on os x due to std::unordered_map whining about the difference between an allocator that allocates std::pair<key, value> and one that allocates std::pair<key const, value>, which, of course, is that the allocator will be no different at all. -Fixed an actual issue where one unordered_map was allocating only key_type instead of std::pair<key_type, value_type> -Fixed a memory leak where coroutine objects would never be cleaned up because either Yield() or reactivate_() will never return (and thus never clean up their stack memory and thus never release any dynamic memory held in stack objects) depending on the situation - if the function runs to completion, reactivate_() never returns after calling swapcontext(); meanwhile, if the function does not run to completion, Yield() never returns after calling Pause(). This behavior will need to be well-documented because it will affect client-side code as well. Stack memory within a coroutine should not rely on RAII behavior. -Fixed compile failure when creating a StlWrapper with a const value_type #review-14217 |
||
#3 | 14117 | ShadauxCat | Removed some TODOs that have been TODONE already. | ||
#2 | 14115 | ShadauxCat |
Add missing copy/move/assignment/destructor for BitVector, variadic constructor for Coroutine #review-14116 |
||
#1 | 13650 | ShadauxCat |
- Windows implementations of thread and time libraries - Added coroutines - Added some more unit tests, fixed some unit tests in windows environments - Fixed an issue where multi threading was not properly detected on Linux - Fixed the makefiles to build with threading by default on linux - Changed the pool allocator to use thread-local pools instead of locking mutexes - Fixed output of sprawl::string in the StringBuilder library to take length into account - Added string builder options for StringLiteral - Added thread local implementation #review |