/* * Copyright 2014, The Sporting Exchange Limited * Copyright 2015, Simon Matić Langford * * 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.betfair.cougar.client.socket; import com.betfair.cougar.client.socket.resolver.NetworkAddressResolver; import com.betfair.cougar.netutil.InetSocketAddressUtils; import com.betfair.cougar.util.NetworkAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; import java.net.SocketAddress; import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import static com.betfair.cougar.netutil.InetSocketAddressUtils.createInetSocketAddress; /** * Recycles socket sessions periodically * - Opens new sessions to any new endpoints * - Closes sessions to inactive endpoints */ @ManagedResource public class SessionRecycler implements Runnable { private static final Logger logger = LoggerFactory.getLogger(SessionRecycler.class); private IoSessionFactory sessionFactory; private NetworkAddressResolver resolver; private String hosts; private String lastResolvedHosts; private String lastOpenedSessions; private String lastClosedSessions; private Date lastRecycleTimestamp; private Date lastCheckTimestamp; private Date lastErrorTimestamp; private String lastErrorMessage; private long sessionRecycleInterval; private static final String DEFAULT_PORT = "9003"; public SessionRecycler(IoSessionFactory sessionFactory, NetworkAddressResolver resolver, String hosts, long sessionRecycleInterval) { this.sessionFactory = sessionFactory; this.resolver = resolver; this.hosts = hosts; this.sessionRecycleInterval = sessionRecycleInterval; } public void initialise() { Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setName("Session Recycler for " + hosts); t.setDaemon(true); return t; } }).scheduleAtFixedRate(this, 0, sessionRecycleInterval, TimeUnit.MILLISECONDS); } @Override public void run() { recycleSessions(); } @ManagedOperation public void recycleSessions() { try { logger.debug("Sessions recycle started"); List<String> resolvedEndpoints = getResolvedEndpoints(); lastResolvedHosts = resolvedEndpoints.toString(); final Set<String> currentEndpoints = getCurrentEndpoints(); if (logger.isDebugEnabled()) { logger.debug("Configured endpoints are " + hosts); logger.debug("Resolved endpoints are " + resolvedEndpoints); logger.debug("Current endpoints are " + currentEndpoints); } List<String> sessionsToOpen = diff(resolvedEndpoints, currentEndpoints); List<String> sessionsToClose = diff(currentEndpoints, resolvedEndpoints); if (!sessionsToOpen.isEmpty() || !sessionsToClose.isEmpty()) { logger.info("Sessions to Open : " + sessionsToOpen); logger.info("Sessions to Close : " + sessionsToClose); lastOpenedSessions = sessionsToOpen.toString(); lastClosedSessions = sessionsToClose.toString(); for (String endPoint : sessionsToOpen) { sessionFactory.openSession(createInetSocketAddress(endPoint)); } for (String endPoint : sessionsToClose) { sessionFactory.closeSession(createInetSocketAddress(endPoint), false); } if (logger.isDebugEnabled()) { logger.debug("Sessions recycle completed"); } lastRecycleTimestamp = new Date(); } else { if (logger.isDebugEnabled()) { logger.debug("No change to any resolved endpoints detected"); } } lastCheckTimestamp = new Date(); } catch (Exception ex) { lastErrorTimestamp = new Date(); lastErrorMessage = ex.getMessage(); logger.error("Error while recycling sessions ", ex); } } private Set<String> getCurrentEndpoints() { Set<String> result = new HashSet(); for (SocketAddress socketAddress : sessionFactory.getCurrentSessionAddresses()) { result.add(InetSocketAddressUtils.asString(socketAddress)); } return result; } /** * Returns a list of elements in the first collection which are not present in the second collection * * @param first First collection * @param second Second collection * @return Difference between the two collections */ private List<String> diff(Collection<String> first, Collection<String> second) { final ArrayList<String> list = new ArrayList<String>(first); list.removeAll(second); return list; } private List<String> getResolvedEndpoints() { List<String> endpoints = new ArrayList<String>(); for (String url : hosts.split(",")) { String host = ""; String defaultPort = DEFAULT_PORT; try { if (url.startsWith("http")) { host = url.trim(); } else { String[] parts = url.trim().split(":"); host = parts[0]; if (parts.length > 1) { defaultPort = parts[1]; } } Set<String> resolvedAddresses = resolver.resolve(host); for (String resolvedAddress : resolvedAddresses) { String[] split = resolvedAddress.trim().split(":"); String serverIPAddress = split[0]; String serverPort = (split.length > 1 ? split[1] : defaultPort); if (NetworkAddress.isValidIPAddress(serverIPAddress)) { endpoints.add(serverIPAddress + ":" + serverPort); } } } catch (Exception ex) { logger.error("Unable to resolve host : " + host, ex); } } return endpoints; } @ManagedAttribute public String getHosts() { return this.hosts; } @ManagedAttribute public String getLastResolvedHosts() { return lastResolvedHosts; } @ManagedAttribute public String getLastOpenedSessions() { return lastOpenedSessions; } @ManagedAttribute public String getLastClosedSessions() { return lastClosedSessions; } @ManagedAttribute public Date getLastErrorTimestamp() { return lastErrorTimestamp; } @ManagedAttribute public String getLastErrorMessage() { return lastErrorMessage; } @ManagedAttribute public long getSessionRecycleInterval() { return sessionRecycleInterval; } @ManagedAttribute public Date getLastRecycleTimestamp() { return lastRecycleTimestamp; } @ManagedAttribute public Date getLastCheckTimestamp() { return lastCheckTimestamp; } }