All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.hystrix.collapser.RequestCollapser Maven / Gradle / Ivy

There is a newer version: 1.5.18
Show newest version
/**
 * Copyright 2015 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.netflix.hystrix.collapser;

import java.lang.ref.Reference;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import rx.Observable;

import com.netflix.hystrix.HystrixCollapserProperties;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixContextCallable;
import com.netflix.hystrix.util.HystrixTimer.TimerListener;

/**
 * Requests are submitted to this and batches executed based on size or time. Scoped to either a request or the global application.
 * 

* Instances of this are retrieved from the RequestCollapserFactory. * * Must be thread-safe since it exists within a RequestVariable which is request-scoped and can be accessed from multiple threads. * * @ThreadSafe */ public class RequestCollapser { static final Logger logger = LoggerFactory.getLogger(RequestCollapser.class); static final Object NULL_SENTINEL = new Object(); private final HystrixCollapserBridge commandCollapser; // batch can be null once shutdown private final AtomicReference> batch = new AtomicReference>(); private final AtomicReference> timerListenerReference = new AtomicReference>(); private final AtomicBoolean timerListenerRegistered = new AtomicBoolean(); private final CollapserTimer timer; private final HystrixCollapserProperties properties; private final HystrixConcurrencyStrategy concurrencyStrategy; /** * @param commandCollapser collapser which will create the batched requests and demultiplex the results * @param properties collapser properties that define how collapsing occurs * @param timer {@link CollapserTimer} which performs the collapsing * @param concurrencyStrategy strategy for managing the {@link Callable}s generated by {@link RequestCollapser} */ RequestCollapser(HystrixCollapserBridge commandCollapser, HystrixCollapserProperties properties, CollapserTimer timer, HystrixConcurrencyStrategy concurrencyStrategy) { this.commandCollapser = commandCollapser; // the command with implementation of abstract methods we need this.concurrencyStrategy = concurrencyStrategy; this.properties = properties; this.timer = timer; batch.set(new RequestBatch(properties, commandCollapser, properties.maxRequestsInBatch().get())); } /** * Submit a request to a batch. If the batch maxSize is hit trigger the batch immediately. * * @param arg argument to a {@link RequestCollapser} * @return Observable * @throws IllegalStateException * if submitting after shutdown */ public Observable submitRequest(final RequestArgumentType arg) { /* * 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.get() && timerListenerRegistered.compareAndSet(false, true)) { /* schedule the collapsing task to be executed every x milliseconds (x defined inside CollapsedTask) */ timerListenerReference.set(timer.addListener(new CollapsedTask())); } // loop until succeed (compare-and-set spin-loop) while (true) { final RequestBatch b = batch.get(); if (b == null) { return Observable.error(new IllegalStateException("Submitting requests after collapser is shutdown")); } final Observable response; if (arg != null) { response = b.offer(arg); } else { response = b.offer( (RequestArgumentType) NULL_SENTINEL); } // it will always get an Observable 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); } } } private void createNewBatchAndExecutePreviousIfNeeded(RequestBatch previousBatch) { if (previousBatch == null) { throw new IllegalStateException("Trying to start null batch which means it was shutdown already."); } if (batch.compareAndSet(previousBatch, new RequestBatch(properties, commandCollapser, properties.maxRequestsInBatch().get()))) { // this thread won so trigger the previous batch previousBatch.executeBatchIfNotAlreadyStarted(); } } /** * Called from RequestVariable.shutdown() to unschedule the task. */ public void shutdown() { RequestBatch currentBatch = batch.getAndSet(null); if (currentBatch != null) { currentBatch.shutdown(); } if (timerListenerReference.get() != null) { // if the timer was started we'll clear it so it stops ticking timerListenerReference.get().clear(); } } /** * Executed on each Timer interval execute the current batch if it has requests in it. */ private class CollapsedTask implements TimerListener { final Callable callableWithContextOfParent; CollapsedTask() { // this gets executed from the context of a HystrixCommand parent thread (such as a Tomcat thread) // so we create the callable now where we can capture the thread context callableWithContextOfParent = new HystrixContextCallable(concurrencyStrategy, new Callable() { // the wrapCallable call allows a strategy to capture thread-context if desired @Override public Void call() throws Exception { try { // we fetch current so that when multiple threads race // we can do compareAndSet with the expected/new to ensure only one happens RequestBatch currentBatch = batch.get(); // 1) it can be null if it got shutdown // 2) we don't execute this batch if it has no requests and let it wait until next tick to be executed if (currentBatch != null && currentBatch.getSize() > 0) { // do execution within context of wrapped Callable createNewBatchAndExecutePreviousIfNeeded(currentBatch); } } catch (Throwable t) { logger.error("Error occurred trying to execute the batch.", t); t.printStackTrace(); // ignore error so we don't kill the Timer mainLoop and prevent further items from being scheduled } return null; } }); } @Override public void tick() { try { callableWithContextOfParent.call(); } catch (Exception e) { logger.error("Error occurred trying to execute callable inside CollapsedTask from Timer.", e); e.printStackTrace(); } } @Override public int getIntervalTimeInMilliseconds() { return properties.timerDelayInMilliseconds().get(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy