/*
* 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.collector.cluster.zookeeper;
import com.navercorp.pinpoint.collector.cluster.connection.ClusterConnectionManager;
import com.navercorp.pinpoint.collector.cluster.connection.CollectorClusterConnectionManager;
import com.navercorp.pinpoint.collector.cluster.zookeeper.exception.ConnectionException;
import com.navercorp.pinpoint.common.util.NetUtils;
import com.navercorp.pinpoint.common.util.PinpointThreadFactory;
import com.navercorp.pinpoint.common.server.util.concurrent.CommonState;
import com.navercorp.pinpoint.common.server.util.concurrent.CommonStateContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author koo.taejin
* @author minwoo.jung
*/
public class ZookeeperClusterManager {
// it is okay for the collector to retry indefinitely, as long as RETRY_INTERVAL is set reasonably
private static final int DEFAULT_RETRY_INTERVAL = 60000;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final GetAndRegisterTask getAndRegisterTask = new GetAndRegisterTask();
private final StopTask stopTask = new StopTask();
private final ZookeeperClient client;
private final ClusterConnectionManager clusterConnectionManager;
private final String zNodePath;
private final AtomicBoolean retryMode = new AtomicBoolean(false);
private final BlockingQueue<Task> queue = new LinkedBlockingQueue<>(1);
private final CommonStateContext workerState;
private final Thread workerThread;
// private final Timer timer;
// Register Worker + Job
// synchronize current status with Zookeeper when an event(job) is triggered.
// (the number of events does not matter as long as a single event is triggered - subsequent events may be ignored)
public ZookeeperClusterManager(ZookeeperClient client, String zookeeperClusterPath, ClusterConnectionManager clusterConnectionManager) {
this.client = client;
this.clusterConnectionManager = clusterConnectionManager;
this.zNodePath = zookeeperClusterPath;
this.workerState = new CommonStateContext();
final ThreadFactory threadFactory = new PinpointThreadFactory(this.getClass().getSimpleName(), true);
this.workerThread = threadFactory.newThread(new Worker());
}
public void start() {
switch (this.workerState.getCurrentState()) {
case NEW:
if (this.workerState.changeStateInitializing()) {
logger.info("{} initialization started.", this.getClass().getSimpleName());
this.workerThread.start();
workerState.changeStateStarted();
logger.info("{} initialization completed.", this.getClass().getSimpleName());
if (clusterConnectionManager != null) {
clusterConnectionManager.start();
}
break;
}
case INITIALIZING:
logger.info("{} already initializing.", this.getClass().getSimpleName());
break;
case STARTED:
logger.info("{} already started.", this.getClass().getSimpleName());
break;
case DESTROYING:
throw new IllegalStateException("Already destroying.");
case STOPPED:
throw new IllegalStateException("Already stopped.");
case ILLEGAL_STATE:
throw new IllegalStateException("Invalid State.");
}
}
public void stop() {
if (!(this.workerState.changeStateDestroying())) {
CommonState state = this.workerState.getCurrentState();
logger.info("{} already {}.", this.getClass().getSimpleName(), state.toString());
return;
}
logger.info("{} destroying started.", this.getClass().getSimpleName());
if (clusterConnectionManager != null) {
clusterConnectionManager.stop();
}
final boolean stopOffer = queue.offer(stopTask);
if (!stopOffer) {
logger.warn("Insert stopTask failed.");
}
boolean interrupted = false;
while (this.workerThread.isAlive()) {
this.workerThread.interrupt();
try {
this.workerThread.join(100L);
} catch (InterruptedException e) {
interrupted = true;
}
}
this.workerState.changeStateStopped();
logger.info("{} destroying completed.", this.getClass().getSimpleName());
}
public void handleAndRegisterWatcher(String path) {
if (workerState.isStarted()) {
if (zNodePath.equals(path)) {
final boolean offerSuccess = queue.offer(getAndRegisterTask);
if (!offerSuccess) {
logger.info("Message Queue is Full.");
}
} else {
logger.info("Invalid Path {}.", path);
}
} else {
CommonState state = this.workerState.getCurrentState();
logger.info("{} invalid state {}.", this.getClass().getSimpleName(), state.toString());
}
}
private class Worker implements Runnable {
@Override
public void run() {
// if the node does not exist, create a node and retry.
// retry on timeout as well.
while (workerState.isStarted()) {
Task task = null;
try {
task = queue.poll(DEFAULT_RETRY_INTERVAL, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
logger.debug(e.getMessage(), e);
}
if (!workerState.isStarted()) {
break;
}
if (task == null) {
if (retryMode.get()) {
boolean success = getAndRegisterTask.handleAndRegisterWatcher0();
if (success) {
retryMode.compareAndSet(true, false);
}
}
} else if (task instanceof GetAndRegisterTask) {
boolean success = ((GetAndRegisterTask) task).handleAndRegisterWatcher0();
if (!success) {
retryMode.compareAndSet(false, true);
}
} else if (task instanceof StopTask) {
break;
}
}
logger.info("{} stopped", this.getClass().getSimpleName());
}
}
interface Task {
}
@SuppressWarnings("SuspiciousMethodCalls")
class GetAndRegisterTask implements Task {
@SuppressWarnings("SuspiciousMethodCalls")
private boolean handleAndRegisterWatcher0() {
boolean needNotRetry = false;
try {
if (!client.exists(zNodePath)) {
client.createPath(zNodePath, true);
}
List<String> childNodeList = client.getChildrenNode(zNodePath, true);
List<InetSocketAddress> clusterAddressList = NetUtils.toInetSocketAddressLIst(childNodeList);
List<SocketAddress> addressList = clusterConnectionManager.getConnectedAddressList();
logger.info("Handle register and remove Task. Current Address List = {}, Cluster Address List = {}", addressList, clusterAddressList);
for (InetSocketAddress clusterAddress : clusterAddressList) {
if (!addressList.contains(clusterAddress)) {
clusterConnectionManager.connectPointIfAbsent(clusterAddress);
}
}
for (SocketAddress address : addressList) {
//noinspection SuspiciousMethodCalls,SuspiciousMethodCalls
if (!clusterAddressList.contains(address)) {
clusterConnectionManager.disconnectPoint(address);
}
}
needNotRetry = true;
return needNotRetry;
} catch (Exception e) {
if (!(e instanceof ConnectionException)) {
needNotRetry = true;
}
}
return needNotRetry;
}
}
static class StopTask implements Task {
}
}