/*
* Copyright 2010 LinkedIn, 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 voldemort.store.routed;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import voldemort.VoldemortException;
import voldemort.store.InsufficientOperationalNodesException;
import voldemort.store.routed.action.Action;
/**
* A Pipeline is the main conduit through which an {@link Action} is run. An
* {@link Action} is executed in response to the Pipeline receiving an event.
* The majority of the events are self-initiated from within the Pipeline
* itself. The only case thus-far where external entities create events are in
* response to asynchronous responses from servers. A {@link Response} instance
* is created on completion of an asynchronous request and is fed back into the
* Pipeline where an appropriate 'response handler' action is executed.
*
* <p/>
*
* A Pipeline instance is created per-request inside {@link RoutedStore}. This
* is due to the fact that it includes internal state, specific to each
* operation request (get, getAll, getVersions, put, and delete) invocation.
*/
public class Pipeline {
public enum Event {
STARTED,
CONFIGURED,
COMPLETED,
INSUFFICIENT_SUCCESSES,
INSUFFICIENT_ZONES,
RESPONSES_RECEIVED,
ERROR,
MASTER_DETERMINED,
ABORTED, // TODO: Aborted is a zombie state. No way to reach this state.
// Please clean me up.
HANDOFF_FINISHED;
}
public enum Operation {
GET,
GET_ALL,
GET_VERSIONS,
PUT,
DELETE;
public String getSimpleName() {
return toString().toLowerCase().replace("_", " ");
}
}
private final Operation operation;
private final long timeout;
private final TimeUnit unit;
private final BlockingQueue<Event> eventQueue;
private final Map<Event, Action> eventActions;
private final Logger logger = Logger.getLogger(getClass());
private volatile boolean enableHintedHandoff = false;
private volatile boolean finished = false;
/**
*
* @param operation
* @param timeout Timeout
* @param unit Unit of timeout
*/
public Pipeline(Operation operation, long timeout, TimeUnit unit) {
this.operation = operation;
this.timeout = timeout;
this.unit = unit;
this.eventQueue = new LinkedBlockingQueue<Event>();
this.eventActions = new ConcurrentHashMap<Event, Action>();
}
public Operation getOperation() {
return operation;
}
/**
* Assigns the event to be handled by the given action. When a given event
* is received, it is expected that there is an action to handle it.
*
* @param event Event
* @param action Action to invoke upon receipt of that event
*/
public void addEventAction(Event event, Action action) {
eventActions.put(event, action);
}
/**
* Pipeline can't proceed further. If hinted handoff is enabled go to go to
* a state where it can be performed, otherwise go straight to error state.
*/
public void abort() {
addEvent(Event.ERROR);
}
/**
* Add an event to the queue. It will be processed in the order received.
*
* @param event Event
*/
public void addEvent(Event event) {
if(event == null)
throw new IllegalStateException("event must be non-null");
if(logger.isTraceEnabled())
logger.trace("Adding event " + event);
eventQueue.add(event);
}
public boolean isHintedHandoffEnabled() {
return enableHintedHandoff;
}
public void setEnableHintedHandoff(boolean enableHintedHandoff) {
this.enableHintedHandoff = enableHintedHandoff;
}
public boolean isFinished() {
return finished;
}
/**
* Process events in the order as they were received.
*
* <p/>
*
* The overall time to process the events must be within the bounds of the
* timeout or an {@link InsufficientOperationalNodesException} will be
* thrown.
*/
public void execute() {
try {
while(true) {
Event event = null;
try {
event = eventQueue.poll(timeout, unit);
} catch(InterruptedException e) {
throw new InsufficientOperationalNodesException(operation.getSimpleName()
+ " operation interrupted!", e);
}
if(event == null)
throw new VoldemortException(operation.getSimpleName()
+ " returned a null event");
if(event.equals(Event.ERROR)) {
if(logger.isTraceEnabled())
logger.trace(operation.getSimpleName()
+ " request, events complete due to error");
break;
} else if(event.equals(Event.COMPLETED)) {
if(logger.isTraceEnabled())
logger.trace(operation.getSimpleName() + " request, events complete");
break;
}
Action action = eventActions.get(event);
if(action == null)
throw new IllegalStateException("action was null for event " + event);
if(logger.isTraceEnabled())
logger.trace(operation.getSimpleName() + " request, action "
+ action.getClass().getSimpleName() + " to handle " + event
+ " event");
action.execute(this);
}
} finally {
finished = true;
}
}
}