// // Copyright (C) Microsoft Corporation // All rights reserved. // // Code in details namespace is for internal usage within the library code // #ifndef _WRL_ASYNC_H_ #define _WRL_ASYNC_H_ #ifdef _MSC_VER #pragma once #endif // _MSC_VER #include #include #include #include #include #if !defined(MIDL_NS_PREFIX) && !defined(____x_ABI_CWindows_CFoundation_CDiagnostics_CITracingStatusChangedEventArgs_FWD_DEFINED__) namespace ABI { namespace Windows { namespace Foundation { typedef ::Windows::Foundation::IAsyncActionCompletedHandler IAsyncActionCompletedHandler; namespace Diagnostics { typedef ::Windows::Foundation::Diagnostics::CausalitySource CausalitySource; typedef ::Windows::Foundation::Diagnostics::IAsyncCausalityTracerStatics IAsyncCausalityTracerStatics; typedef ::Windows::Foundation::Diagnostics::TracingStatusChangedEventArgs TracingStatusChangedEventArgs; typedef ::Windows::Foundation::Diagnostics::ITracingStatusChangedEventArgs ITracingStatusChangedEventArgs; typedef ::Windows::Foundation::Diagnostics::CausalityTraceLevel CausalityTraceLevel; const ::Windows::Foundation::Diagnostics::CausalityTraceLevel CausalityTraceLevel_Verbose = ::Windows::Foundation::Diagnostics::CausalityTraceLevel_Verbose; const ::Windows::Foundation::Diagnostics::CausalityTraceLevel CausalityTraceLevel_Important = ::Windows::Foundation::Diagnostics::CausalityTraceLevel_Important; const ::Windows::Foundation::Diagnostics::CausalityTraceLevel CausalityTraceLevel_Required = ::Windows::Foundation::Diagnostics::CausalityTraceLevel_Required; const ::Windows::Foundation::Diagnostics::CausalityRelation CausalityRelation_Join = ::Windows::Foundation::Diagnostics::CausalityRelation_Join; const ::Windows::Foundation::Diagnostics::CausalityRelation CausalityRelation_Choice = ::Windows::Foundation::Diagnostics::CausalityRelation_Choice; const ::Windows::Foundation::Diagnostics::CausalityRelation CausalityRelation_Error = ::Windows::Foundation::Diagnostics::CausalityRelation_Error; const ::Windows::Foundation::Diagnostics::CausalityRelation CausalityRelation_Cancel = ::Windows::Foundation::Diagnostics::CausalityRelation_Cancel; const ::Windows::Foundation::Diagnostics::CausalityRelation CausalityRelation_AssignDelegate = ::Windows::Foundation::Diagnostics::CausalityRelation_AssignDelegate ; const ::Windows::Foundation::Diagnostics::CausalitySynchronousWork CausalitySynchronousWork_CompletionNotification = ::Windows::Foundation::Diagnostics::CausalitySynchronousWork_CompletionNotification; const ::Windows::Foundation::Diagnostics::CausalitySynchronousWork CausalitySynchronousWork_ProgressNotification = ::Windows::Foundation::Diagnostics::CausalitySynchronousWork_ProgressNotification; const ::Windows::Foundation::Diagnostics::CausalitySynchronousWork CausalitySynchronousWork_Execution = ::Windows::Foundation::Diagnostics::CausalitySynchronousWork_Execution; } } } } #endif #include #include #include #include #include #include // Set packing #include #pragma warning(push) // nonstandard extension used: override specifier 'override' #pragma warning(disable: 4481) // GUID identifying the the Windows Platform for logging purposes // {C54C95D9-5B6E-41E9-A28D-2DD68F94B500} extern _declspec(selectany) const GUID GUID_CAUSALITY_WINDOWS_PLATFORM_ID = { 0xc54c95d9, 0x5b6e, 0x41e9, { 0xa2, 0x8d, 0x2d, 0xd6, 0x8f, 0x94, 0xb5, 0x0 } }; namespace Microsoft { namespace WRL { // Designates the error propagation policy used by FireProgress and FireComplete. If PropagateDelegateError is the mode // then failures returned from the async completion and progress delegates are propagated. If IgnoreDelegateError // is the mode, then failures returned from the async completion and progress delegates are converted to successes and // the errors are swallowed. enum ErrorPropagationPolicy { PropagateDelegateError = 1, IgnoreDelegateError = 2 #if defined(BUILD_WINDOWS) // negative numbers are a good choice for Windows defined ErrorPropagationPolicy. , PropagateErrorWithWin8Quirk = -1 #endif }; namespace Details { // contains states indicating existance or lack of options struct AsyncOptionsBase { static const bool hasCausalityOptions = false; static const bool hasErrorPropagationPolicy = false; static const bool hasCausalityOperationName = false; static const bool isCausalityEnabled = true; }; template < PCWSTR OpName > struct IsOperationName { static const bool Value = true; }; template < > struct IsOperationName< nullptr > { static const bool Value = false; }; } // Options for error propagation and the defaults are set here. #if defined(BUILD_WINDOWS) && (NTDDI_VERSION >= NTDDI_WINBLUE) template < ErrorPropagationPolicy errorPropagationPolicy = PropagateErrorWithWin8Quirk> #else template < ErrorPropagationPolicy errorPropagationPolicy = Microsoft::WRL::ErrorPropagationPolicy::IgnoreDelegateError> #endif struct ErrorPropagationOptions : public Microsoft::WRL::Details::AsyncOptionsBase { static const ErrorPropagationPolicy PropagationPolicy = errorPropagationPolicy; static const bool hasErrorPropagationPolicy = true; }; #ifndef _WRL_DISABLE_CAUSALITY_ // Options for causality tracing and the needed defaults are set here. The following class may be used as // a reference to add more options to AsyncBase #ifdef BUILD_WINDOWS #define WRL_DEFAULT_CAUSALITY_GUID GUID_CAUSALITY_WINDOWS_PLATFORM_ID #define WRL_DEFAULT_CAUSALITY_SOURCE ::ABI::Windows::Foundation::Diagnostics::CausalitySource::CausalitySource_System #else #define WRL_DEFAULT_CAUSALITY_GUID GUID_NULL #define WRL_DEFAULT_CAUSALITY_SOURCE ::ABI::Windows::Foundation::Diagnostics::CausalitySource::CausalitySource_Application #endif //BUILD_WINDOWS template < PCWSTR OpName = nullptr, const GUID &PlatformId = WRL_DEFAULT_CAUSALITY_GUID, ::ABI::Windows::Foundation::Diagnostics::CausalitySource CausalitySource = WRL_DEFAULT_CAUSALITY_SOURCE > struct AsyncCausalityOptions : public Microsoft::WRL::Details::AsyncOptionsBase { static PCWSTR GetAsyncOperationName() { return OpName; } static const GUID GetPlatformId() { return PlatformId; } static ::ABI::Windows::Foundation::Diagnostics::CausalitySource GetCausalitySource() { return CausalitySource; } static const bool hasCausalityOptions = true; static const bool hasCausalityOperationName = Microsoft::WRL::Details::IsOperationName::Value; }; // This option type for causality tracing disables just the tracing part. extern __declspec(selectany) const WCHAR DisableCausalityAsyncOperationName[] = L"Disabled"; struct DisableCausality : public AsyncCausalityOptions< DisableCausalityAsyncOperationName > { static const bool isCausalityEnabled = false; }; #endif // _WRL_DISABLE_CAUSALITY_ namespace Details { // maps internal definitions for AsyncStatus and defines states that are not client visible enum AsyncStatusInternal { // non-client visible internal states _Undefined = -2, _Created = -1, // client visible states (must match AsyncStatus exactly) _Started = static_cast(::ABI::Windows::Foundation::AsyncStatus::Started), _Completed = static_cast(::ABI::Windows::Foundation::AsyncStatus::Completed), _Canceled = static_cast(::ABI::Windows::Foundation::AsyncStatus::Canceled), _Error = static_cast(::ABI::Windows::Foundation::AsyncStatus::Error), // non-client visible internal states _Closed }; template < typename T > struct DerefHelper; template < typename T > struct DerefHelper { typedef T DerefType; }; #pragma region AsyncOptionsHelper #ifndef _WRL_DISABLE_CAUSALITY_ // Provides the name for a async operation/action for logging purposes template < typename TComplete, bool hasName, typename TOptions > struct CausalityNameHelper { // Provides a default string for the Async Operation/Action name if a name is not provided // for logging purposes static PCWSTR GetName() { return TOptions::GetAsyncOperationName(); } }; // Specialization to handle the logging for those classes that implement z_get_rc_name_impl template < typename TComplete, typename TOptions > struct CausalityNameHelper< TComplete, false, TOptions > { static PCWSTR GetName() { return TComplete::z_get_rc_name_impl(); } }; // Specialization to handle the logging for IAsyncAction template struct CausalityNameHelper< ::ABI::Windows::Foundation::IAsyncActionCompletedHandler, false, TOptions > { // IAsyncActionCompletedHandler is not templatized so it does not implement z_get_rc_name_impl // hence this specialization static PCWSTR GetName() { return L"Windows.Foundation.IAsyncActionCompletedHandler"; } }; #endif // _WRL_DISABLE_CAUSALITY_ // helper class to switch between default or given options for error // propagation template < bool hasValue, typename TOptions > struct ErrorPropagationOptionsHelper; // provides the given options for error propagation template < typename TOptions > struct ErrorPropagationOptionsHelper< true , TOptions > { static const Microsoft::WRL::ErrorPropagationPolicy PropagationPolicy = TOptions::PropagationPolicy; }; // provides default options for error propagation template < typename TOptions > struct ErrorPropagationOptionsHelper< false , TOptions > { static const Microsoft::WRL::ErrorPropagationPolicy PropagationPolicy = Microsoft::WRL::ErrorPropagationOptions<>::PropagationPolicy; }; #ifndef _WRL_DISABLE_CAUSALITY_ // helper class to switch between default or given options for // Async Causality Options template < bool hasCausalityOptions, typename TComplete, typename TOptions > struct AsyncCausalityOptionsHelper; // provides the given options for causality tracing template < typename TComplete, typename TOptions > struct AsyncCausalityOptionsHelper < true, TComplete, TOptions > { #if defined(BUILD_WINDOWS) || defined(WRL_REQUIRE_ASYNC_NAMES) // Take reasonable steps to make the name unique. Suggest choosing a name that captures // the return type, class name, and method name. Example: // "Windows.Foundation.IAsyncAction MyNamespace.MyClass.DoSomethingAsync" static_assert(!(__is_base_of(::ABI::Windows::Foundation::IAsyncActionCompletedHandler, TComplete) && !TOptions::hasCausalityOperationName), "Please add name to Asynchronous Operations for Better Diagnostics"); #endif static PCWSTR GetAsyncOperationName() { return CausalityNameHelper< TComplete, TOptions::hasCausalityOperationName, TOptions >::GetName(); } static const GUID GetPlatformId() { return TOptions::GetPlatformId(); } static ::ABI::Windows::Foundation::Diagnostics::CausalitySource GetCausalitySource() { return TOptions::GetCausalitySource(); } static const bool CausalityEnabled = TOptions::isCausalityEnabled; }; // provides the default options for causality tracing template < typename TComplete, typename TOptions > struct AsyncCausalityOptionsHelper < false, TComplete, TOptions > { #if defined(BUILD_WINDOWS) || defined(WRL_REQUIRE_ASYNC_NAMES) // Take reasonable steps to make the name unique. Suggest choosing a name that captures // the return type, class name, and method name. Example: // "Windows.Foundation.IAsyncAction MyNamespace.MyClass.DoSomethingAsync" static_assert(!(__is_base_of(::ABI::Windows::Foundation::IAsyncActionCompletedHandler, TComplete) && !TOptions::hasCausalityOperationName), "Please add name to Asynchronous Operations for Better Diagnostics"); #endif static PCWSTR GetAsyncOperationName() { return CausalityNameHelper< TComplete, TOptions::hasCausalityOperationName, TOptions >::GetName(); } static const GUID GetPlatformId() { return Microsoft::WRL::AsyncCausalityOptions<>::GetPlatformId(); } static ::ABI::Windows::Foundation::Diagnostics::CausalitySource GetCausalitySource() { return Microsoft::WRL::AsyncCausalityOptions<>::GetCausalitySource(); } static const bool CausalityEnabled = TOptions::isCausalityEnabled; }; #endif // _WRL_DISABLE_CAUSALITY_ // helper calls to accumulate all options // Future options have to be added here template < typename TComplete, typename TOptions > struct AsyncOptionsHelper : #ifndef _WRL_DISABLE_CAUSALITY_ public AsyncCausalityOptionsHelper < TOptions::hasCausalityOptions, TComplete, TOptions >, #endif // _WRL_DISABLE_CAUSALITY_ public ErrorPropagationOptionsHelper < TOptions::hasErrorPropagationPolicy, TOptions > { }; #pragma endregion // End of AsyncOptionsHelper } // Details // designates whether the "GetResults" method returns a single result (after complete fires) or multiple results // (which are progressively consumable between Start state and before Close is called) enum AsyncResultType { SingleResult = 0x0001, MultipleResults = 0x0002 }; // indicates how an attempt to transition to a terminal state of Completed or Error should behave with respect to // the (client-requested) Canceled state. enum CancelTransitionPolicy { // If the async operation is presently in a (client-requested) Canceled state, this indicates that // it will stay in the Canceled state as opposed to transitioning to a terminal Completed or Error // state. RemainCanceled = 0, // If the async operation is presently in a (client-requested) Canceled state, this indicates that // state should transition from that Canceled state to the terminal state of Completed or Error as // determined by the call utilizing this flag. TransitionFromCanceled }; #pragma region AsyncOptions template < ErrorPropagationPolicy errorPropagationPolicy > struct ErrorPropagationPolicyTraits; // This error propagation policy passes through all errors template <> struct ErrorPropagationPolicyTraits< PropagateDelegateError > { static HRESULT FireCompletionErrorPropagationPolicyFilter(HRESULT hrIn, IUnknown *, void * = nullptr) { // Ignore errors if the error is caused by a disconnected object if (hrIn == RPC_E_DISCONNECTED || hrIn == HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE) || hrIn == JSCRIPT_E_CANTEXECUTE) { ::RoTransformError(hrIn, S_OK, nullptr); hrIn = S_OK; } return hrIn; } static HRESULT FireProgressErrorPropagationPolicyFilter(HRESULT hrIn, IUnknown *, void * = nullptr) { // Ignore errors if the error is caused by a disconnected object if (hrIn == RPC_E_DISCONNECTED || hrIn == HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE) || hrIn == JSCRIPT_E_CANTEXECUTE) { ::RoTransformError(hrIn, S_OK, nullptr); hrIn = S_OK; } return hrIn; } }; // This error propagation policy ignores all errors and converts them to S_OK template <> struct ErrorPropagationPolicyTraits< IgnoreDelegateError > { static HRESULT FireCompletionErrorPropagationPolicyFilter(HRESULT hrIn, IUnknown *, void * = nullptr) { if (FAILED(hrIn)) { ::RoTransformError(hrIn, S_OK, nullptr); hrIn = S_OK; } return hrIn; } static HRESULT FireProgressErrorPropagationPolicyFilter(HRESULT hrIn, IUnknown *, void * = nullptr) { if (FAILED(hrIn)) { ::RoTransformError(hrIn, S_OK, nullptr); hrIn = S_OK; } return hrIn; } }; // All options for the AsyncBase class are accumulated here. This class may be expanded to include // new options as needed. template < #if defined(BUILD_WINDOWS) && (NTDDI_VERSION >= NTDDI_WINBLUE) ErrorPropagationPolicy errorPropagationPolicy = PropagateErrorWithWin8Quirk, #else ErrorPropagationPolicy errorPropagationPolicy = ErrorPropagationPolicy::IgnoreDelegateError, #endif PCWSTR OpName = nullptr, #ifdef BUILD_WINDOWS const GUID &PlatformId = GUID_CAUSALITY_WINDOWS_PLATFORM_ID, ::ABI::Windows::Foundation::Diagnostics::CausalitySource CausalitySource = ::ABI::Windows::Foundation::Diagnostics::CausalitySource::CausalitySource_System #else const GUID &PlatformId = GUID_NULL, ::ABI::Windows::Foundation::Diagnostics::CausalitySource CausalitySource = ::ABI::Windows::Foundation::Diagnostics::CausalitySource::CausalitySource_Application #endif //BUILD_WINDOWS > struct AsyncOptions : #ifndef _WRL_DISABLE_CAUSALITY_ public AsyncCausalityOptions, #endif // _WRL_DISABLE_CAUSALITY_ public ErrorPropagationOptions { static const bool hasCausalityOptions = true; static const bool hasErrorPropagationPolicy = true; static const bool hasCausalityOperationName = Microsoft::WRL::Details::IsOperationName::Value; static const bool isCausalityEnabled = true; }; #pragma endregion // End of AsyncOptions region #ifndef _WRL_DISABLE_CAUSALITY_ _declspec(selectany) INIT_ONCE gCausalityInitOnce = INIT_ONCE_STATIC_INIT; _declspec(selectany) ::ABI::Windows::Foundation::Diagnostics::IAsyncCausalityTracerStatics* gCausality; #endif // _WRL_DISABLE_CAUSALITY_ // AsyncBase - base class that implements the WinRT Async state machine // this base class is designed to be used with WRL to implement an async worker object template < typename TComplete, typename TProgress = Details::Nil, AsyncResultType resultType = SingleResult, typename TAsyncBaseOptions = AsyncOptions<> > class AsyncBase : public AsyncBase< TComplete, Details::Nil, resultType, TAsyncBaseOptions > { typedef typename Details::ArgTraitsHelper< TProgress >::Traits ProgressTraits; typedef Microsoft::WRL::Details::AsyncOptionsHelper< TComplete, TAsyncBaseOptions > AllOptions; friend class AsyncBase< TComplete, Details::Nil, resultType, TAsyncBaseOptions >; public: // since this is designed to be used inside of an RuntimeClass<> template, we can // only have a default constructor AsyncBase() : progressDelegate_(nullptr), progressDelegateBucketAssist_(nullptr) { } // Delegate Helpers STDMETHOD(PutOnProgress)(TProgress* progressHandler) { HRESULT hr = this->CheckValidStateForDelegateCall(); if (SUCCEEDED(hr)) { progressDelegate_ = progressHandler; if (progressDelegate_ != nullptr) { progressDelegateBucketAssist_ = Microsoft::WRL::Details::GetDelegateBucketAssist(progressDelegate_.Get()); } this->TraceDelegateAssigned(); } return hr; } STDMETHOD(GetOnProgress)(TProgress** progressHandler) { *progressHandler = nullptr; HRESULT hr = this->CheckValidStateForDelegateCall(); if (SUCCEEDED(hr)) { progressDelegate_.CopyTo(progressHandler); } return hr; } HRESULT FireProgress(const typename ProgressTraits::Arg2Type arg) { HRESULT hr = S_OK; ComPtr< ::ABI::Windows::Foundation::IAsyncInfo > asyncInfo = this; ComPtr::DerefType> operationInterface; if (progressDelegate_) { hr = asyncInfo.As(&operationInterface); if (SUCCEEDED(hr)) { this->TraceProgressNotificationStart(); hr = progressDelegate_->Invoke(operationInterface.Get(), arg); this->TraceProgressNotificationComplete(); } } // filter the errors per the Error Propagation Policy hr = ErrorPropagationPolicyTraits< AllOptions::PropagationPolicy >::FireProgressErrorPropagationPolicyFilter(hr, progressDelegate_.Get(), progressDelegateBucketAssist_); return hr; } HRESULT FireCompletion(void) override { // "this" may be deleted during the completion call. Remove progress prior to firing completion. progressDelegate_.Reset(); return AsyncBase< TComplete, Details::Nil, resultType, TAsyncBaseOptions >::FireCompletion(); } private: ::Microsoft::WRL::ComPtr progressDelegate_; void *progressDelegateBucketAssist_; }; template < typename TComplete, AsyncResultType resultType, typename TAsyncBaseOptions > class AsyncBase< TComplete, Details::Nil, resultType, TAsyncBaseOptions > : public ::Microsoft::WRL::Implements< ::ABI::Windows::Foundation::IAsyncInfo > { typedef typename Details::ArgTraitsHelper< TComplete >::Traits CompleteTraits; typedef Microsoft::WRL::Details::AsyncOptionsHelper AllOptions; public: // since this is designed to be used inside of a RuntimeClass<> template, we can // only have a default constructor AsyncBase() : cCallbackMade_(0), cCompleteDelegateAssigned_(0), completeDelegate_(nullptr), completeDelegateBucketAssist_(nullptr), currentStatus_(Details::AsyncStatusInternal::_Created), errorCode_(S_OK), id_(1), asyncOperationBucketAssist_(nullptr) { } // The TraceCompletion in logged if the FireCompletion occurs and the completion call back is assigned // if the callback was not made then the async operation completion is logged here virtual ~AsyncBase() { if (!cCallbackMade_) { TraceOperationComplete(); } } // IAsyncInfo::put_Id STDMETHOD(put_Id)(const unsigned int id) { if (id == 0) { #if (NTDDI_VERSION >= NTDDI_WINBLUE) WCHAR const pszParamName[] = L"id"; ::RoOriginateErrorW(E_INVALIDARG, ARRAYSIZE(pszParamName) - 1, pszParamName); #endif // (NTDDI_VERSION >= NTDDI_WINBLUE) return E_INVALIDARG; } id_ = id; Details::AsyncStatusInternal current = Details::_Undefined; CurrentStatus(¤t); if (current != Details::_Created) { #if (NTDDI_VERSION >= NTDDI_WINBLUE) ::RoOriginateError(E_ILLEGAL_METHOD_CALL, nullptr); #endif // (NTDDI_VERSION >= NTDDI_WINBLUE) return E_ILLEGAL_METHOD_CALL; } return S_OK; } // IAsyncInfo::get_Id STDMETHOD(get_Id)(unsigned int *id) override { *id = id_; return CheckValidStateForAsyncInfoCall(); } // IAsyncInfo::get_Status STDMETHOD(get_Status)(::ABI::Windows::Foundation::AsyncStatus *status) override { Details::AsyncStatusInternal current = Details::_Undefined; CurrentStatus(¤t); *status = static_cast< ::ABI::Windows::Foundation::AsyncStatus >(current); return CheckValidStateForAsyncInfoCall(); } // IAsyncInfo::get_ErrorCode STDMETHOD(get_ErrorCode)(HRESULT* errorCode) override { HRESULT hr = CheckValidStateForAsyncInfoCall(); if (SUCCEEDED(hr)) { ErrorCode(errorCode); } else { // Do not propagate the error and error info associated with the async error if this call generated // a specific error itself. *errorCode = hr; } return hr; } protected: // Start - this is not externally visible since async operations "hot start" before returning to the caller STDMETHOD(Start)(void) { HRESULT hr = S_OK; if (TransitionToState(Details::_Started)) { hr = OnStart(); #ifndef _WRL_DISABLE_CAUSALITY_ if (SUCCEEDED(hr) && ::InitOnceExecuteOnce(&gCausalityInitOnce, InitCausality, NULL, NULL)) { TraceOperationStart(); } #endif // _WRL_DISABLE_CAUSALITY_ } else { hr = E_ILLEGAL_STATE_CHANGE; #if (NTDDI_VERSION >= NTDDI_WINBLUE) ::RoOriginateError(E_ILLEGAL_STATE_CHANGE, nullptr); #endif // (NTDDI_VERSION >= NTDDI_WINBLUE) } return hr; } public: // IAsyncInfo::Cancel STDMETHOD(Cancel)(void) override { if (TransitionToState(Details::_Canceled)) { OnCancel(); TraceCancellation(); } return S_OK; } // IAsyncInfo::Close STDMETHOD(Close)(void) override { HRESULT hr = S_OK; if (TransitionToState(Details::_Closed)) { OnClose(); } else // illegal state change { Details::AsyncStatusInternal current = Details::_Undefined; CurrentStatus(¤t); if (current == Details::_Closed) { hr = S_OK; // Closed => Closed transition is just ignored } else { hr = E_ILLEGAL_STATE_CHANGE; #if (NTDDI_VERSION >= NTDDI_WINBLUE) ::RoOriginateError(E_ILLEGAL_STATE_CHANGE, nullptr); #endif // (NTDDI_VERSION >= NTDDI_WINBLUE) } } return hr; } // Delegate helpers STDMETHOD(PutOnComplete)(TComplete* completeHandler) { HRESULT hr = CheckValidStateForDelegateCall(); if (SUCCEEDED(hr)) { // this delegate property is "write once" if (InterlockedIncrement(&cCompleteDelegateAssigned_) == 1) { if (completeHandler != nullptr) { completeDelegateBucketAssist_ = Microsoft::WRL::Details::GetDelegateBucketAssist(completeHandler); } completeDelegate_ = completeHandler; // Guarantee that the write of completeDelegate_ is ordered with respect to the read of state below // as perceived from FireCompletion on another thread. MemoryBarrier(); this->TraceDelegateAssigned(); // in the "hot start" case, put_Completed could have been called after the async operation has hit // a terminal state. If so, fire the completion immediately. if (IsTerminalState()) { FireCompletion(); } } else { hr = E_ILLEGAL_DELEGATE_ASSIGNMENT; #if (NTDDI_VERSION >= NTDDI_WINBLUE) ::RoOriginateError(E_ILLEGAL_DELEGATE_ASSIGNMENT, nullptr); #endif // (NTDDI_VERSION >= NTDDI_WINBLUE) } } return hr; } STDMETHOD(GetOnComplete)(TComplete** completeHandler) { *completeHandler = nullptr; HRESULT hr = CheckValidStateForDelegateCall(); if (SUCCEEDED(hr)) { completeDelegate_.CopyTo(completeHandler); } return hr; } virtual HRESULT FireCompletion() { HRESULT hr = S_OK; // must do this *before* the InterlockedIncrement! TryTransitionToCompleted(); __WRL_ASSERT__(IsTerminalState() && "Must only call FireCompletion when operation is in terminal state"); // we guarantee that completion can only ever be fired once if (completeDelegate_ != nullptr && InterlockedIncrement(&cCallbackMade_) == 1) { ComPtr< ::ABI::Windows::Foundation::IAsyncInfo> asyncInfo = this; ComPtr::DerefType> operationInterface; TraceOperationComplete(); if (SUCCEEDED(asyncInfo.As(&operationInterface))) { Details::AsyncStatusInternal current = Details::_Undefined; CurrentStatus(¤t); TraceCompletionNotificationStart(); hr = completeDelegate_->Invoke(operationInterface.Get(), static_cast<::ABI::Windows::Foundation::AsyncStatus>(current)); // Filter the errors as per the Error Propagation Policy hr = ErrorPropagationPolicyTraits< AllOptions::PropagationPolicy >::FireCompletionErrorPropagationPolicyFilter(hr, completeDelegate_.Get(), completeDelegateBucketAssist_); completeDelegate_ = nullptr; TraceCompletionNotificationComplete(); } } return hr; } protected: inline void CurrentStatus(Details::AsyncStatusInternal *status) { ::_InterlockedCompareExchange(reinterpret_cast(status), currentStatus_, static_cast(*status)); __WRL_ASSERT__(*status != Details::_Undefined); } // This method returns the error code stored as a result of a transition into the error state. // In addition, if there is any restricted error information associated with the error that was captured at the time // of the error transition, it will be associated with the calling thread via a SetRestrictedErrorInfo call. inline void ErrorCode(HRESULT *error) { Details::AsyncStatusInternal current = Details::_Undefined; CurrentStatus(¤t); // Do not allow visibility of the error until such point as we have had a successful state transition into the error state. // The error + information is not a single atomic quantity. It is not considered "published" until we are actively in the error state. if (current != Details::_Error) { *error = S_OK; } else { ::_InterlockedCompareExchange(reinterpret_cast(error), errorCode_, static_cast(*error)); if (errorInfo_ != nullptr) { SetRestrictedErrorInfo(errorInfo_.Get()); } } } bool TryTransitionToCompleted(CancelTransitionPolicy cancelBehavior = CancelTransitionPolicy::RemainCanceled) { bool bTransition = TransitionToState(Details::AsyncStatusInternal::_Completed); if (!bTransition && cancelBehavior == CancelTransitionPolicy::TransitionFromCanceled) { bTransition = TransitionCanceledToCompleted(); } return bTransition; } bool TryTransitionToError(const HRESULT error, CancelTransitionPolicy cancelBehavior = CancelTransitionPolicy::RemainCanceled, _In_opt_ void * bucketAssist = nullptr) { // In addition to the result being transitioned to, there might be restricted error information associated with the error. It // is assumed that such is on the calling thread. If we successfully transition to the error state with "error" as the code, // we must also capture the error info and funnel it over to callers of GetResults / ErrorCode. Our call to // GetRestrictedErrorInfo below will capture the error info after which, it is owned by this async operation. // // Since there are multiple pieces of information and the capturing of these are not atomic, no one from the outside is allowed // to view these until the state transition to error is complete. This happens in two parts: // // - A successful CAS from S_OK to error (meaning that this error is the one being captures) // - A successful state change into the error state (via the Transition* call below) if (bucketAssist != nullptr) { asyncOperationBucketAssist_ = bucketAssist; } bool bTransition = false; if (::_InterlockedCompareExchange(reinterpret_cast(&errorCode_), error, S_OK) == S_OK) { (void)GetRestrictedErrorInfo(&errorInfo_); // This thread is the "owner" of the rights to transition to the error state. bTransition = TransitionToState(Details::AsyncStatusInternal::_Error); if (!bTransition && cancelBehavior == CancelTransitionPolicy::TransitionFromCanceled) { bTransition = TransitionCanceledToError(); } } if (bTransition) { TraceError(); } // if we return true, then we did a valid state transition // queue firing of completed event (cannot be done from this call frame) // otherwise we are already in a terminal state: error, canceled, completed, or closed // and we ignore the transition request to the Error state return bTransition; } // This method checks to see if the delegate properties can be // modified in the current state and generates the appropriate // error hr in the case of violation. inline HRESULT CheckValidStateForDelegateCall() { Details::AsyncStatusInternal current = Details::_Undefined; CurrentStatus(¤t); if (current == Details::_Closed) { #if (NTDDI_VERSION >= NTDDI_WINBLUE) ::RoOriginateError(E_ILLEGAL_METHOD_CALL, nullptr); #endif // (NTDDI_VERSION >= NTDDI_WINBLUE) return E_ILLEGAL_METHOD_CALL; } return S_OK; } // This method checks to see if results can be collected in the // current state and generates the appropriate error hr in // the case of a violation. inline HRESULT CheckValidStateForResultsCall() { Details::AsyncStatusInternal current = Details::_Undefined; CurrentStatus(¤t); if (current == Details::_Error) { HRESULT hr; // Make sure to propagate any restricted error info associated with the asynchronous failure. ErrorCode(&hr); return hr; } #pragma warning(push) #pragma warning(disable: 4127) // Conditional expression is constant // single result only legal in Completed state if (resultType == SingleResult) #pragma warning(pop) { if (current != Details::_Completed) { #if (NTDDI_VERSION >= NTDDI_WINBLUE) ::RoOriginateError(E_ILLEGAL_METHOD_CALL, nullptr); #endif // (NTDDI_VERSION >= NTDDI_WINBLUE) return E_ILLEGAL_METHOD_CALL; } } // multiple results can be called after async operation is running (started) and before/after Completed else if (current != Details::_Started && current != Details::_Canceled && current != Details::_Completed) { #if (NTDDI_VERSION >= NTDDI_WINBLUE) ::RoOriginateError(E_ILLEGAL_METHOD_CALL, nullptr); #endif // (NTDDI_VERSION >= NTDDI_WINBLUE) return E_ILLEGAL_METHOD_CALL; } return S_OK; } // This method can be called by derived classes periodically to determine // whether the asynchronous operation should continue processing or should // be halted. inline bool ContinueAsyncOperation() { Details::AsyncStatusInternal current = Details::_Undefined; CurrentStatus(¤t); return (current == Details::_Started); } // These methods are used to allow the async worker implementation do work on // state transitions. No real "work" should be done in these methods. In other words // they should not block for a long time on UI timescales. virtual HRESULT OnStart(void) = 0; virtual void OnClose(void) = 0; virtual void OnCancel(void) = 0; private: #ifndef _WRL_DISABLE_CAUSALITY_ // This method is used to initialize the Causality tracking static BOOL WINAPI InitCausality( _Inout_opt_ PINIT_ONCE InitOnce, _Inout_opt_ PVOID Parameter, _Out_opt_ PVOID* Context ) { UNREFERENCED_PARAMETER(InitOnce); UNREFERENCED_PARAMETER(Parameter); UNREFERENCED_PARAMETER(Context); __WRL_ASSERT__(!Context); _Analysis_assume_(!Context); #ifdef _NO_CAUSALITY_DOWNLEVEL_ // do not attempt to trace causality on OS versions less than 6.2 (Windows 8) OSVERSIONINFOEX osvi; DWORDLONG dwlConditionMask = 0; ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osvi.dwMajorVersion = 6; osvi.dwMinorVersion = 2; VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL); VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL); // if running on Windows 8 or greater if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask)) { #endif Microsoft::WRL::Wrappers::HStringReference hstrCausalityTraceName(RuntimeClass_Windows_Foundation_Diagnostics_AsyncCausalityTracer); if (FAILED(::Windows::Foundation::GetActivationFactory(hstrCausalityTraceName.Get(), &gCausality))) { return FALSE; } return TRUE; #ifdef _NO_CAUSALITY_DOWNLEVEL_ } else { gCausality = nullptr; return FALSE; } #endif } #endif // _WRL_DISABLE_CAUSALITY_ // This method is used to check if calls to the AsyncInfo properties // (id, status, error code) are legal in the current state. It also // generates the appropriate error hr to return in the case of an // illegal call. inline HRESULT CheckValidStateForAsyncInfoCall() { Details::AsyncStatusInternal current = Details::_Undefined; CurrentStatus(¤t); if (current == Details::_Closed) { #if (NTDDI_VERSION >= NTDDI_WINBLUE) ::RoOriginateError(E_ILLEGAL_METHOD_CALL, nullptr); #endif // (NTDDI_VERSION >= NTDDI_WINBLUE) return E_ILLEGAL_METHOD_CALL; } else if (current == Details::_Created) // error in async ::ABI object - returned to caller not started { // No RoOriginateError needed since this can hit multiple times in expected scenarios return E_ASYNC_OPERATION_NOT_STARTED; } return S_OK; } inline bool TransitionToState(const Details::AsyncStatusInternal newState) { Details::AsyncStatusInternal current = Details::_Undefined; CurrentStatus(¤t); // This enforces the valid state transitions of the asynchronous worker object // state machine. switch(newState) { case Details::_Started: if (current != Details::_Created) { return false; } break; case Details::_Completed: if (current != Details::_Started) { return false; } break; case Details::_Canceled: if (current != Details::_Started) { return false; } break; case Details::_Error: if (current != Details::_Started) { return false; } break; case Details::_Closed: if (!IsTerminalState(current)) { return false; } break; default: return false; break; } // attempt the transition to the new state // Note: if currentStatus_ == current, then there was no intervening write // by the async work object and the swap succeeded. Details::AsyncStatusInternal retState = static_cast( ::_InterlockedCompareExchange(reinterpret_cast(¤tStatus_), newState, static_cast(current))); // ICE returns the former state, if the returned state and the // state we captured at the beginning of this method are the same, // the swap succeeded. return (retState == current); } protected: // It is legal for an async operation object to transition from (client-requested) Canceled // state to Completed if, for example, the operation completed near the time of the cancellation request. // An operation which is no longer responsive to client requests to cancel and intends to complete // successfully despite any new incoming requests to cancel should call TryTransitionToCompleted and // pass TransitionFromCanceled instead of using this method. inline bool TransitionCanceledToCompleted() { // this is somewhat overly pessimistic since the client cannot possibly transition // the operation out of the canceled state (only the async operation itself can call // this method) Details::AsyncStatusInternal retState = static_cast( ::_InterlockedCompareExchange(reinterpret_cast(¤tStatus_), Details::AsyncStatusInternal::_Completed, Details::AsyncStatusInternal::_Canceled)); return (retState == Details::AsyncStatusInternal::_Canceled); } // It is legal for an async operation object to transition from (client-requested) Canceled // state to the error state if, for example, the operation encountered an error near the time // of the cancellation request. An operation which is no longer responsive to client requests to cancel // and intends to complete with an error despite any new incoming requests to cancel should call // TryTransitionToError and pass TransitionFromCanceled instead of using this method. inline bool TransitionCanceledToError() { Details::AsyncStatusInternal retState = static_cast( ::_InterlockedCompareExchange(reinterpret_cast(¤tStatus_), Details::AsyncStatusInternal::_Error, Details::AsyncStatusInternal::_Canceled)); return (retState == Details::AsyncStatusInternal::_Canceled); } inline bool IsTerminalState() { Details::AsyncStatusInternal current = Details::_Undefined; CurrentStatus(¤t); return IsTerminalState(current); } inline bool IsTerminalState(Details::AsyncStatusInternal status) { return (status == Details::_Error || status == Details::_Canceled || status == Details::_Completed || status == Details::_Closed); } long cCallbackMade_; long cCompleteDelegateAssigned_; #pragma region TracingMethods void TraceOperationStart() { #ifndef _WRL_DISABLE_CAUSALITY_ if (gCausality && AllOptions::CausalityEnabled) { // Ignoring HRESULT intentionally. Tracking failure should not change the // normal flow of AsyncOperations gCausality->TraceOperationCreation( ::ABI::Windows::Foundation::Diagnostics::CausalityTraceLevel_Required, AllOptions::GetCausalitySource(), AllOptions::GetPlatformId(), reinterpret_cast< UINT64 >(this), Microsoft::WRL::Wrappers::HStringReference(AllOptions::GetAsyncOperationName()).Get(), id_); } #endif // _WRL_DISABLE_CAUSALITY_ } void TraceOperationComplete() { #ifndef _WRL_DISABLE_CAUSALITY_ if (gCausality && AllOptions::CausalityEnabled) { // Ignoring HRESULT intentionally. Tracking failure should not change the // normal flow of AsyncOperations Details::AsyncStatusInternal status; CurrentStatus(&status); gCausality->TraceOperationCompletion( ::ABI::Windows::Foundation::Diagnostics::CausalityTraceLevel_Required, AllOptions::GetCausalitySource(), AllOptions::GetPlatformId(), reinterpret_cast< UINT64 >(this), static_cast< ::ABI::Windows::Foundation::AsyncStatus >(status)); } #endif // _WRL_DISABLE_CAUSALITY_ } void TraceProgressNotificationStart() { #ifndef _WRL_DISABLE_CAUSALITY_ if (gCausality && AllOptions::CausalityEnabled) { // Ignoring HRESULT intentionally. Tracking failure should not change the // normal flow of AsyncOperations gCausality->TraceSynchronousWorkStart( ::ABI::Windows::Foundation::Diagnostics::CausalityTraceLevel_Important, AllOptions::GetCausalitySource(), AllOptions::GetPlatformId(), reinterpret_cast< UINT64 >(this), ::ABI::Windows::Foundation::Diagnostics::CausalitySynchronousWork_ProgressNotification); } #endif // _WRL_DISABLE_CAUSALITY_ } void TraceProgressNotificationComplete() { #ifndef _WRL_DISABLE_CAUSALITY_ if (gCausality && AllOptions::CausalityEnabled) { // Ignoring HRESULT intentionally. Tracking failure should not change the // normal flow of AsyncOperations gCausality->TraceSynchronousWorkCompletion( ::ABI::Windows::Foundation::Diagnostics::CausalityTraceLevel_Important, AllOptions::GetCausalitySource(), ::ABI::Windows::Foundation::Diagnostics::CausalitySynchronousWork_ProgressNotification); } #endif // _WRL_DISABLE_CAUSALITY_ } void TraceCompletionNotificationStart() { #ifndef _WRL_DISABLE_CAUSALITY_ if (gCausality && AllOptions::CausalityEnabled) { // Ignoring HRESULT intentionally. Tracking failure should not change the // normal flow of AsyncOperations gCausality->TraceSynchronousWorkStart( ::ABI::Windows::Foundation::Diagnostics::CausalityTraceLevel_Required, AllOptions::GetCausalitySource(), AllOptions::GetPlatformId(), reinterpret_cast< UINT64 >(this), ::ABI::Windows::Foundation::Diagnostics::CausalitySynchronousWork_CompletionNotification); } #endif // _WRL_DISABLE_CAUSALITY_ } void TraceCompletionNotificationComplete() { #ifndef _WRL_DISABLE_CAUSALITY_ if (gCausality && AllOptions::CausalityEnabled) { // Ignoring HRESULT intentionally. Tracking failure should not change the // normal flow of AsyncOperations gCausality->TraceSynchronousWorkCompletion( ::ABI::Windows::Foundation::Diagnostics::CausalityTraceLevel_Required, AllOptions::GetCausalitySource(), ::ABI::Windows::Foundation::Diagnostics::CausalitySynchronousWork_CompletionNotification); } #endif // _WRL_DISABLE_CAUSALITY_ } void TraceExecutionStart(::ABI::Windows::Foundation::Diagnostics::CausalityTraceLevel traceLevel) { #ifndef _WRL_DISABLE_CAUSALITY_ if (gCausality && AllOptions::CausalityEnabled) { // Ignoring HRESULT intentionally. Tracking failure should not change the // normal flow of AsyncOperations gCausality->TraceSynchronousWorkStart( traceLevel, AllOptions::GetCausalitySource(), AllOptions::GetPlatformId(), reinterpret_cast< UINT64 >(this), ::ABI::Windows::Foundation::Diagnostics::CausalitySynchronousWork_Execution); } #endif // _WRL_DISABLE_CAUSALITY_ } void TraceExecutionComplete(::ABI::Windows::Foundation::Diagnostics::CausalityTraceLevel traceLevel) { #ifndef _WRL_DISABLE_CAUSALITY_ if (gCausality && AllOptions::CausalityEnabled) { // Ignoring HRESULT intentionally. Tracking failure should not change the // normal flow of AsyncOperations gCausality->TraceSynchronousWorkCompletion( traceLevel, AllOptions::GetCausalitySource(), ::ABI::Windows::Foundation::Diagnostics::CausalitySynchronousWork_Execution); } #endif // _WRL_DISABLE_CAUSALITY_ } void TraceDelegateAssigned() { #ifndef _WRL_DISABLE_CAUSALITY_ if (gCausality && AllOptions::CausalityEnabled) { // Ignoring HRESULT intentionally. Tracking failure should not change the // normal flow of AsyncOperations gCausality->TraceOperationRelation( ::ABI::Windows::Foundation::Diagnostics::CausalityTraceLevel_Verbose, AllOptions::GetCausalitySource(), AllOptions::GetPlatformId(), reinterpret_cast< UINT64 >(this), ::ABI::Windows::Foundation::Diagnostics::CausalityRelation_AssignDelegate); } #endif // _WRL_DISABLE_CAUSALITY_ } void TraceError() { #ifndef _WRL_DISABLE_CAUSALITY_ if (gCausality && AllOptions::CausalityEnabled) { // Ignoring HRESULT intentionally. Tracking failure should not change the // normal flow of AsyncOperations gCausality->TraceOperationRelation( ::ABI::Windows::Foundation::Diagnostics::CausalityTraceLevel_Verbose, AllOptions::GetCausalitySource(), AllOptions::GetPlatformId(), reinterpret_cast< UINT64 >(this), ::ABI::Windows::Foundation::Diagnostics::CausalityRelation_Error); } #endif // _WRL_DISABLE_CAUSALITY_ } void TraceCancellation() { #ifndef _WRL_DISABLE_CAUSALITY_ if (gCausality && AllOptions::CausalityEnabled) { // Ignoring HRESULT intentionally. Tracking failure should not change the // normal flow of AsyncOperations gCausality->TraceOperationRelation( ::ABI::Windows::Foundation::Diagnostics::CausalityTraceLevel_Important, AllOptions::GetCausalitySource(), AllOptions::GetPlatformId(), reinterpret_cast< UINT64 >(this), ::ABI::Windows::Foundation::Diagnostics::CausalityRelation_Cancel); } #endif // _WRL_DISABLE_CAUSALITY_ } #pragma endregion private: ::Microsoft::WRL::ComPtr completeDelegate_; void *completeDelegateBucketAssist_; ::Microsoft::WRL::ComPtr errorInfo_; Details::AsyncStatusInternal volatile currentStatus_; HRESULT volatile errorCode_; unsigned int id_; protected: void *asyncOperationBucketAssist_; }; }} // namespace Microsoft::WRL #pragma warning(pop) #ifndef _WRL_DISABLE_CAUSALITY #define CAUSALITY_OPTIONS(OpName) \ Microsoft::WRL::AsyncCausalityOptions< OpName > #define ASYNCBASE_CAUSALITY_OPTIONS(TComplete, OpName) \ Microsoft::WRL::AsyncBase< TComplete, Microsoft::WRL::Details::Nil, Microsoft::WRL::AsyncResultType::SingleResult, CAUSALITY_OPTIONS(OpName)> #define ASYNCBASE_WITH_PROGRESS_CAUSALITY_OPTIONS(TComplete, TProgress, OpName) \ Microsoft::WRL::AsyncBase< TComplete, TProgress, Microsoft::WRL::AsyncResultType::SingleResult, CAUSALITY_OPTIONS(OpName)> #define ASYNCBASE_DISABLE_CAUSALITY(TComplete) \ Microsoft::WRL::AsyncBase< TComplete, Microsoft::WRL::Details::Nil, Microsoft::WRL::AsyncResultType::SingleResult, Microsoft::WRL::DisableCausality> #define ASYNCBASE_WITH_PROGRESS_DISABLE_CAUSALITY(TComplete, TProgress) \ Microsoft::WRL::AsyncBase< TComplete, TProgress, Microsoft::WRL::AsyncResultType::SingleResult, Microsoft::WRL::DisableCausality> #endif // _WRL_DISABLE_CAUSALITY // Restore packing #include #endif // _WRL_ASYNC_H_ #ifdef BUILD_WINDOWS #include #endif