/* Copyright (c) 2011 Danish Maritime Authority. * * 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 net.maritimecloud.mms.server.connection.client; import static java.util.Objects.requireNonNull; import java.util.Iterator; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import net.maritimecloud.mms.server.connection.client.Client.State; import net.maritimecloud.mms.server.connection.transport.ServerTransport; import net.maritimecloud.net.mms.MmsConnectionClosingCode; import org.cakeframework.container.concurrent.ScheduleAtFixedRate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A client reaper takes care of removing stale clients * * @author Kasper Nielsen */ public class ClientReaper { /** The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(ServerTransport.class); /** The number of nanoseconds before a stale connection is detected. */ private final long timeoutNanos = TimeUnit.MINUTES.toNanos(5); /** The client manager that maintains a list of connected clients. */ private final ClientManager clientManager; /** The transport listener, used for reaping clients that do not send hello messages. */ private final DefaultTransportListener transportListener; /** * @param clientManager */ public ClientReaper(ClientManager clientManager, DefaultTransportListener transportListener) { this.clientManager = requireNonNull(clientManager); this.transportListener = requireNonNull(transportListener); } /** Cleans up and remove stale clients. */ @ScheduleAtFixedRate(value = 30, unit = TimeUnit.SECONDS) public void cleanudp() { try { cleanup(); } catch (Throwable e) { e.printStackTrace(); } } public void cleanup() { long now = System.nanoTime(); // We start by reaping missing Helloes from clients. That is clients that have received the welcome message. // But who for some reason do not ever reply to it but keeps the connection open. Iterator<ServerTransport> transports = transportListener.missingHellos.iterator(); while (transports.hasNext()) { ServerTransport t = transports.next(); if (t.getTimeOfCreation()+ timeoutNanos < now) { t.close(MmsConnectionClosingCode.CLIENT_TIMEOUT); transports.remove(); } } Iterator<Client> clients = clientManager.clients.values().iterator(); // clients.iterator() is immutable while (clients.hasNext()) { Client ic = clients.next(); ReentrantReadWriteLock.WriteLock lock = ic.lock.writeLock(); // We use tryLock() in the following instead of lock() because we do not perform any critical operations. // And if we do not succeed in acquiring the lock this time. We most likely will the next time this method // is run. // TODO maybe check for no messages in 2 minutes, send pong or something like it ClientInternalState state = ic.state; // state.session != null -> connected or disconnected if (state.session != null && ic.getTimeOfLatestReceivedMessage() + timeoutNanos < now && lock.tryLock()) { try { state = ic.state; // refresh after we have locked if (state.session != null && ic.getTimeOfLatestReceivedMessage() + timeoutNanos < now) { LOGGER.info("Killing client " + ic.getId()); ic.close(MmsConnectionClosingCode.CLIENT_TIMEOUT); ic.state = ClientInternalState.TERMINATED; state = ic.state;// refresh state } } finally { lock.unlock(); } } if (state.state == State.TERMINATED && lock.tryLock()) { try { clients.remove(); // A client will never transition from the terminated state } finally { lock.unlock(); } } } } }