/* * Tigase Jabber/XMPP Server * Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. Look for COPYING file in the top folder. * If not, see http://www.gnu.org/licenses/. * * $Rev$ * Last modified by $Author$ * $Date$ */ package tigase.server; import tigase.annotations.TODO; import tigase.net.ConnectionOpenListener; import tigase.net.ConnectionOpenThread; import tigase.net.ConnectionType; import tigase.net.SocketThread; import tigase.net.SocketType; import tigase.server.script.CommandIfc; import tigase.stats.StatisticsList; import tigase.util.DataTypes; import tigase.xmpp.JID; import tigase.xmpp.XMPPIOService; import tigase.xmpp.XMPPIOServiceListener; import java.io.IOException; import java.net.SocketException; import java.nio.channels.SocketChannel; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.script.Bindings; /** * Describe class ConnectionManager here. * * * Created: Sun Jan 22 22:52:58 2006 * * @param <IO> * @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a> * @version $Rev$ */ public abstract class ConnectionManager<IO extends XMPPIOService<?>> extends AbstractMessageReceiver implements XMPPIOServiceListener<IO> { private static final Logger log = Logger.getLogger(ConnectionManager.class.getName()); /** Field description */ public static final String NET_BUFFER_ST_PROP_KEY = "--net-buff-standard"; /** Field description */ public static final String NET_BUFFER_HT_PROP_KEY = "--net-buff-high-throughput"; protected static final String PORT_KEY = "port-no"; protected static final String PROP_KEY = "connections/"; protected static final String PORTS_PROP_KEY = PROP_KEY + "ports"; protected static final String PORT_TYPE_PROP_KEY = "type"; protected static final String PORT_SOCKET_PROP_KEY = "socket"; protected static final String PORT_IFC_PROP_KEY = "ifc"; protected static final String PORT_CLASS_PROP_KEY = "class"; protected static final String PORT_REMOTE_HOST_PROP_KEY = "remote-host"; protected static final String PORT_REMOTE_HOST_PROP_VAL = "localhost"; protected static final String TLS_PROP_KEY = PROP_KEY + "tls/"; protected static final String TLS_USE_PROP_KEY = TLS_PROP_KEY + "use"; protected static final boolean TLS_USE_PROP_VAL = true; protected static final String TLS_REQUIRED_PROP_KEY = TLS_PROP_KEY + "required"; protected static final boolean TLS_REQUIRED_PROP_VAL = false; protected static final String MAX_RECONNECTS_PROP_KEY = "max-reconnects"; protected static final String NET_BUFFER_PROP_KEY = "net-buffer"; protected static final int NET_BUFFER_ST_PROP_VAL = 2 * 1024; protected static final int NET_BUFFER_HT_PROP_VAL = 64 * 1024; /** Field description */ public static final String PORT_LOCAL_HOST_PROP_KEY = "local-host"; private static ConnectionOpenThread connectThread = ConnectionOpenThread.getInstance(); /** Field description */ public String[] PORT_IFC_PROP_VAL = { "*" }; private long bytesReceived = 0; private long bytesSent = 0; private int services_size = 0; private long socketOverflow = 0; private Thread watchdog = null; private long watchdogRuns = 0; private long watchdogStopped = 0; private long watchdogTests = 0; private LinkedList<Map<String, Object>> waitingTasks = new LinkedList<Map<String, Object>>(); private ConcurrentHashMap<String, IO> services = new ConcurrentHashMap<String, IO>(); private Set<ConnectionListenerImpl> pending_open = Collections .synchronizedSet(new HashSet<ConnectionListenerImpl>());; protected int net_buffer = NET_BUFFER_ST_PROP_VAL; private IOServiceStatisticsGetter ioStatsGetter = new IOServiceStatisticsGetter(); private boolean initializationCompleted = false; protected long connectionDelay = 2 * SECOND; /** * Method description * * * @param serv * * @return */ public abstract Queue<Packet> processSocketData(IO serv); /** * Method description * * * @param port_props */ public abstract void reconnectionFailed(Map<String, Object> port_props); protected abstract long getMaxInactiveTime(); protected abstract IO getXMPPIOServiceInstance(); /** * Method description * */ @Override public synchronized void everyMinute() { super.everyMinute(); // This variable used to provide statistics gets off on a busy // services as it is handled in methods called concurrently by // many threads. While accuracy of this variable is not critical // for the server functions, statistics should be as accurate as // possible to provide valuable metrics data. // So in the watchdog thread we re-synchronize this number int tmp = services.size(); services_size = tmp; doForAllServices(ioStatsGetter); } /** * Method description * * * @param params * * @return */ @Override public Map<String, Object> getDefaults(Map<String, Object> params) { log.log(Level.CONFIG, "{0} defaults: {1}", new Object[] { getName(), params.toString() }); Map<String, Object> props = super.getDefaults(params); props.put(TLS_USE_PROP_KEY, TLS_USE_PROP_VAL); int buffSize = NET_BUFFER_ST_PROP_VAL; if (isHighThroughput()) { buffSize = DataTypes.parseSizeInt((String) params.get(NET_BUFFER_HT_PROP_KEY), NET_BUFFER_HT_PROP_VAL); } else { buffSize = DataTypes.parseSizeInt((String) params.get(NET_BUFFER_ST_PROP_KEY), NET_BUFFER_ST_PROP_VAL); } props.put(NET_BUFFER_PROP_KEY, buffSize); int[] ports = null; String ports_str = (String) params.get("--" + getName() + "-ports"); if (ports_str != null) { String[] ports_stra = ports_str.split(","); ports = new int[ports_stra.length]; int k = 0; for (String p : ports_stra) { try { ports[k++] = Integer.parseInt(p); } catch (Exception e) { log.warning("Incorrect ports default settings: " + p); } } } int ports_size = 0; if (ports != null) { log.config("Port settings preset: " + Arrays.toString(ports)); for (int port : ports) { putDefPortParams(props, port, SocketType.plain); } // end of for (int i = 0; i < idx; i++) props.put(PORTS_PROP_KEY, ports); } else { int[] plains = getDefPlainPorts(); if (plains != null) { ports_size += plains.length; } // end of if (plains != null) int[] ssls = getDefSSLPorts(); if (ssls != null) { ports_size += ssls.length; } // end of if (ssls != null) if (ports_size > 0) { ports = new int[ports_size]; } // end of if (ports_size > 0) if (ports != null) { int idx = 0; if (plains != null) { idx = plains.length; for (int i = 0; i < idx; i++) { ports[i] = plains[i]; putDefPortParams(props, ports[i], SocketType.plain); } // end of for (int i = 0; i < idx; i++) } // end of if (plains != null) if (ssls != null) { for (int i = idx; i < idx + ssls.length; i++) { ports[i] = ssls[i - idx]; putDefPortParams(props, ports[i], SocketType.ssl); } // end of for (int i = 0; i < idx + ssls.length; i++) } // end of if (ssls != null) props.put(PORTS_PROP_KEY, ports); } // end of if (ports != null) } return props; } /** * Generates the component statistics. * * @param list * is a collection to put the component statistics in. */ @Override public void getStatistics(StatisticsList list) { super.getStatistics(list); list.add(getName(), "Open connections", services_size, Level.INFO); if (list.checkLevel(Level.FINEST) || services.size() < 1000) { int waitingToSendSize = 0; for (IO serv : services.values()) { waitingToSendSize += serv.waitingToSendSize(); } list.add(getName(), "Waiting to send", waitingToSendSize, Level.FINE); } list.add(getName(), "Bytes sent", bytesSent, Level.FINE); list.add(getName(), "Bytes received", bytesReceived, Level.FINE); list.add(getName(), "Socket overflow", socketOverflow, Level.FINE); list.add(getName(), "Watchdog runs", watchdogRuns, Level.FINER); list.add(getName(), "Watchdog tests", watchdogTests, Level.FINE); list.add(getName(), "Watchdog stopped", watchdogStopped, Level.FINE); } /** * This method can be overwritten in extending classes to get a different * packets distribution to different threads. For PubSub, probably better * packets distribution to different threads would be based on the sender * address rather then destination address. * * @param packet * @return */ @Override public int hashCodeForPacket(Packet packet) { if (packet.getStanzaTo() != null) { return packet.getStanzaTo().hashCode(); } if (packet.getTo() != null) { return packet.getTo().hashCode(); } return super.hashCodeForPacket(packet); } /** * Method description * * * @param binds */ @Override public void initBindings(Bindings binds) { super.initBindings(binds); binds.put(CommandIfc.SERVICES_MAP, services); } /** * Method description * */ @Override public void initializationCompleted() { super.initializationCompleted(); initializationCompleted = true; for (Map<String, Object> params : waitingTasks) { reconnectService(params, connectionDelay); } waitingTasks.clear(); } /** * Method description * * * @param serv * * @throws IOException */ @Override public void packetsReady(IO serv) throws IOException { // Under a high load data, especially lots of packets on a single // connection it may happen that one threads started processing // socketData and then another thread reads more packets which // may take over earlier data depending on a thread scheduler used. // synchronized (serv) { writePacketsToSocket(serv, processSocketData(serv)); // } } /** * Method description * * * @param packet */ @Override public void processPacket(Packet packet) { writePacketToSocket(packet); } /** * Method description * * * @return */ @Override public int processingInThreads() { return Runtime.getRuntime().availableProcessors() * 4; } @Override public int processingOutThreads() { return Runtime.getRuntime().availableProcessors() * 4; } /** * Method description * */ @Override public void release() { // delayedTasks.cancel(); releaseListeners(); super.release(); } /** * Method description * * * @param service */ @TODO(note = "Do something if service with the same unique ID is already started, " + "possibly kill the old one...") public void serviceStarted(final IO service) { // synchronized(services) { String id = getUniqueId(service); if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, "[[{0}]] Connection started: {1}", new Object[] { getName(), service }); } IO serv = services.get(id); if (serv != null) { if (serv == service) { log.log(Level.WARNING, "{0}: That would explain a lot, adding the same service twice, ID: {1}", new Object[] { getName(), serv }); } else { // Is it at all possible to happen??? // let's log it for now.... log.log(Level.WARNING, "{0}: Attempt to add different service with the same ID: {1}", new Object[] { getName(), service }); // And stop the old service.... serv.stop(); } } services.put(id, service); ++services_size; // } } /** * * @param service * @return */ @Override public boolean serviceStopped(IO service) { // Hopefuly there is no exception at this point, but just in case... // This is a very fresh code after all try { ioStatsGetter.check(service); } catch (Exception e) { log.log(Level.INFO, "Nothing serious to worry about but please notify the developer.", e); } // synchronized(service) { String id = getUniqueId(service); if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, "[[{0}]] Connection stopped: {1}", new Object[] { getName(), service }); } // id might be null if service is stopped in accept method due to // an exception during establishing TCP/IP connection // IO serv = (id != null ? services.get(id) : null); if (id != null) { boolean result = services.remove(id, service); if (result) { --services_size; } else if (log.isLoggable(Level.FINER)) { // Is it at all possible to happen??? // let's log it for now.... log.log(Level.FINER, "[[{0}]] Attempt to stop incorrect service: {1}", new Object[] { getName(), service }); } return result; } return false; // } } /** * Method description * * * @param name */ @Override public void setName(String name) { super.setName(name); watchdog = new Thread(new Watchdog(), "Watchdog - " + name); watchdog.setDaemon(true); watchdog.start(); } /** * Method description * * * @param props */ @Override public void setProperties(Map<String, Object> props) { super.setProperties(props); if (props.get(NET_BUFFER_PROP_KEY) != null) { net_buffer = (Integer) props.get(NET_BUFFER_PROP_KEY); } if (props.size() == 1) { // If props.size() == 1, it means this is a single property update and // ConnectionManager does not support it yet. return; } releaseListeners(); int[] ports = (int[]) props.get(PORTS_PROP_KEY); if (ports != null) { for (int i = 0; i < ports.length; i++) { Map<String, Object> port_props = new LinkedHashMap<String, Object>(20); for (Map.Entry<String, Object> entry : props.entrySet()) { if (entry.getKey().startsWith(PROP_KEY + ports[i])) { int idx = entry.getKey().lastIndexOf('/'); String key = entry.getKey().substring(idx + 1); log.log(Level.CONFIG, "Adding port property key: {0}={1}", new Object[] { key, entry.getValue() }); port_props.put(key, entry.getValue()); } // end of if (entry.getKey().startsWith()) } // end of for () port_props.put(PORT_KEY, ports[i]); addWaitingTask(port_props); // reconnectService(port_props, startDelay); } // end of for (int i = 0; i < ports.length; i++) } // end of if (ports != null) } /** * Method description * * * @param ios * @param p * * @return */ public boolean writePacketToSocket(IO ios, Packet p) { if (ios != null) { if (log.isLoggable(Level.FINER) && !log.isLoggable(Level.FINEST)) { log.log(Level.FINER, "{0}, Processing packet: {1}, type: {2}", new Object[] { ios, p.getElemName(), p.getType() }); } if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "{0}, Writing packet: {1}", new Object[] { ios, p }); } // synchronized (ios) { ios.addPacketToSend(p); if (ios.writeInProgress.tryLock()) { try { ios.processWaitingPackets(); SocketThread.addSocketService(ios); return true; } catch (Exception e) { log.log(Level.WARNING, ios + "Exception during writing packets: ", e); try { ios.stop(); } catch (Exception e1) { log.log(Level.WARNING, ios + "Exception stopping XMPPIOService: ", e1); } // end of try-catch } finally { ios.writeInProgress.unlock(); } } // } } else { if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "Can''t find service for packet: <{0}> {1}, service id: {2}", new Object[] { p.getElemName(), p.getTo(), getServiceId(p) }); } } // end of if (ios != null) else return false; } /** * Method description * * * @param serv * @param packets */ public void writePacketsToSocket(IO serv, Queue<Packet> packets) { if (serv != null) { // synchronized (serv) { if ((packets != null) && (packets.size() > 0)) { Packet p = null; while ((p = packets.poll()) != null) { if (log.isLoggable(Level.FINER) && !log.isLoggable(Level.FINEST)) { log.log(Level.FINER, "{0}, Processing packet: {1}, type: {2}", new Object[] { serv, p.getElemName(), p.getType() }); } if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "{0}, Writing packet: {1}", new Object[] { serv, p }); } serv.addPacketToSend(p); } // end of for () try { serv.processWaitingPackets(); SocketThread.addSocketService(serv); } catch (Exception e) { log.log(Level.WARNING, serv + "Exception during writing packets: ", e); try { serv.stop(); } catch (Exception e1) { log.log(Level.WARNING, serv + "Exception stopping XMPPIOService: ", e1); } // end of try-catch } // end of try-catch } // } } else { if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "Can't find service for packets: [{0}] ", packets); } } // end of if (ios != null) else } protected void addWaitingTask(Map<String, Object> conn) { if (initializationCompleted) { reconnectService(conn, connectionDelay); } else { waitingTasks.add(conn); } } /** * Returns number of active network connections (IOServices). * * @return number of active network connections (IOServices). */ protected int countIOServices() { return services.size(); } /** * Perform a given action defined by ServiceChecker for all active IOService * objects (active network connections). * * @param checker * is a <code>ServiceChecker</code> instance defining an action to * perform for all IOService objects. */ protected void doForAllServices(ServiceChecker<IO> checker) { for (IO service : services.values()) { checker.check(service); } } protected int[] getDefPlainPorts() { return null; } protected int[] getDefSSLPorts() { return null; } protected Map<String, Object> getParamsForPort(int port) { return null; } protected String getServiceId(Packet packet) { return getServiceId(packet.getTo()); } protected String getServiceId(JID jid) { return jid.getResource(); } protected String getUniqueId(IO serv) { return serv.getUniqueId(); } protected IO getXMPPIOService(String serviceId) { return services.get(serviceId); } protected IO getXMPPIOService(Packet p) { return services.get(getServiceId(p)); } protected boolean isHighThroughput() { return false; } /** * * @param p * @return */ protected boolean writePacketToSocket(Packet p) { IO ios = getXMPPIOService(p); if (ios != null) { return writePacketToSocket(ios, p); } else { return false; } } protected boolean writePacketToSocket(Packet p, String serviceId) { IO ios = getXMPPIOService(serviceId); if (ios != null) { return writePacketToSocket(ios, p); } else { return false; } } protected void writeRawData(IO ios, String data) { try { ios.writeRawData(data); SocketThread.addSocketService(ios); } catch (Exception e) { log.log(Level.WARNING, ios + "Exception during writing data: " + data, e); try { ios.stop(); } catch (Exception e1) { log.log(Level.WARNING, ios + "Exception stopping XMPPIOService: ", e1); } // end of try-catch } } private void putDefPortParams(Map<String, Object> props, int port, SocketType sock) { log.log(Level.CONFIG, "Generating defaults for port: {0}", port); props.put(PROP_KEY + port + "/" + PORT_TYPE_PROP_KEY, ConnectionType.accept); props.put(PROP_KEY + port + "/" + PORT_SOCKET_PROP_KEY, sock); props.put(PROP_KEY + port + "/" + PORT_IFC_PROP_KEY, PORT_IFC_PROP_VAL); props.put(PROP_KEY + port + "/" + PORT_REMOTE_HOST_PROP_KEY, PORT_REMOTE_HOST_PROP_VAL); props.put(PROP_KEY + port + "/" + TLS_REQUIRED_PROP_KEY, TLS_REQUIRED_PROP_VAL); Map<String, Object> extra = getParamsForPort(port); if (extra != null) { for (Map.Entry<String, Object> entry : extra.entrySet()) { props.put(PROP_KEY + port + "/" + entry.getKey(), entry.getValue()); } // end of for () } // end of if (extra != null) } private void reconnectService(final Map<String, Object> port_props, long delay) { if (log.isLoggable(Level.FINER)) { String cid = "" + port_props.get("local-hostname") + "@" + port_props.get("remote-hostname"); log.log(Level.FINER, "Reconnecting service for: {0}, scheduling next try in {1}secs, cid: {2}", new Object[] { getName(), delay / 1000, cid }); } addTimerTask(new TimerTask() { @Override public void run() { String host = (String) port_props.get(PORT_REMOTE_HOST_PROP_KEY); if (host == null) { host = (String) port_props.get("remote-hostname"); } int port = (Integer) port_props.get(PORT_KEY); if (log.isLoggable(Level.FINE)) { log.log( Level.FINE, "Reconnecting service for component: {0}, to remote host: {1} on port: {2}", new Object[] { getName(), host, port }); } startService(port_props); } }, delay); } private void releaseListeners() { for (ConnectionListenerImpl cli : pending_open) { connectThread.removeConnectionOpenListener(cli); } pending_open.clear(); } private void startService(Map<String, Object> port_props) { if (port_props == null) { throw new NullPointerException("port_props cannot be null."); } ConnectionListenerImpl cli = new ConnectionListenerImpl(port_props); if (cli.getConnectionType() == ConnectionType.accept) { pending_open.add(cli); } connectThread.addConnectionOpenListener(cli); } private class ConnectionListenerImpl implements ConnectionOpenListener { private Map<String, Object> port_props = null; private ConnectionListenerImpl(Map<String, Object> port_props) { this.port_props = port_props; } /** * Method description * * * @param sc */ @Override public void accept(SocketChannel sc) { String cid = "" + port_props.get("local-hostname") + "@" + port_props.get("remote-hostname"); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Accept called for service: {0}", cid); } IO serv = getXMPPIOServiceInstance(); serv.setIOServiceListener(ConnectionManager.this); serv.setSessionData(port_props); try { serv.accept(sc); if (getSocketType() == SocketType.ssl) { serv.startSSL(false); } // end of if (socket == SocketType.ssl) serviceStarted(serv); SocketThread.addSocketService(serv); } catch (SocketException e) { if (getConnectionType() == ConnectionType.connect) { // Accept side for component service is not ready yet? // Let's wait for a few secs and try again. if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Problem reconnecting the service: {0}, cid: {1}", new Object[] { serv, cid }); } boolean reconnect = false; Integer reconnects = (Integer) port_props.get(MAX_RECONNECTS_PROP_KEY); if (reconnects != null) { int recon = reconnects.intValue(); if (recon != 0) { port_props.put(MAX_RECONNECTS_PROP_KEY, (--recon)); reconnect = true; } // end of if (recon != 0) } if (reconnect) { reconnectService(port_props, connectionDelay); } else { reconnectionFailed(port_props); } } else { // Ignore } } catch (Exception e) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Can not accept connection cid: " + cid, e); } log.log(Level.WARNING, "Can not accept connection.", e); serv.stop(); } // end of try-catch } /** * Method description * * * @return */ @Override public ConnectionType getConnectionType() { String type = null; if (port_props.get(PORT_TYPE_PROP_KEY) == null) { log.warning(getName() + ": connection type is null: " + port_props.get(PORT_KEY).toString()); } else { type = port_props.get(PORT_TYPE_PROP_KEY).toString(); } return ConnectionType.valueOf(type); } /** * Method description * * * @return */ @Override public String[] getIfcs() { return (String[]) port_props.get(PORT_IFC_PROP_KEY); } /** * Method description * * * @return */ @Override public int getPort() { return (Integer) port_props.get(PORT_KEY); } /** * Method description * * * @return */ @Override public int getReceiveBufferSize() { return net_buffer; } /** * Method description * * * @return */ public SocketType getSocketType() { return SocketType.valueOf(port_props.get(PORT_SOCKET_PROP_KEY).toString()); } /** * Method description * * * @return */ @Override public int getTrafficClass() { if (isHighThroughput()) { return IPTOS_THROUGHPUT; } else { return DEF_TRAFFIC_CLASS; } } /** * Method description * * * @return */ @Override public String toString() { return port_props.toString(); } } private class IOServiceStatisticsGetter implements ServiceChecker<IO> { private StatisticsList list = new StatisticsList(Level.ALL); /** * Method description * * * @param service */ @Override public synchronized void check(IO service) { service.getStatistics(list, true); bytesReceived += list.getValue("socketio", "Bytes received", -1l); bytesSent += list.getValue("socketio", "Bytes sent", -1l); socketOverflow += list.getValue("socketio", "Buffers overflow", -1l); } } /** * Looks in all established connections and checks whether any of them is * dead.... * */ private class Watchdog implements Runnable { /** * Method description * */ @Override public void run() { while (true) { try { // Sleep... Thread.sleep(10 * MINUTE); ++watchdogRuns; // Walk through all connections and check whether they are // really alive...., try to send space for each service which // is inactive for hour or more and close the service // on Exception doForAllServices(new ServiceChecker<IO>() { @Override public void check(final XMPPIOService service) { try { if (null != service) { long curr_time = System.currentTimeMillis(); long lastTransfer = service.getLastTransferTime(); if (curr_time - lastTransfer >= getMaxInactiveTime()) { // Stop the service is max keep-alive time is exceeded // for non-active connections. if (log.isLoggable(Level.INFO)) { log.log(Level.INFO, "{0}: Max inactive time exceeded, stopping: {1}", new Object[] { getName(), service }); } ++watchdogStopped; service.stop(); } else { if (curr_time - lastTransfer >= (29 * MINUTE)) { // At least once an hour check if the connection is // still alive. service.writeRawData(" "); ++watchdogTests; } } } } catch (Exception e) { // Close the service.... try { if (service != null) { log.info(getName() + "Found dead connection, stopping: " + service); ++watchdogStopped; service.forceStop(); } } catch (Exception ignore) { // Do nothing here as we expect Exception to be thrown here... } } } }); } catch (InterruptedException e) { /* Do nothing here */ } } } } } // ConnectionManager