public CollapsedRequest <RequestResponseType, RequestArgumentType> SubmitRequest(RequestArgumentType arg, CancellationToken token) { /* * We only want the timer ticking if there are actually things to do so we register it the first time something is added. */ if (!timerListenerRegistered.Value && timerListenerRegistered.CompareAndSet(false, true)) { /* schedule the collapsing task to be executed every x milliseconds (x defined inside CollapsedTask) */ timerListenerReference.Value = timer.AddListener(new CollapsedTask <BatchReturnType, RequestResponseType, RequestArgumentType>(this)); } // loop until succeed (compare-and-set spin-loop) while (true) { RequestBatch <BatchReturnType, RequestResponseType, RequestArgumentType> b = batch.Value; if (b == null) { throw new InvalidOperationException("Submitting requests after collapser is shutdown"); } CollapsedRequest <RequestResponseType, RequestArgumentType> response = b.Offer(arg, token); // it will always get an CollapsedRequest unless we hit the max batch size if (response != null) { return(response); } else { // this batch can't accept requests so create a new one and set it if another thread doesn't beat us CreateNewBatchAndExecutePreviousIfNeeded(b); } } }
/** * Best-effort attempt to remove an argument from a batch. This may get invoked when a cancellation occurs somewhere downstream. * This method finds the argument in the batch, and removes it. * */ internal void Remove(RequestArgumentType arg) { if (batchStarted.Value) { //nothing we can do return; } if (batchLock.TryEnterReadLock(1)) { try { /* double-check now that we have the lock - if the batch is started, deleting is useless */ if (batchStarted.Value) { return; } CollapsedRequest <RequestResponseType, RequestArgumentType> existing = null; if (arg == null) { nullArg.Value = null; } if (argumentMap.TryRemove(arg, out existing)) { // Log } } finally { batchLock.ExitReadLock(); } } }
public CollapsedRequest <RequestResponseType, RequestArgumentType> Offer(RequestArgumentType arg, CancellationToken token) { /* short-cut - if the batch is started we reject the offer */ if (batchStarted.Value) { return(null); } /* * The 'read' just means non-exclusive even though we are writing. */ if (batchLock.TryEnterReadLock(1)) { try { /* double-check now that we have the lock - if the batch is started we reject the offer */ if (batchStarted.Value) { return(null); } if (argumentMap.Count >= maxBatchSize) { return(null); } else { CollapsedRequest <RequestResponseType, RequestArgumentType> collapsedRequest = new CollapsedRequest <RequestResponseType, RequestArgumentType>(arg, token); TaskCompletionSource <RequestResponseType> tcs = new TaskCompletionSource <RequestResponseType>(collapsedRequest); collapsedRequest.CompletionSource = tcs; CollapsedRequest <RequestResponseType, RequestArgumentType> existing = null; if (arg == null) { existing = GetOrAddNullArg(collapsedRequest); } else { existing = argumentMap.GetOrAdd(arg, collapsedRequest); } /* * If the argument already exists in the batch, then there are 2 options: * A) If request caching is ON (the default): only keep 1 argument in the batch and let all responses * be hooked up to that argument * B) If request caching is OFF: return an error to all duplicate argument requests * * This maintains the invariant that each batch has no duplicate arguments. This prevents the impossible * logic (in a user-provided mapResponseToRequests for HystrixCollapser and the internals of HystrixObservableCollapser) * of trying to figure out which argument of a set of duplicates should get attached to a response. * * See https://github.com/Netflix/Hystrix/pull/1176 for further discussion. */ if (existing != collapsedRequest) { bool requestCachingEnabled = properties.RequestCacheEnabled; if (requestCachingEnabled) { return(existing); } else { throw new ArgumentException("Duplicate argument in collapser batch : [" + arg + "] This is not supported. Please turn request-caching on for HystrixCollapser:" + commandCollapser.CollapserKey.Name + " or prevent duplicates from making it into the batch!"); } } else { return(collapsedRequest); } } } finally { batchLock.ExitReadLock(); } } else { return(null); } }
internal CollapsedRequest <RequestResponseType, RequestArgumentType> GetOrAddNullArg(CollapsedRequest <RequestResponseType, RequestArgumentType> collapsedRequest) { if (nullArg.CompareAndSet(null, collapsedRequest)) { return(collapsedRequest); } return(nullArg.Value); }