// return: true ==> throw an OCE(externalCT) // false ==> thrown an AggregateException(ex). private static bool ThrowAnOCE(Exception ex, CancellationState cancellationState) { // if the query has been canceled and the exception represents this, we want to throw OCE // but otherwise we want to throw an AggregateException to mimic normal Plinq operation // See QueryTaskGroupState.WaitAll for the main plinq exception handling logic. // check for co-operative cancellation. #if PFX_LEGACY_3_5 OperationCanceledException2 cancelEx = ex as OperationCanceledException2; #else OperationCanceledException cancelEx = ex as OperationCanceledException; #endif if (cancelEx != null && cancelEx.CancellationToken == cancellationState.ExternalCancellationToken && cancellationState.ExternalCancellationToken.IsCancellationRequested) { return(true); // let the OCE(extCT) be rethrown. } // check for external cancellation which triggered the mergedToken. if (cancelEx != null && cancelEx.CancellationToken == cancellationState.MergedCancellationToken && cancellationState.MergedCancellationToken.IsCancellationRequested && cancellationState.ExternalCancellationToken.IsCancellationRequested) { return(true); // convert internal cancellation back to OCE(extCT). } return(false); }
//--------------------------------------------------------------------------------------- // Executes the entire query tree, and aggregates the intermediate results into the // final result based on the binary operators and final reduction. // // Return Value: // The single result of aggregation. // internal TResult Aggregate() { TResult tr; Exception toThrow = null; try { tr = InternalAggregate(ref toThrow); } catch (ThreadAbortException) { // Do not wrap ThreadAbortExceptions throw; } catch (Exception ex) { // If the exception is not an aggregate, we must wrap it up and throw that instead. if (!(ex is AggregateException)) { // // Special case: if the query has been canceled, we do not want to wrap the // OperationCanceledException with an AggregateException. // // The query has been canceled iff these conditions hold: // - The exception thrown is OperationCanceledException // - We find the external CancellationToken for this query in the OperationCanceledException // - The externalToken is actually in the canceled state. #if PFX_LEGACY_3_5 OperationCanceledException2 cancelEx = ex as OperationCanceledException2; #else OperationCanceledException cancelEx = ex as OperationCanceledException; #endif if (cancelEx != null && cancelEx.CancellationToken == SpecifiedQuerySettings.CancellationState.ExternalCancellationToken && SpecifiedQuerySettings.CancellationState.ExternalCancellationToken.IsCancellationRequested) { throw; } throw new AggregateException(ex); } // Else, just rethrow the current active exception. throw; } // If the aggregation requested that we throw a singular exception, throw it. if (toThrow != null) { throw toThrow; } return(tr); }
//----------------------------------------------------------------------------------- // The implementation of the Work API just enumerates the producer's data, and // enqueues it into the consumer channel. Well, really, it just defers to extension // points (below) implemented by subclasses to do these things. // protected override void Work() { try { // Defer to the base class for the actual work. SpoolingWork(); } catch (Exception ex) { #if PFX_LEGACY_3_5 OperationCanceledException2 oce = ex as OperationCanceledException2; #else OperationCanceledException oce = ex as OperationCanceledException; #endif if (oce != null && oce.CancellationToken == m_groupState.CancellationState.MergedCancellationToken && m_groupState.CancellationState.MergedCancellationToken.IsCancellationRequested) { //an expected internal cancellation has occurred. suppress this exception. } else { // TPL will catch and store the exception on the task object. We'll then later // turn around and wait on it, having the effect of propagating it. In the meantime, // we want to cooperative cancel all workers. m_groupState.CancellationState.InternalCancellationTokenSource.Cancel(); // And then repropagate to let TPL catch it. throw; } } finally { SpoolingFinally(); //dispose resources etc. } }
//----------------------------------------------------------------------------------- // Marks the end of a query's execution, waiting for all tasks to finish and // propagating any relevant exceptions. Note that the full set of tasks must have // been initialized (with SetTask) before calling this. // internal void QueryEnd(bool userInitiatedDispose) { Contract.Assert(m_rootTask != null); //Contract.Assert(Task.Current == null || (Task.Current != m_rootTask && Task.Current.Parent != m_rootTask)); if (Interlocked.Exchange(ref m_alreadyEnded, 1) == 0) { // There are four cases: // Case #1: Wait produced an exception that is not OCE(ct), or an AggregateException which is not full of OCE(ct) ==> We rethrow. // Case #2: External cancellation has been requested ==> we'll manually throw OCE(externalToken). // Case #3a: We are servicing a call to Dispose() (and possibly also external cancellation has been requested).. simply return. See bug 695173 // Case #3b: The enumerator has already been disposed (and possibly also external cancellation was requested). Throw an ODE. // Case #4: No exceptions or explicit call to Dispose() by this caller ==> we just return. // See also "InlinedAggregationOperator" which duplicates some of this logic for the aggregators. // See also "QueryOpeningEnumerator" which duplicates some of this logic. // See also "ExceptionAggregator" which duplicates some of this logic. try { // Wait for all the tasks to complete // If any of the tasks ended in the Faulted stated, an AggregateException will be thrown. m_rootTask.Wait(); } catch (AggregateException ae) { AggregateException flattenedAE = ae.Flatten(); bool allOCEsOnTrackedExternalCancellationToken = true; for (int i = 0; i < flattenedAE.InnerExceptions.Count; i++) { #if PFX_LEGACY_3_5 OperationCanceledException2 oce = flattenedAE.InnerExceptions[i] as OperationCanceledException2; #else OperationCanceledException oce = flattenedAE.InnerExceptions[i] as OperationCanceledException; #endif // we only let it pass through iff: // it is not null, not default, and matches the exact token we were given as being the external token // and the external Token is actually canceled (ie not a spoof OCE(extCT) for a non-canceled extCT) if (oce == null || !oce.CancellationToken.IsCancellationRequested || oce.CancellationToken != m_cancellationState.ExternalCancellationToken) { allOCEsOnTrackedExternalCancellationToken = false; break; } } // if all the exceptions were OCE(externalToken), then we will propogate only a single OCE(externalToken) below // otherwise, we flatten the aggregate (because the WaitAll above already aggregated) and rethrow. if (!allOCEsOnTrackedExternalCancellationToken) { throw flattenedAE; // Case #1 } } finally { m_rootTask.Dispose(); } if (m_cancellationState.MergedCancellationToken.IsCancellationRequested) { // cancellation has occured but no user-delegate exceptions were detected // NOTE: it is important that we see other state variables correctly here, and that // read-reordering hasn't played havoc. // This is OK because // 1. all the state writes (eg in the Initiate* methods) are volatile writes (standard .NET MM) // 2. tokenCancellationRequested is backed by a volatile field, hence the reads below // won't get reordered about the read of token.IsCancellationRequested. // If the query has already been disposed, we don't want to throw an OCE (this is a fix for bug 695173.) if (!m_cancellationState.TopLevelDisposedFlag.Value) { CancellationState.ThrowWithStandardMessageIfCanceled(m_cancellationState.ExternalCancellationToken); // Case #2 } //otherwise, given that there were no user-delegate exceptions (they would have been rethrown above), //the only remaining situation is user-initiated dispose. Contract.Assert(m_cancellationState.TopLevelDisposedFlag.Value); // If we aren't actively disposing, that means somebody else previously disposed // of the enumerator. We must throw an ObjectDisposedException. if (!userInitiatedDispose) { throw new ObjectDisposedException("enumerator", "PLINQ_DisposeRequested"); // Case #3 } } // Case #4. nothing to do. } }