Package com.linkedin.helix.messaging.handling

Source Code of com.linkedin.helix.messaging.handling.HelixStateTransitionHandler

/**
* Copyright (C) 2012 LinkedIn Inc <opensource@linkedin.com>
*
* 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.linkedin.helix.messaging.handling;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;

import com.linkedin.helix.HelixDataAccessor;
import com.linkedin.helix.HelixException;
import com.linkedin.helix.HelixManager;
import com.linkedin.helix.NotificationContext;
import com.linkedin.helix.PropertyKey;
import com.linkedin.helix.PropertyKey.Builder;
import com.linkedin.helix.ZNRecordBucketizer;
import com.linkedin.helix.ZNRecordDelta;
import com.linkedin.helix.ZNRecordDelta.MergeOperation;
import com.linkedin.helix.model.CurrentState;
import com.linkedin.helix.model.Message;
import com.linkedin.helix.participant.statemachine.StateModel;
import com.linkedin.helix.participant.statemachine.StateModelParser;
import com.linkedin.helix.participant.statemachine.StateTransitionError;
import com.linkedin.helix.util.StatusUpdateUtil;

public class HelixStateTransitionHandler extends MessageHandler
{
  public static class HelixStateMismatchException extends Exception
  {
    public HelixStateMismatchException(String info)
    {
      super(info);
    }
  }
  private static Logger          logger     =
                                                Logger.getLogger(HelixStateTransitionHandler.class);
  private final StateModel       _stateModel;
  StatusUpdateUtil               _statusUpdateUtil;
  private final StateModelParser _transitionMethodFinder;
  private final CurrentState     _currentStateDelta;
  volatile boolean               _isTimeout = false;
  private final HelixTaskExecutor              _executor;

  public HelixStateTransitionHandler(StateModel stateModel,
                                     Message message,
                                     NotificationContext context,
                                     CurrentState currentStateDelta,
                                     HelixTaskExecutor executor)
  {
    super(message, context);
    _stateModel = stateModel;
    _statusUpdateUtil = new StatusUpdateUtil();
    _transitionMethodFinder = new StateModelParser();
    _currentStateDelta = currentStateDelta;
    _executor = executor;
  }

  private void prepareMessageExecution(HelixManager manager, Message message) throws HelixException,
      HelixStateMismatchException
  {
    if (!message.isValid())
    {
      String errorMessage =
          "Invalid Message, ensure that message: " + message
              + " has all the required fields: "
              + Arrays.toString(Message.Attributes.values());

      _statusUpdateUtil.logError(message,
                                 HelixStateTransitionHandler.class,
                                 errorMessage,
                                 manager.getHelixDataAccessor());
      logger.error(errorMessage);
      throw new HelixException(errorMessage);
    }
    // DataAccessor accessor = manager.getDataAccessor();
    HelixDataAccessor accessor = manager.getHelixDataAccessor();

    String partitionName = message.getPartitionName();
    String fromState = message.getFromState();

    // Verify the fromState and current state of the stateModel
    String state = _currentStateDelta.getState(partitionName);

    if (fromState != null && !fromState.equals("*") && !fromState.equalsIgnoreCase(state))
    {
      String errorMessage =
          "Current state of stateModel does not match the fromState in Message"
              + ", Current State:" + state + ", message expected:" + fromState
              + ", partition: " + partitionName + ", from: " + message.getMsgSrc()
              + ", to: " + message.getTgtName();

      _statusUpdateUtil.logError(message,
                                 HelixStateTransitionHandler.class,
                                 errorMessage,
                                 accessor);
      logger.error(errorMessage);
      throw new HelixStateMismatchException(errorMessage);
    }
  }

  void postExecutionMessage(HelixManager manager,
                            Message message,
                            NotificationContext context,
                            HelixTaskResult taskResult,
                            Exception exception)
  {
    String partitionKey = message.getPartitionName();
    String resource = message.getResourceName();
    String sessionId = message.getTgtSessionId();
    String instanceName = manager.getInstanceName();

    HelixDataAccessor accessor = manager.getHelixDataAccessor();
    Builder keyBuilder = accessor.keyBuilder();

    int bucketSize = message.getBucketSize();
    ZNRecordBucketizer bucketizer = new ZNRecordBucketizer(bucketSize);

    // Lock the helix manager so that the session id will not change when we update
    // the state model state. for zk current state it is OK as we have the per-session
    // current state node
    synchronized (manager)
    {
      if (!message.getTgtSessionId().equals(manager.getSessionId()))
      {
        logger.warn("Session id has changed. Skip postExecutionMessage. Old session "
            + message.getExecutionSessionId() + " , new session : "
            + manager.getSessionId());
        return;
      }

      if (taskResult.isSucess())
      {
        // String fromState = message.getFromState();
        String toState = message.getToState();
        _currentStateDelta.setState(partitionKey, toState);

        if (toState.equalsIgnoreCase("DROPPED"))
        {
          // for "OnOfflineToDROPPED" message, we need to remove the resource key record
          // from the current state of the instance because the resource key is dropped.
          // In the state model it will be stayed as "OFFLINE", which is OK.
          ZNRecordDelta delta =
              new ZNRecordDelta(_currentStateDelta.getRecord(), MergeOperation.SUBTRACT);
          // Don't subtract simple fields since they contain stateModelDefRef
          delta._record.getSimpleFields().clear();

          List<ZNRecordDelta> deltaList = new ArrayList<ZNRecordDelta>();
          deltaList.add(delta);
          _currentStateDelta.setDeltaList(deltaList);
        }
        else
        {
          // if the partition is not to be dropped, update _stateModel to the TO_STATE
          _stateModel.updateState(toState);
        }
      }
      else
      {
        if (exception instanceof HelixStateMismatchException)
        {
          // if fromState mismatch, set current state on zk to stateModel's current state
          logger.warn("Force CurrentState on Zk to be stateModel's CurrentState. partitionKey: "
              + partitionKey
              + ", currentState: "
              + _stateModel.getCurrentState()
              + ", message: " + message);
          _currentStateDelta.setState(partitionKey, _stateModel.getCurrentState());
        }
        else
        {
          StateTransitionError error =
              new StateTransitionError(ErrorType.INTERNAL, ErrorCode.ERROR, exception);
          if (exception instanceof InterruptedException)
          {
            if (_isTimeout)
            {
              error =
                  new StateTransitionError(ErrorType.INTERNAL,
                                           ErrorCode.TIMEOUT,
                                           exception);
            }
            else
            {
              // State transition interrupted but not caused by timeout. Keep the current
              // state in this case
              logger.error("State transition interrupted but not timeout. Not updating state. Partition : "
                  + message.getPartitionName() + " MsgId : " + message.getMsgId());
              return;
            }
          }
          _stateModel.rollbackOnError(message, context, error);
          _currentStateDelta.setState(partitionKey, "ERROR");
          _stateModel.updateState("ERROR");
        }
      }
    }
    try
    {
      // Update the ZK current state of the node
      PropertyKey key = keyBuilder.currentState(instanceName,
                              sessionId,
                              resource,
                              bucketizer.getBucketName(partitionKey));
      if (!_message.getGroupMessageMode())
      {
        accessor.updateProperty(key, _currentStateDelta);
      }
      else
      {
        _executor._groupMsgHandler.addCurStateUpdate(_message, key, _currentStateDelta);
      }
    }
    catch (Exception e)
    {
      logger.error("Error when updating the state ", e);
      StateTransitionError error =
          new StateTransitionError(ErrorType.FRAMEWORK, ErrorCode.ERROR, e);
      _stateModel.rollbackOnError(message, context, error);
      _statusUpdateUtil.logError(message,
                                 HelixStateTransitionHandler.class,
                                 e,
                                 "Error when update the state ",
                                 accessor);
    }
  }

  public HelixTaskResult handleMessageInternal(Message message,
                                               NotificationContext context)
  {
    synchronized (_stateModel)
    {
      HelixTaskResult taskResult = new HelixTaskResult();
      HelixManager manager = context.getManager();
      HelixDataAccessor accessor = manager.getHelixDataAccessor();

      _statusUpdateUtil.logInfo(message,
                                HelixStateTransitionHandler.class,
                                "Message handling task begin execute",
                                accessor);
      message.setExecuteStartTimeStamp(new Date().getTime());

      Exception exception = null;
      try
      {
        prepareMessageExecution(manager, message);
        invoke(accessor, context, taskResult, message);
      }
      catch (HelixStateMismatchException e)
      {
        // Simply log error and return from here if State mismatch.
        // The current state of the state model is intact.
        taskResult.setSuccess(false);
        taskResult.setMessage(e.toString());
        taskResult.setException(e);
        exception = e;
        // return taskResult;
      }
      catch (Exception e)
      {
        String errorMessage =
            "Exception while executing a state transition task "
                + message.getPartitionName();
        logger.error(errorMessage, e);
        if (e.getCause() != null && e.getCause() instanceof InterruptedException)
        {
          e = (InterruptedException) e.getCause();
        }
        _statusUpdateUtil.logError(message,
                                   HelixStateTransitionHandler.class,
                                   e,
                                   errorMessage,
                                   accessor);
        taskResult.setSuccess(false);
        taskResult.setMessage(e.toString());
        taskResult.setException(e);
        taskResult.setInterrupted(e instanceof InterruptedException);
        exception = e;
      }
      postExecutionMessage(manager, message, context, taskResult, exception);

      return taskResult;
    }
  }

  private void invoke(HelixDataAccessor accessor,
                      NotificationContext context,
                      HelixTaskResult taskResult,
                      Message message) throws IllegalAccessException,
      InvocationTargetException,
      InterruptedException
  {
    _statusUpdateUtil.logInfo(message,
                              HelixStateTransitionHandler.class,
                              "Message handling invoking",
                              accessor);

    // by default, we invoke state transition function in state model
    Method methodToInvoke = null;
    String fromState = message.getFromState();
    String toState = message.getToState();
    methodToInvoke =
        _transitionMethodFinder.getMethodForTransition(_stateModel.getClass(),
                                                       fromState,
                                                       toState,
                                                       new Class[] { Message.class,
                                                           NotificationContext.class });
    if (methodToInvoke != null)
    {
      methodToInvoke.invoke(_stateModel, new Object[] { message, context });
      taskResult.setSuccess(true);
    }
    else
    {
      String errorMessage =
          "Unable to find method for transition from " + fromState + " to " + toState
              + "in " + _stateModel.getClass();
      logger.error(errorMessage);
      taskResult.setSuccess(false);

      _statusUpdateUtil.logError(message,
                                 HelixStateTransitionHandler.class,
                                 errorMessage,
                                 accessor);
    }
  }

  @Override
  public HelixTaskResult handleMessage()
  {
    return handleMessageInternal(_message, _notificationContext);
  }

  @Override
  public void onError(Exception e, ErrorCode code, ErrorType type)
  {
    // All internal error has been processed already, so we can skip them
    if (type == ErrorType.INTERNAL)
    {
      logger.error("Skip internal error " + e.getMessage() + " " + code);
      return;
    }
    HelixManager manager = _notificationContext.getManager();
    HelixDataAccessor accessor = manager.getHelixDataAccessor();
    Builder keyBuilder = accessor.keyBuilder();

    String instanceName = manager.getInstanceName();
    String partition = _message.getPartitionName();
    String resourceName = _message.getResourceName();
    CurrentState currentStateDelta = new CurrentState(resourceName);

    StateTransitionError error = new StateTransitionError(type, code, e);
    _stateModel.rollbackOnError(_message, _notificationContext, error);
    // if the transition is not canceled, it should go into error state
    if (code == ErrorCode.ERROR)
    {
      currentStateDelta.setState(partition, "ERROR");
      _stateModel.updateState("ERROR");

      accessor.updateProperty(keyBuilder.currentState(instanceName,
                                                      _message.getTgtSessionId(),
                                                      resourceName),
                              currentStateDelta);
    }
  }

  @Override
  public void onTimeout()
  {
    _isTimeout = true;
  }
};
TOP

Related Classes of com.linkedin.helix.messaging.handling.HelixStateTransitionHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.