Copyright 2016 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.
/** * Copyright 2016 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; import java.util.ArrayList; import java.util.BitSet; import java.util.List;
Immutable holder class for the status of command execution.

This object can be referenced and "modified" by parent and child threads as well as by different instances of HystrixCommand since 1 instance could create an ExecutionResult, cache a Future that refers to it, a 2nd instance execution then retrieves a Future from cache and wants to append RESPONSE_FROM_CACHE to whatever the ExecutionResult was from the first command execution.

This being immutable forces and ensure thread-safety instead of using AtomicInteger/ConcurrentLinkedQueue and determining when it's safe to mutate the object directly versus needing to deep-copy clone to a new instance.

/** * Immutable holder class for the status of command execution. * <p> * This object can be referenced and "modified" by parent and child threads as well as by different instances of HystrixCommand since * 1 instance could create an ExecutionResult, cache a Future that refers to it, a 2nd instance execution then retrieves a Future * from cache and wants to append RESPONSE_FROM_CACHE to whatever the ExecutionResult was from the first command execution. * <p> * This being immutable forces and ensure thread-safety instead of using AtomicInteger/ConcurrentLinkedQueue and determining * when it's safe to mutate the object directly versus needing to deep-copy clone to a new instance. */
public class ExecutionResult { private final EventCounts eventCounts; private final Exception failedExecutionException; private final Exception executionException; private final long startTimestamp; private final int executionLatency; //time spent in run() method private final int userThreadLatency; //time elapsed between caller thread submitting request and response being visible to it private final boolean executionOccurred; private final boolean isExecutedInThread; private final HystrixCollapserKey collapserKey; private static final HystrixEventType[] ALL_EVENT_TYPES = HystrixEventType.values(); private static final int NUM_EVENT_TYPES = ALL_EVENT_TYPES.length; private static final BitSet EXCEPTION_PRODUCING_EVENTS = new BitSet(NUM_EVENT_TYPES); private static final BitSet TERMINAL_EVENTS = new BitSet(NUM_EVENT_TYPES); static { for (HystrixEventType eventType: HystrixEventType.EXCEPTION_PRODUCING_EVENT_TYPES) { EXCEPTION_PRODUCING_EVENTS.set(eventType.ordinal()); } for (HystrixEventType eventType: HystrixEventType.TERMINAL_EVENT_TYPES) { TERMINAL_EVENTS.set(eventType.ordinal()); } } public static class EventCounts { private final BitSet events; private final int numEmissions; private final int numFallbackEmissions; private final int numCollapsed; EventCounts() { this.events = new BitSet(NUM_EVENT_TYPES); this.numEmissions = 0; this.numFallbackEmissions = 0; this.numCollapsed = 0; } EventCounts(BitSet events, int numEmissions, int numFallbackEmissions, int numCollapsed) { this.events = events; this.numEmissions = numEmissions; this.numFallbackEmissions = numFallbackEmissions; this.numCollapsed = numCollapsed; } EventCounts(HystrixEventType... eventTypes) { BitSet newBitSet = new BitSet(NUM_EVENT_TYPES); int localNumEmits = 0; int localNumFallbackEmits = 0; int localNumCollapsed = 0; for (HystrixEventType eventType: eventTypes) { switch (eventType) { case EMIT: newBitSet.set(HystrixEventType.EMIT.ordinal()); localNumEmits++; break; case FALLBACK_EMIT: newBitSet.set(HystrixEventType.FALLBACK_EMIT.ordinal()); localNumFallbackEmits++; break; case COLLAPSED: newBitSet.set(HystrixEventType.COLLAPSED.ordinal()); localNumCollapsed++; break; default: newBitSet.set(eventType.ordinal()); break; } } this.events = newBitSet; this.numEmissions = localNumEmits; this.numFallbackEmissions = localNumFallbackEmits; this.numCollapsed = localNumCollapsed; } EventCounts plus(HystrixEventType eventType) { return plus(eventType, 1); } EventCounts plus(HystrixEventType eventType, int count) { BitSet newBitSet = (BitSet) events.clone(); int localNumEmits = numEmissions; int localNumFallbackEmits = numFallbackEmissions; int localNumCollapsed = numCollapsed; switch (eventType) { case EMIT: newBitSet.set(HystrixEventType.EMIT.ordinal()); localNumEmits += count; break; case FALLBACK_EMIT: newBitSet.set(HystrixEventType.FALLBACK_EMIT.ordinal()); localNumFallbackEmits += count; break; case COLLAPSED: newBitSet.set(HystrixEventType.COLLAPSED.ordinal()); localNumCollapsed += count; break; default: newBitSet.set(eventType.ordinal()); break; } return new EventCounts(newBitSet, localNumEmits, localNumFallbackEmits, localNumCollapsed); } public boolean contains(HystrixEventType eventType) { return events.get(eventType.ordinal()); } public boolean containsAnyOf(BitSet other) { return events.intersects(other); } public int getCount(HystrixEventType eventType) { switch (eventType) { case EMIT: return numEmissions; case FALLBACK_EMIT: return numFallbackEmissions; case EXCEPTION_THROWN: return containsAnyOf(EXCEPTION_PRODUCING_EVENTS) ? 1 : 0; case COLLAPSED: return numCollapsed; default: return contains(eventType) ? 1 : 0; } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; EventCounts that = (EventCounts) o; if (numEmissions != that.numEmissions) return false; if (numFallbackEmissions != that.numFallbackEmissions) return false; if (numCollapsed != that.numCollapsed) return false; return events.equals(that.events); } @Override public int hashCode() { int result = events.hashCode(); result = 31 * result + numEmissions; result = 31 * result + numFallbackEmissions; result = 31 * result + numCollapsed; return result; } @Override public String toString() { return "EventCounts{" + "events=" + events + ", numEmissions=" + numEmissions + ", numFallbackEmissions=" + numFallbackEmissions + ", numCollapsed=" + numCollapsed + '}'; } } private ExecutionResult(EventCounts eventCounts, long startTimestamp, int executionLatency, int userThreadLatency, Exception failedExecutionException, Exception executionException, boolean executionOccurred, boolean isExecutedInThread, HystrixCollapserKey collapserKey) { this.eventCounts = eventCounts; this.startTimestamp = startTimestamp; this.executionLatency = executionLatency; this.userThreadLatency = userThreadLatency; this.failedExecutionException = failedExecutionException; this.executionException = executionException; this.executionOccurred = executionOccurred; this.isExecutedInThread = isExecutedInThread; this.collapserKey = collapserKey; } // we can return a static version since it's immutable static ExecutionResult EMPTY = ExecutionResult.from(); public static ExecutionResult from(HystrixEventType... eventTypes) { boolean didExecutionOccur = false; for (HystrixEventType eventType: eventTypes) { if (didExecutionOccur(eventType)) { didExecutionOccur = true; } } return new ExecutionResult(new EventCounts(eventTypes), -1L, -1, -1, null, null, didExecutionOccur, false, null); } private static boolean didExecutionOccur(HystrixEventType eventType) { switch (eventType) { case SUCCESS: return true; case FAILURE: return true; case BAD_REQUEST: return true; case TIMEOUT: return true; case CANCELLED: return true; default: return false; } } public ExecutionResult setExecutionOccurred() { return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, failedExecutionException, executionException, true, isExecutedInThread, collapserKey); } public ExecutionResult setExecutionLatency(int executionLatency) { return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); } public ExecutionResult setException(Exception e) { return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, e, executionException, executionOccurred, isExecutedInThread, collapserKey); } public ExecutionResult setExecutionException(Exception executionException) { return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); } public ExecutionResult setInvocationStartTime(long startTimestamp) { return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); } public ExecutionResult setExecutedInThread() { return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, failedExecutionException, executionException, executionOccurred, true, collapserKey); } public ExecutionResult setNotExecutedInThread() { return new ExecutionResult(eventCounts, startTimestamp, executionLatency, userThreadLatency, failedExecutionException, executionException, executionOccurred, false, collapserKey); } public ExecutionResult markCollapsed(HystrixCollapserKey collapserKey, int sizeOfBatch) { return new ExecutionResult(eventCounts.plus(HystrixEventType.COLLAPSED, sizeOfBatch), startTimestamp, executionLatency, userThreadLatency, failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); } public ExecutionResult markUserThreadCompletion(long userThreadLatency) { if (startTimestamp > 0 && !isResponseRejected()) { /* execution time (must occur before terminal state otherwise a race condition can occur if requested by client) */ return new ExecutionResult(eventCounts, startTimestamp, executionLatency, (int) userThreadLatency, failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); } else { return this; } }
Creates a new ExecutionResult by adding the defined 'event' to the ones on the current instance.
Params:
  • eventType – event to add
Returns:new ExecutionResult with event added
/** * Creates a new ExecutionResult by adding the defined 'event' to the ones on the current instance. * * @param eventType event to add * @return new {@link ExecutionResult} with event added */
public ExecutionResult addEvent(HystrixEventType eventType) { return new ExecutionResult(eventCounts.plus(eventType), startTimestamp, executionLatency, userThreadLatency, failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); } public ExecutionResult addEvent(int executionLatency, HystrixEventType eventType) { if (startTimestamp >= 0 && !isResponseRejected()) { return new ExecutionResult(eventCounts.plus(eventType), startTimestamp, executionLatency, userThreadLatency, failedExecutionException, executionException, executionOccurred, isExecutedInThread, collapserKey); } else { return addEvent(eventType); } } public EventCounts getEventCounts() { return eventCounts; } public long getStartTimestamp() { return startTimestamp; } public int getExecutionLatency() { return executionLatency; } public int getUserThreadLatency() { return userThreadLatency; } public long getCommandRunStartTimeInNanos() { return startTimestamp * 1000 * 1000; } public Exception getException() { return failedExecutionException; } public Exception getExecutionException() { return executionException; } public HystrixCollapserKey getCollapserKey() { return collapserKey; } public boolean isResponseSemaphoreRejected() { return eventCounts.contains(HystrixEventType.SEMAPHORE_REJECTED); } public boolean isResponseThreadPoolRejected() { return eventCounts.contains(HystrixEventType.THREAD_POOL_REJECTED); } public boolean isResponseRejected() { return isResponseThreadPoolRejected() || isResponseSemaphoreRejected(); } public List<HystrixEventType> getOrderedList() { List<HystrixEventType> eventList = new ArrayList<HystrixEventType>(); for (HystrixEventType eventType: ALL_EVENT_TYPES) { if (eventCounts.contains(eventType)) { eventList.add(eventType); } } return eventList; } public boolean isExecutedInThread() { return isExecutedInThread; } public boolean executionOccurred() { return executionOccurred; } public boolean containsTerminalEvent() { return eventCounts.containsAnyOf(TERMINAL_EVENTS); } @Override public String toString() { return "ExecutionResult{" + "eventCounts=" + eventCounts + ", failedExecutionException=" + failedExecutionException + ", executionException=" + executionException + ", startTimestamp=" + startTimestamp + ", executionLatency=" + executionLatency + ", userThreadLatency=" + userThreadLatency + ", executionOccurred=" + executionOccurred + ", isExecutedInThread=" + isExecutedInThread + ", collapserKey=" + collapserKey + '}'; } }