com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault Maven / Gradle / Ivy
Show all versions of hystrix-core Show documentation
/**
* Copyright 2012 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.strategy.concurrency;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default implementation of {@link HystrixRequestVariable}. Similar to {@link ThreadLocal} but scoped at the user request level. Context is managed via {@link HystrixRequestContext}.
*
* All statements below assume that child threads are spawned and initialized with the use of {@link HystrixContextCallable} or {@link HystrixContextRunnable} which capture state from a parent thread
* and propagate to the child thread.
*
* Characteristics that differ from ThreadLocal:
*
* - HystrixRequestVariable context must be initialized at the beginning of every request by {@link HystrixRequestContext#initializeContext}
* - HystrixRequestVariables attached to a thread will be cleared at the end of every user request by {@link HystrixRequestContext#shutdown} which execute {@link #remove} for each
* HystrixRequestVariable
* - HystrixRequestVariables have a {@link #shutdown} lifecycle method that gets called at the end of every user request (invoked when {@link HystrixRequestContext#shutdown} is called) to allow for
* resource cleanup.
* - HystrixRequestVariables are copied (by reference) to child threads via the {@link HystrixRequestContext#getContextForCurrentThread} and {@link HystrixRequestContext#setContextOnCurrentThread}
* functionality.
* - HystrixRequestVariables created on a child thread are available on sibling and parent threads.
* - HystrixRequestVariables created on a child thread will be cleaned up by the parent thread via the {@link #shutdown} method.
*
*
*
* Note on thread-safety: By design a HystrixRequestVariables is intended to be accessed by all threads in a user request, thus anything stored in a HystrixRequestVariables must be thread-safe and
* plan on being accessed/mutated concurrently.
*
* For example, a HashMap would likely not be a good choice for a RequestVariable value, but ConcurrentHashMap would.
*
* @param
* Type to be stored on the HystrixRequestVariable
*
* Example 1: {@code HystrixRequestVariable>}
* Example 2: {@code HystrixRequestVariable}
*
* @ExcludeFromJavadoc
* @ThreadSafe
*/
public class HystrixRequestVariableDefault implements HystrixRequestVariable {
static final Logger logger = LoggerFactory.getLogger(HystrixRequestVariableDefault.class);
/**
* Creates a new HystrixRequestVariable that will exist across all threads
* within a {@link HystrixRequestContext}
*/
public HystrixRequestVariableDefault() {
}
/**
* Get the current value for this variable for the current request context.
*
* @return the value of the variable for the current request,
* or null if no value has been set and there is no initial value
*/
@SuppressWarnings("unchecked")
public T get() {
if (HystrixRequestContext.getContextForCurrentThread() == null) {
throw new IllegalStateException(HystrixRequestContext.class.getSimpleName() + ".initializeContext() must be called at the beginning of each request before RequestVariable functionality can be used.");
}
ConcurrentHashMap, LazyInitializer>> variableMap = HystrixRequestContext.getContextForCurrentThread().state;
// short-circuit the synchronized path below if we already have the value in the ConcurrentHashMap
LazyInitializer> v = variableMap.get(this);
if (v != null) {
return (T) v.get();
}
/*
* Optimistically create a LazyInitializer to put into the ConcurrentHashMap.
*
* The LazyInitializer will not invoke initialValue() unless the get() method is invoked
* so we can optimistically instantiate LazyInitializer and then discard for garbage collection
* if the putIfAbsent fails.
*
* Whichever instance of LazyInitializer succeeds will then have get() invoked which will call
* the initialValue() method once-and-only-once.
*/
LazyInitializer l = new LazyInitializer(this);
LazyInitializer> existing = variableMap.putIfAbsent(this, l);
if (existing == null) {
/*
* We won the thread-race so can use 'l' that we just created.
*/
return l.get();
} else {
/*
* We lost the thread-race so let 'l' be garbage collected and instead return 'existing'
*/
return (T) existing.get();
}
}
/**
* Computes the initial value of the HystrixRequestVariable in a request.
*
* This is called the first time the value of the HystrixRequestVariable is fetched in a request. Override this to provide an initial value for a HystrixRequestVariable on each request on which it
* is used.
*
* The default implementation returns null.
*
* @return initial value of the HystrixRequestVariable to use for the instance being constructed
*/
public T initialValue() {
return null;
}
/**
* Sets the value of the HystrixRequestVariable for the current request context.
*
* Note, if a value already exists, the set will result in overwriting that value. It is up to the caller to ensure the existing value is cleaned up. The {@link #shutdown} method will not be
* called
*
* @param value
* the value to set
*/
public void set(T value) {
HystrixRequestContext.getContextForCurrentThread().state.put(this, new LazyInitializer(this, value));
}
/**
* Removes the value of the HystrixRequestVariable from the current request.
*
* This will invoke {@link #shutdown} if implemented.
*
* If the value is subsequently fetched in the thread, the {@link #initialValue} method will be called again.
*/
public void remove() {
if (HystrixRequestContext.getContextForCurrentThread() != null) {
remove(HystrixRequestContext.getContextForCurrentThread(), this);
}
}
@SuppressWarnings("unchecked")
/* package */static void remove(HystrixRequestContext context, HystrixRequestVariableDefault v) {
// remove first so no other threads get it
LazyInitializer> o = context.state.remove(v);
if (o != null) {
// this thread removed it so let's execute shutdown
v.shutdown((T) o.get());
}
}
/**
* Provide life-cycle hook for a HystrixRequestVariable implementation to perform cleanup
* before the HystrixRequestVariable is removed from the current thread.
*
* This is executed at the end of each user request when {@link HystrixRequestContext#shutdown} is called or whenever {@link #remove} is invoked.
*
* By default does nothing.
*
* NOTE: Do not call get()
from within this method or initialValue()
will be invoked again. The current value is passed in as an argument.
*
* @param value
* the value of the HystrixRequestVariable being removed
*/
public void shutdown(T value) {
// do nothing by default
}
/**
* Holder for a value that can be derived from the {@link HystrixRequestVariableDefault#initialValue} method that needs
* to be executed once-and-only-once.
*
* This class can be instantiated and garbage collected without calling initialValue() as long as the get() method is not invoked and can thus be used with compareAndSet in
* ConcurrentHashMap.putIfAbsent and allow "losers" in a thread-race to be discarded.
*
* @param
*/
/* package */static final class LazyInitializer {
// @GuardedBy("synchronization on get() or construction")
private T value;
/*
* Boolean to ensure only-once initialValue() execution instead of using
* a null check in case initialValue() returns null
*/
// @GuardedBy("synchronization on get() or construction")
private boolean initialized = false;
private final HystrixRequestVariableDefault rv;
private LazyInitializer(HystrixRequestVariableDefault rv) {
this.rv = rv;
}
private LazyInitializer(HystrixRequestVariableDefault rv, T value) {
this.rv = rv;
this.value = value;
this.initialized = true;
}
public synchronized T get() {
if (!initialized) {
value = rv.initialValue();
initialized = true;
}
return value;
}
}
}