/* * 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.profiler.sender; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.thrift.TBase; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.util.HashedWheelTimer; 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.Future; import com.navercorp.pinpoint.rpc.FutureListener; import com.navercorp.pinpoint.rpc.ResponseMessage; import com.navercorp.pinpoint.rpc.client.PinpointClient; import com.navercorp.pinpoint.rpc.client.PinpointClientReconnectEventListener; import com.navercorp.pinpoint.rpc.util.TimerFactory; import com.navercorp.pinpoint.thrift.dto.TResult; import com.navercorp.pinpoint.thrift.io.HeaderTBaseDeserializer; import com.navercorp.pinpoint.thrift.io.HeaderTBaseDeserializerFactory; import com.navercorp.pinpoint.thrift.io.HeaderTBaseSerializer; import com.navercorp.pinpoint.thrift.io.HeaderTBaseSerializerFactory; /** * @author emeroad * @author koo.taejin * @author netspider */ public class TcpDataSender extends AbstractDataSender implements EnhancedDataSender { private final Logger logger = LoggerFactory.getLogger(this.getClass()); static { // preClassLoad ChannelBuffers.buffer(2); } private final PinpointClient client; private final Timer timer; private final AtomicBoolean fireState = new AtomicBoolean(false); private final WriteFailFutureListener writeFailFutureListener; private final HeaderTBaseSerializer serializer; private final RetryQueue retryQueue = new RetryQueue(); private AsyncQueueingExecutor<Object> executor; public TcpDataSender(PinpointClient client) { this(client, HeaderTBaseSerializerFactory.DEFAULT_FACTORY.createSerializer()); } public TcpDataSender(PinpointClient client, HeaderTBaseSerializer serializer) { this.client = client; this.serializer = serializer; this.timer = createTimer(); writeFailFutureListener = new WriteFailFutureListener(logger, "io write fail.", "host", -1); this.executor = createAsyncQueueingExecutor(1024 * 5, "Pinpoint-TcpDataExecutor"); } private Timer createTimer() { HashedWheelTimer timer = TimerFactory.createHashedWheelTimer("Pinpoint-DataSender-Timer", 100, TimeUnit.MILLISECONDS, 512); timer.start(); return timer; } @Override public boolean send(TBase<?, ?> data) { return executor.execute(data); } @Override public boolean request(TBase<?, ?> data) { return this.request(data, 3); } @Override public boolean request(TBase<?, ?> data, int retryCount) { RequestMarker message = new RequestMarker(data, retryCount); return executor.execute(message); } @Override public boolean request(TBase<?, ?> data, FutureListener<ResponseMessage> listener) { RequestMarker message = new RequestMarker(data, listener); return executor.execute(message); } @Override public boolean addReconnectEventListener(PinpointClientReconnectEventListener eventListener) { return this.client.addPinpointClientReconnectEventListener(eventListener); } @Override public boolean removeReconnectEventListener(PinpointClientReconnectEventListener eventListener) { return this.client.removePinpointClientReconnectEventListener(eventListener); } @Override public void stop() { executor.stop(); Set<Timeout> stop = timer.stop(); if (!stop.isEmpty()) { logger.info("stop Timeout:{}", stop.size()); } } @Override protected void sendPacket(Object message) { try { if (message instanceof TBase) { byte[] copy = serialize(serializer, (TBase) message); if (copy == null) { return; } doSend(copy); } else if (message instanceof RequestMarker) { RequestMarker requestMarker = (RequestMarker) message; TBase tBase = requestMarker.getTBase(); int retryCount = requestMarker.getRetryCount(); FutureListener futureListener = requestMarker.getFutureListener(); byte[] copy = serialize(serializer, tBase); if (copy == null) { return; } if (futureListener != null) { doRequest(copy, futureListener); } else { doRequest(copy, retryCount, tBase); } } else { logger.error("sendPacket fail. invalid dto type:{}", message.getClass()); return; } } catch (Exception e) { logger.warn("tcp send fail. Caused:{}", e.getMessage(), e); } } private void doSend(byte[] copy) { Future write = this.client.sendAsync(copy); write.setListener(writeFailFutureListener); } // Separate doRequest method to avoid creating unnecessary objects. (Generally, sending message is successed when firt attempt.) private void doRequest(final byte[] requestPacket, final int maxRetryCount, final Object targetClass) { FutureListener futureListener = (new FutureListener<ResponseMessage>() { @Override public void onComplete(Future<ResponseMessage> future) { if (future.isSuccess()) { // Should cache? HeaderTBaseDeserializer deserializer = HeaderTBaseDeserializerFactory.DEFAULT_FACTORY.createDeserializer(); TBase<?, ?> response = deserialize(deserializer, future.getResult()); if (response instanceof TResult) { TResult result = (TResult) response; if (result.isSuccess()) { logger.debug("result success"); } else { logger.info("request fail. request:{} Caused:{}", targetClass, result.getMessage()); RetryMessage retryMessage = new RetryMessage(1, maxRetryCount, requestPacket, targetClass.getClass().getSimpleName()); retryRequest(retryMessage); } } else { logger.warn("Invalid respose:{}", response); // This is not retransmission. need to log for debugging // it could be null // retryRequest(requestPacket); } } else { logger.info("request fail. request:{} Caused:{}", targetClass, future.getCause().getMessage(), future.getCause()); RetryMessage retryMessage = new RetryMessage(1, maxRetryCount, requestPacket, targetClass.getClass().getSimpleName()); retryRequest(retryMessage); } } }); doRequest(requestPacket, futureListener); } // Separate doRequest method to avoid creating unnecessary objects. (Generally, sending message is successed when firt attempt.) private void doRequest(final RetryMessage retryMessage) { FutureListener futureListener = (new FutureListener<ResponseMessage>() { @Override public void onComplete(Future<ResponseMessage> future) { if (future.isSuccess()) { // Should cache? HeaderTBaseDeserializer deserializer = HeaderTBaseDeserializerFactory.DEFAULT_FACTORY.createDeserializer(); TBase<?, ?> response = deserialize(deserializer, future.getResult()); if (response instanceof TResult) { TResult result = (TResult) response; if (result.isSuccess()) { logger.debug("result success"); } else { logger.info("request fail. request:{}, Caused:{}", retryMessage, result.getMessage()); retryRequest(retryMessage); } } else { logger.warn("Invalid response:{}", response); // This is not retransmission. need to log for debugging // it could be null // retryRequest(requestPacket); } } else { logger.info("request fail. request:{}, caused:{}", retryMessage, future.getCause().getMessage(), future.getCause()); retryRequest(retryMessage); } } }); doRequest(retryMessage.getBytes(), futureListener); } private void retryRequest(RetryMessage retryMessage) { retryQueue.add(retryMessage); if (fireTimeout()) { timer.newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { while(true) { RetryMessage retryMessage = retryQueue.get(); if (retryMessage == null) { // Maybe concurrency issue. But ignore it because it's unlikely. fireComplete(); return; } int fail = retryMessage.fail(); doRequest(retryMessage); } } }, 1000 * 10, TimeUnit.MILLISECONDS); } } private void doRequest(final byte[] requestPacket, FutureListener futureListener) { final Future<ResponseMessage> response = this.client.request(requestPacket); response.setListener(futureListener); } private boolean fireTimeout() { if (fireState.compareAndSet(false, true)) { return true; } else { return false; } } private void fireComplete() { logger.debug("fireComplete"); fireState.compareAndSet(true, false); } }