/** *Copyright [2010-2011] [dennis zhuang(killme2008@gmail.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.google.code.hs4j.network.hs; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Map; import java.util.Queue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.DelayQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import com.google.code.hs4j.Command; import com.google.code.hs4j.CommandFactory; import com.google.code.hs4j.HSClient; import com.google.code.hs4j.Protocol; import com.google.code.hs4j.exception.HandlerSocketException; import com.google.code.hs4j.impl.HSClientImpl; import com.google.code.hs4j.impl.IndexRecord; import com.google.code.hs4j.impl.ReconnectRequest; import com.google.code.hs4j.network.config.Configuration; import com.google.code.hs4j.network.core.Controller; import com.google.code.hs4j.network.core.ControllerStateListener; import com.google.code.hs4j.network.core.EventType; import com.google.code.hs4j.network.core.Session; import com.google.code.hs4j.network.core.WriteMessage; import com.google.code.hs4j.network.nio.NioSession; import com.google.code.hs4j.network.nio.NioSessionConfig; import com.google.code.hs4j.network.nio.impl.SocketChannelController; import com.google.code.hs4j.network.util.SystemUtils; /** * Connector implementation * * @author dennis */ public class HandlerSocketConnectorImpl extends SocketChannelController implements HandlerSocketConnector { private final DelayQueue<ReconnectRequest> waitingQueue = new DelayQueue<ReconnectRequest>(); private final ExecutorService openIndexExectur = Executors .newCachedThreadPool(); private volatile long healSessionInterval = 2000L; private final int connectionPoolSize; // session pool size protected Protocol protocol; private volatile boolean allowAutoReconnect = true; private final CommandFactory commandFactory; private final HSClientImpl hsClient; /** * When session was created,it must be added to sessionList,but before * that,the opening index must reopen on this session. * * @author dennis * @date 2010-11-28 */ private final class AddSessionTask implements Runnable { private final HandlerSocketSessionImpl session; private AddSessionTask(HandlerSocketSessionImpl session) { this.session = session; } public void run() { try { Map<Integer, IndexRecord> indexMap = HandlerSocketConnectorImpl.this.hsClient .getIndexMap(); if (!indexMap.isEmpty()) { for (IndexRecord record : indexMap.values()) { try { Command cmd = commandFactory .createOpenIndexCommand(String .valueOf(record.id), record.db, record.tableName, record.indexName, record.fieldList, record.filterFieldList); session.write(cmd); cmd.await(3000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { log .error( "Open index failure when connection was connected", e); } } } } finally { HandlerSocketConnectorImpl.this.addSession(this.session); } } } /** * Session monitor for healing sessions. * * @author dennis * */ class SessionMonitor extends Thread { public SessionMonitor() { this.setName("Heal-Session-Thread"); } @Override public void run() { while (HandlerSocketConnectorImpl.this.isStarted()) { ReconnectRequest request=null; InetSocketAddress address=null; try { request = HandlerSocketConnectorImpl.this.waitingQueue .take(); address = request.getRemoteAddr(); boolean connected = false; Future<Boolean> future = HandlerSocketConnectorImpl.this .connect(request.getRemoteAddr()); request.setTries(request.getTries() + 1); try { log.warn("Trying to connect to " + address.getAddress().getHostAddress() + ":" + address.getPort() + " for " + request.getTries() + " times"); if (!future.get(HSClient.DEFAULT_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)) { connected = false; } else { connected = true; break; } } catch (TimeoutException e) { future.cancel(true); } catch (ExecutionException e) { future.cancel(true); } finally { if (!connected) { // update timestamp for next reconnecting rescheduleConnectRequest(request, address); } else { continue; } } } catch (InterruptedException e) { // ignore,check status } catch (Exception e) { log.error("SessionMonitor connect error", e); rescheduleConnectRequest(request, address); } } } private void rescheduleConnectRequest(ReconnectRequest request, InetSocketAddress address) { if(request==null) return; request .updateNextReconnectTimeStamp(HandlerSocketConnectorImpl.this.healSessionInterval * request.getTries()); log.error("Reconnect to " + address.getAddress().getHostAddress() + ":" + address.getPort() + " fail"); // add to tail HandlerSocketConnectorImpl.this.waitingQueue .offer(request); } } /* * (non-Javadoc) * * @see * com.google.code.hs4j.network.hs.HandlerCOnnector#setHealSessionInterval * (long) */ public final void setHealSessionInterval(long healConnectionInterval) { this.healSessionInterval = healConnectionInterval; } /* * (non-Javadoc) * * @see * com.google.code.hs4j.network.hs.HandlerCOnnector#getHealSessionInterval() */ public long getHealSessionInterval() { return this.healSessionInterval; } /* * (non-Javadoc) * * @see com.google.code.hs4j.network.hs.HandlerCOnnector#getProtocol() */ public Protocol getProtocol() { return this.protocol; } protected final CopyOnWriteArrayList<Session> sessionList = new CopyOnWriteArrayList<Session>(); /* * (non-Javadoc) * * @see * com.google.code.hs4j.network.hs.HandlerCOnnector#addSession(com.google * .code.hs4j.network.core.Session) */ public void addSession(Session session) { InetSocketAddress remoteSocketAddress = session .getRemoteSocketAddress(); log.warn("Add a session: " + SystemUtils.getRawAddress(remoteSocketAddress) + ":" + remoteSocketAddress.getPort()); this.sessionList.add(session); } /* * (non-Javadoc) * * @see * com.google.code.hs4j.network.hs.HandlerCOnnector#removeSession(com.google * .code.hs4j.network.core.Session) */ public void removeSession(Session session) { InetSocketAddress remoteSocketAddress = session .getRemoteSocketAddress(); log.warn("Remove a session: " + SystemUtils.getRawAddress(remoteSocketAddress) + ":" + remoteSocketAddress.getPort()); this.sessionList.remove(session); } @Override protected void doStart() throws IOException { this.setLocalSocketAddress(new InetSocketAddress("localhost", 0)); } @Override public void onConnect(SelectionKey key) throws IOException { key.interestOps(key.interestOps() & ~SelectionKey.OP_CONNECT); ConnectFuture future = (ConnectFuture) key.attachment(); if (future == null || future.isCancelled()) { cancelKey(key); return; } try { if (!((SocketChannel) key.channel()).finishConnect()) { future.failure(new IOException("Connect to " + SystemUtils.getRawAddress(future.getRemoteAddr()) + ":" + future.getRemoteAddr().getPort() + " fail")); } else { key.attach(null); this.createSession((SocketChannel) key.channel()); future.setResult(Boolean.TRUE); } } catch (Exception e) { future.failure(e); cancelKey(key); throw new IOException("Connect to " + SystemUtils.getRawAddress(future.getRemoteAddr()) + ":" + future.getRemoteAddr().getPort() + " fail," + e.getMessage()); } } private void cancelKey(SelectionKey key) throws IOException { try { if (key.channel() != null) key.channel().close(); } finally { key.cancel(); } } protected HandlerSocketSessionImpl createSession(SocketChannel socketChannel) { final HandlerSocketSessionImpl session = (HandlerSocketSessionImpl) this .buildSession(socketChannel); this.selectorManager.registerSession(session, EventType.ENABLE_READ); session.start(); session.onEvent(EventType.CONNECTED, null); this.hsClient.notifyConnected(session); this.openIndexExectur.execute(new AddSessionTask(session)); return session; } /* * (non-Javadoc) * * @see * com.google.code.hs4j.network.hs.HandlerCOnnector#addToWatingQueue(com * .google.code.hs4j.impl.ReconnectRequest) */ public void addToWatingQueue(ReconnectRequest request) { this.waitingQueue.add(request); } /* * (non-Javadoc) * * @seecom.google.code.hs4j.network.hs.HandlerCOnnector#connect(java.net. * InetSocketAddress) */ public Future<Boolean> connect(InetSocketAddress remoteAddr) throws IOException { if (remoteAddr == null) { throw new NullPointerException("Null Address"); } SocketChannel socketChannel = SocketChannel.open(); this.configureSocketChannel(socketChannel); ConnectFuture future = new ConnectFuture(remoteAddr); try { if (!socketChannel.connect(remoteAddr)) { this.selectorManager.registerChannel(socketChannel, SelectionKey.OP_CONNECT, future); } else { this.createSession(socketChannel); future.setResult(true); } return future; } catch (IOException e) { if (socketChannel != null) { socketChannel.close(); } throw e; } } public void closeChannel(Selector selector) throws IOException { // do nothing } private final AtomicInteger sets = new AtomicInteger(); public Session selectSession() throws HandlerSocketException { Session session = this.sessionList.get(this.sets.incrementAndGet() % this.sessionList.size()); int retryCount = 0; while ((session == null || session.isClosed()) && retryCount++ < 6) { session = this.sessionList.get(this.sets.incrementAndGet() % this.sessionList.size()); } if (session == null || session.isClosed()) { throw new HandlerSocketException( "Could not find an open connection"); } return session; } /* * (non-Javadoc) * * @see com.google.code.hs4j.network.hs.HandlerCOnnector#getSessionList() */ public CopyOnWriteArrayList<Session> getSessionList() { return this.sessionList; } /* * (non-Javadoc) * * @see * com.google.code.hs4j.network.hs.HandlerCOnnector#send(com.google.code * .hs4j.Command) */ public void send(final Command msg) throws HandlerSocketException { Session session = this.selectSession(); session.write(msg); } /** * Inner state listenner,manage session monitor. * * @author boyan * */ class InnerControllerStateListener implements ControllerStateListener { private final SessionMonitor sessionMonitor = new SessionMonitor(); public void onAllSessionClosed(Controller controller) { } public void onException(Controller controller, Throwable t) { log.error("Exception occured in controller", t); } public void onReady(Controller controller) { this.sessionMonitor.start(); } public void onStarted(Controller controller) { } public void onStopped(Controller controller) { this.sessionMonitor.interrupt(); HandlerSocketConnectorImpl.this.openIndexExectur.shutdown(); } } /* * (non-Javadoc) * * @see * com.google.code.hs4j.network.hs.HandlerCOnnector#isAllowAutoReconnect() */ public boolean isAllowAutoReconnect() { return this.allowAutoReconnect; } /* * (non-Javadoc) * * @see * com.google.code.hs4j.network.hs.HandlerCOnnector#setAllowAutoReconnect * (boolean) */ public void setAllowAutoReconnect(boolean allowAutoReconnect) { this.allowAutoReconnect = allowAutoReconnect; } public HandlerSocketConnectorImpl(Configuration configuration, CommandFactory commandFactory, int poolSize, HSClientImpl hsClient) { super(configuration, null); this.protocol = commandFactory.getProtocol(); this.addStateListener(new InnerControllerStateListener()); this.connectionPoolSize = poolSize; this.soLingerOn = true; this.commandFactory = commandFactory; this.hsClient = hsClient; this .setSelectorPoolSize(2 * Runtime.getRuntime() .availableProcessors()); } @Override protected NioSession buildSession(SocketChannel sc) { Queue<WriteMessage> queue = this.buildQueue(); final NioSessionConfig sessionCofig = this .buildSessionConfig(sc, queue); HandlerSocketSessionImpl session = new HandlerSocketSessionImpl( sessionCofig, this.configuration.getSessionReadBufferSize(), this.getReadThreadCount(), this.commandFactory); session.setAllowReconnect(this.allowAutoReconnect); return session; } }