public async Task <T> ExecuteAsync()
{
/* this is a stateful object so can only be used once */
if (Interlocked.CompareExchange(ref _started, 1, 0) == 1)
{
throw new IllegalStateException("This instance can only be executed once. Please instantiate a new instance.");
}
CacheItem cacheItem;
// get from cache
if (_requestCache != null)
{
var key = GetCacheKey();
if (key != null && _requestCache.TryGetValue(key, out cacheItem))
{
Metrics.MarkResponseFromCache();
_executionResult = cacheItem.ExecutionResult;
_isExecutionComplete = true;
RecordExecutedCommand();
try
{
_executionHook.OnCacheHit(this);
}
catch (Exception hookEx)
{
Logger.LogWarning("Error calling CommandExecutionHook.onCacheHit", hookEx);
}
return(cacheItem.Value);
}
}
T result;
long ms;
var start = _clock.EllapsedTimeInMs;
try
{
RecordExecutedCommand();
Metrics.IncrementConcurrentExecutionCount();
// mark that we're starting execution on the ExecutionHook
// if this hook throws an exception, then a fast-fail occurs with no fallback. No state is left inconsistent
_executionHook.OnStart(this);
if (_circuitBreaker.AllowRequest) // short circuit closed = OK
{
if (ExecutionSemaphore.TryAcquire()) // semaphore
{
try
{
// if bulkhead by thread
var token = Properties.ExecutionTimeoutEnabled.Value
? new CancellationTokenSource(Properties.ExecutionIsolationThreadTimeoutInMilliseconds.Value)
: new CancellationTokenSource();
try
{
if ((_flags & ServiceCommandOptions.SemaphoreExecutionStrategy) != ServiceCommandOptions.SemaphoreExecutionStrategy)
{
_isExecutedInThread = true;
///
/// If any of these hooks throw an exception, then it appears as if the actual execution threw an error
///
_executionHook.OnThreadStart(this);
_executionHook.OnExecutionStart(this);
// Run with bulkhead and timeout
var t = await Task.Factory.StartNew(
() => Run(token.Token),
token.Token,
TaskCreationOptions.DenyChildAttach | TaskCreationOptions.HideScheduler,
TaskScheduler
)
.ConfigureAwait(false);
result = await t.ConfigureAwait(false);
if (token.IsCancellationRequested)
{
throw new OperationCanceledException();
}
_executionHook.OnThreadComplete(this);
}
else
{
// Simple semaphore
_executionHook.OnExecutionStart(this);
result = await Run(token.Token).ConfigureAwait(false);
}
try
{
_executionHook.OnExecutionSuccess(this);
}
catch (Exception hookEx)
{
Logger.LogWarning("Error calling CommandExecutionHook.onExecutionSuccess", hookEx);
}
}
catch (AggregateException ae)
{
ms = _clock.EllapsedTimeInMs - start;
var e = ((AggregateException)ae).InnerException;
return(await OnExecutionError(ms, e));
}
catch (TaskSchedulerException tex)
{
start = 0; // We don't want register execution time.
Metrics.MarkThreadPoolRejection();
return(await GetFallbackOrThrowException(EventType.THREAD_POOL_REJECTED, FailureType.REJECTED_THREAD_EXECUTION, "Thread pool is full", tex.InnerException));
}
catch (OperationCanceledException)
{
// timeout
ms = _clock.EllapsedTimeInMs - start;
Metrics.MarkTimeout(ms);
return(await GetFallbackOrThrowException(EventType.TIMEOUT, FailureType.TIMEOUT, "timed-out", ServiceCommand <T> .TimeoutException));
}
catch (Exception e)
{
ms = _clock.EllapsedTimeInMs - start;
e = _executionHook.OnExecutionError(this, e);
return(await OnExecutionError(ms, e));
}
ms = _clock.EllapsedTimeInMs - start;
Metrics.AddCommandExecutionTime(ms);
Metrics.MarkSuccess(ms);
_circuitBreaker.MarkSuccess();
_executionResult.AddEvent(EventType.SUCCESS);
try
{
_executionHook.OnSuccess(this);
}
catch (Exception hookEx)
{
Logger.LogWarning("Error calling CommandExecutionHook.onSuccess", hookEx);
}
if (_requestCache != null)
{
var key = GetCacheKey();
cacheItem = new CacheItem {
ExecutionResult = new ExecutionResult(_executionResult), Value = result
};
cacheItem.ExecutionResult.AddEvent(EventType.RESPONSE_FROM_CACHE);
cacheItem.ExecutionResult.ExecutionTime = -1;
if (key != null && !_requestCache.TryAdd(key, cacheItem))
{
_requestCache.TryGetValue(key, out cacheItem);
_executionResult = cacheItem.ExecutionResult;
result = cacheItem.Value;
start = 0;
}
}
return(result);
}
finally
{
ExecutionSemaphore.Release();
}
}
else
{
Metrics.MarkSemaphoreRejection();
return(await GetFallbackOrThrowException(EventType.SEMAPHORE_REJECTED, FailureType.REJECTED_SEMAPHORE_EXECUTION,
"could not acquire a semaphore for execution", new Exception("could not acquire a semaphore for execution")));
}
}
else
{
start = 0;
Metrics.MarkShortCircuited();
return(await GetFallbackOrThrowException(EventType.SHORT_CIRCUITED, FailureType.SHORTCIRCUIT,
"short-circuited", new Exception(" circuit short-circuited and is OPEN")));
}
}
finally
{
if (start > 0)
{
RecordTotalExecutionTime(start);
}
Metrics.DecrementConcurrentExecutionCount();
_isExecutionComplete = true;
}
}