/* * Copyright 2014 NAVER Corp. * * 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.navercorp.pinpoint.rpc.client; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import com.navercorp.pinpoint.common.util.StringUtils; import com.navercorp.pinpoint.rpc.cluster.ClusterOption; import com.navercorp.pinpoint.rpc.cluster.Role; import com.navercorp.pinpoint.rpc.util.AssertUtils; import com.navercorp.pinpoint.rpc.util.ClassUtils; import com.navercorp.pinpoint.rpc.util.ControlMessageEncodingUtils; import com.navercorp.pinpoint.rpc.util.MapUtils; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.util.Timeout; import org.jboss.netty.util.Timer; import org.jboss.netty.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.navercorp.pinpoint.rpc.control.ProtocolException; import com.navercorp.pinpoint.rpc.packet.ControlHandshakePacket; import com.navercorp.pinpoint.rpc.packet.ControlHandshakeResponsePacket; import com.navercorp.pinpoint.rpc.packet.HandshakeResponseCode; public class PinpointClientHandshaker { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final ChannelFutureListener handShakeFailFutureListener = new WriteFailFutureListener(this.logger, "HandShakePacket write fail.", "HandShakePacket write success."); private static final int STATE_INIT = 0; private static final int STATE_STARTED = 1; private static final int STATE_FINISHED = 2; // STATE_INIT -> STATE_STARTED -> STATE_COMPLETED // STATE_INIT -> STATE_STARTED -> STATE_ABORTED private final AtomicInteger state; private final AtomicInteger handshakeCount; private final Timer handshakerTimer; private final int retryInterval; private final int maxHandshakeCount; private final Object lock = new Object(); private final AtomicReference<HandshakeResponseCode> handshakeResult = new AtomicReference<HandshakeResponseCode>(null); private final AtomicReference<ClusterOption> clusterOption = new AtomicReference<ClusterOption>(null); private String simpleName; public PinpointClientHandshaker(Timer handshakerTimer, int retryInterval, int maxHandshakeCount) { AssertUtils.assertNotNull(handshakerTimer, "handshakerTimer may not be null."); AssertUtils.assertTrue(retryInterval > 0, "retryInterval must greater than zero."); AssertUtils.assertTrue(maxHandshakeCount > 0, "maxHandshakeCount must greater than zero."); this.state = new AtomicInteger(STATE_INIT); this.handshakerTimer = handshakerTimer; this.retryInterval = retryInterval; this.maxHandshakeCount = maxHandshakeCount; this.handshakeCount = new AtomicInteger(0); } public void handshakeStart(Channel channel, Map<String, Object> handshakeData) { logger.info("{} handshakeStart() started. channel:{}", simpleClassNameAndHashCodeString(), channel); if (channel == null) { logger.warn("{} handshakeStart() failed. caused:channel may not be null.", simpleClassNameAndHashCodeString()); return; } if (!channel.isConnected()) { logger.warn("{} handshakeStart() failed. caused:channel is not connected.", simpleClassNameAndHashCodeString()); return; } if (!state.compareAndSet(STATE_INIT, STATE_STARTED)) { logger.warn("{} handshakeStart() failed. caused:unexpected state.", simpleClassNameAndHashCodeString()); return; } HandshakeJob handshakeJob = null; try { handshakeJob = createHandshakeJob(channel, handshakeData); } catch (Exception e) { logger.warn("{} create HandshakeJob failed. caused:{}", simpleClassNameAndHashCodeString(), e.getMessage(), e); } if (handshakeJob == null) { logger.warn("{} handshakeStart() failed. caused:handshakeJob may not be null.", simpleClassNameAndHashCodeString()); handshakeAbort(); return; } handshake(handshakeJob); reserveHandshake(handshakeJob); logger.info("{} handshakeStart() completed. channel:{}, data:{}", simpleClassNameAndHashCodeString(), channel, handshakeData); } private HandshakeJob createHandshakeJob(Channel channel, Map<String, Object> handshakeData) throws ProtocolException { byte[] payload = ControlMessageEncodingUtils.encode(handshakeData); ControlHandshakePacket handshakePacket = new ControlHandshakePacket(payload); HandshakeJob handshakeJob = new HandshakeJob(channel, handshakePacket); return handshakeJob; } private void handshake(HandshakeJob handshakeJob) { handshakeCount.incrementAndGet(); Channel channel = handshakeJob.getChannel(); ControlHandshakePacket packet = handshakeJob.getHandshakePacket(); logger.info("{} do handshake({}/{}). channel:{}.", simpleClassNameAndHashCodeString(), handshakeCount.get(), maxHandshakeCount, channel); final ChannelFuture future = channel.write(packet); future.addListener(handShakeFailFutureListener); } private void reserveHandshake(HandshakeJob handshake) { if (handshakeCount.get() >= maxHandshakeCount) { logger.warn("{} reserveHandshake() failed. caused:Retry count is over({}/{}).", simpleClassNameAndHashCodeString(), handshakeCount.get(), maxHandshakeCount); handshakeAbort(); return; } logger.debug("{} reserveHandshake() started.", simpleClassNameAndHashCodeString()); this.handshakerTimer.newTimeout(handshake, retryInterval, TimeUnit.MILLISECONDS); } public boolean handshakeComplete(ControlHandshakeResponsePacket responsePacket) { logger.info("{} handshakeComplete() started. responsePacket:{}", simpleClassNameAndHashCodeString(), responsePacket); synchronized (lock) { if (!this.state.compareAndSet(STATE_STARTED, STATE_FINISHED)) { // state can be 0 or 2. logger.info("{} handshakeComplete() failed. caused:unexpected state.", simpleClassNameAndHashCodeString()); this.state.set(STATE_FINISHED); return false; } Map handshakeResponse = decode(responsePacket); HandshakeResponseCode code = getResponseCode(handshakeResponse); handshakeResult.compareAndSet(null, code); ClusterOption clusterOption = getClusterOption(handshakeResponse); this.clusterOption.compareAndSet(null, clusterOption); logger.info("{} handshakeComplete() completed. handshake-response:{}.", simpleClassNameAndHashCodeString(), handshakeResponse); return true; } } private Map decode(ControlHandshakeResponsePacket message) { byte[] payload = message.getPayload(); if (payload == null) { return Collections.EMPTY_MAP; } try { Map result = (Map) ControlMessageEncodingUtils.decode(payload); return result; } catch (ProtocolException e) { } return Collections.EMPTY_MAP; } private HandshakeResponseCode getResponseCode(Map handshakeResponse) { if (handshakeResponse == Collections.EMPTY_MAP) { return HandshakeResponseCode.PROTOCOL_ERROR; } int code = MapUtils.getInteger(handshakeResponse, ControlHandshakeResponsePacket.CODE, -1); int subCode = MapUtils.getInteger(handshakeResponse, ControlHandshakeResponsePacket.SUB_CODE, -1); return HandshakeResponseCode.getValue(code, subCode); } private ClusterOption getClusterOption(Map handshakeResponse) { if (handshakeResponse == Collections.EMPTY_MAP) { return ClusterOption.DISABLE_CLUSTER_OPTION; } Map cluster = (Map) handshakeResponse.get(ControlHandshakeResponsePacket.CLUSTER); if (cluster == null) { return ClusterOption.DISABLE_CLUSTER_OPTION; } String id = MapUtils.getString(cluster, "id", ""); List<Role> roles = getRoles((List) cluster.get("roles")); if (StringUtils.isEmpty(id)) { return ClusterOption.DISABLE_CLUSTER_OPTION; } else { return new ClusterOption(true, id, roles); } } private List<Role> getRoles(List roleNames) { List<Role> roles = new ArrayList<Role>(); for (Object roleName : roleNames) { if (roleName instanceof String && StringUtils.isNotEmpty((String) roleName)) { roles.add(Role.getValue((String) roleName)); } } return roles; } public HandshakeResponseCode getHandshakeResult() { return handshakeResult.get(); } public ClusterOption getClusterOption() { return clusterOption.get(); } public void handshakeAbort() { logger.info("{} handshakeAbort() started.", simpleClassNameAndHashCodeString()); if (!state.compareAndSet(STATE_STARTED, STATE_FINISHED)) { // state can be 0 or 2. logger.info("{} unexpected state", simpleClassNameAndHashCodeString()); this.state.set(STATE_FINISHED); return; } logger.info("{} handshakeAbort() completed.", simpleClassNameAndHashCodeString()); } public boolean isRun() { int currentState = currentState(); return isRun(currentState); } private boolean isRun(int currentState) { if (currentState == STATE_STARTED) { return true; } else { return false; } } public boolean isFinished() { int currentState = currentState(); return isFinished(currentState); } private boolean isFinished(int currentState) { return currentState == STATE_FINISHED; } private int currentState() { synchronized (lock) { return this.state.get(); } } private String simpleClassNameAndHashCodeString() { if (simpleName == null) { simpleName = ClassUtils.simpleClassNameAndHashCodeString(this); } return simpleName; } private class HandshakeJob implements TimerTask { private final Channel channel; private final ControlHandshakePacket handshakePacket; public HandshakeJob(Channel channel, ControlHandshakePacket handshakePacket) { this.channel = channel; this.handshakePacket = handshakePacket; } @Override public void run(Timeout timeout) throws Exception { logger.debug("{} HandshakeJob started.", simpleClassNameAndHashCodeString()); if (timeout.isCancelled()) { reserveHandshake(this); return; } int currentState = currentState(); if (isRun(currentState)) { handshake(this); reserveHandshake(this); } else if (isFinished(currentState)) { logger.info("{} HandshakeJob completed.", simpleClassNameAndHashCodeString()); } else { logger.warn("{} HandshakeJob will be stop. caused:unexpected state.", simpleClassNameAndHashCodeString()); } } public Channel getChannel() { return channel; } public ControlHandshakePacket getHandshakePacket() { return handshakePacket; } } }