All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.kv.impl.util.registry.TimeoutSocket Maven / Gradle / Ivy

/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.util.registry;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * TimeoutSocket provides an efficient implementation of socket read timeouts.
 * The more obvious approach is to have the ClientSocketFactory.createSocket
 * method create a vanilla java.net.Socket, and specify timeouts with
 * Socket.setSoTimeout(). This turned out to be cpu intensive, because the
 * implementation uses a poll system call to enforce the timeout.
 * 

* Instead, the TimeoutSocket provides an isActive() method that lets a timer * thread query the socket to see if a read has taken too long. If the socket * has a pending read, but has been inactive for longer than the timeout * period, the socket is forcibly closed. Timeout monitoring is only in effect * if this socket is properly registered with the * ClientSocketFactor.TimeoutTask. * * @see ClientSocketFactory */ public class TimeoutSocket extends Socket { /* * Denotes read response activity associated with the socket. It's set each * time a read operation is successfully completed on the socket's input * stream. */ private volatile boolean readActivity; /* * Tracks the number of pending read operations. Timeouts only take effect * if a read operation is underway: the socket will be left open if it is * idle. Synchronize on pendingReadsSync when accessing this field to * coordinate between the timeout thread, which sets closedTimeout only if * pendingReads is non-zero, and the reading thread, which needs to notice * closedTimeout for the read that was timed out. */ private int pendingReads = 0; private final Object pendingReadsSync = new Object(); /* * The timeout associated with the socket. A value of zero indicates no * timeout. */ private volatile int timeoutMs; /* The "time" of the last check for read activity on the socket. */ private long lastCheckMs = 0L; /** Set to true if the socket has been closed due to a timeout. */ private volatile boolean closedTimeout; /** * The caller should register this socket with a TimeoutTask so it's * monitored for inactivity. See ClientSocketFactory.TimeoutTask.register */ public TimeoutSocket(int timeoutMs) { this.timeoutMs = timeoutMs; readActivity = true; } /** * Used to modify the timeout associated with the socket. * * @param timeoutMs the new timeout value */ public void setTimeoutMs(int timeoutMs) { this.timeoutMs = timeoutMs; /* Ensure that the next tick resets the time and counter. */ readActivity = true; } @Override public InputStream getInputStream() throws IOException { return new TimeoutInputStream(super.getInputStream()); } /** * All reads done with this socket with are wrapped so we can keep track of * whether there have been any reads. Timeouts are only enforced during * reads. */ private class TimeoutInputStream extends FilterInputStream { private int bytesRead; TimeoutInputStream(InputStream in) { super(in); } @Override public int read() throws IOException { synchronized (pendingReadsSync) { pendingReads++; } try { try { synchronized (this) { final int result = super.read(); if (result > 0) { readActivity = true; bytesRead += result; } return result; } } finally { /* * Note that this method will throw SocketTimeoutException * if a timeout has occurred, which will be thrown instead * of any exception thrown by the read */ checkClosedTimeout(); } } finally { synchronized (pendingReadsSync) { pendingReads--; } } } /** * Throw SocketTimeoutException if the socket has been closed due to a * timeout. */ private void checkClosedTimeout() throws SocketTimeoutException { if (closedTimeout) { final int br; synchronized (this) { br = bytesRead; } final SocketTimeoutException e = new SocketTimeoutException( "Read interrupted after stream transferred " + br + " bytes because inactive socket " + TimeoutSocket.this + " timed out and was forcibly closed, timeout: " + timeoutMs + " ms"); e.bytesTransferred = br; throw e; } } @Override public int read(byte[] b) throws IOException { synchronized (pendingReadsSync) { pendingReads++; } try { try { synchronized (this) { final int result = super.read(b); if (result > 0) { readActivity = true; bytesRead += result; } return result; } } finally { checkClosedTimeout(); } } finally { synchronized (pendingReadsSync) { pendingReads--; } } } @Override public int read(byte[] b, int off, int len) throws IOException { synchronized (pendingReadsSync) { pendingReads++; } try { try { synchronized (this) { final int result = super.read(b, off, len); if (result > 0) { readActivity = true; bytesRead += result; } return result; } } finally { checkClosedTimeout(); } } finally { synchronized (pendingReadsSync) { pendingReads--; } } } } @Override public synchronized void close() throws IOException { super.close(); readActivity = false; } private void resetActivityCounter(long timeMs) { lastCheckMs = timeMs; readActivity = false; } /** * Method invoked by the timeout thread to check on the socket on a * periodic basis. Note that the time that is passed in is a "pseudo" time * that is only meaningful for calculating time differences. * * @param timeMs the pseudo time * @param logger is provided if possible. There may be times when the * logger is not available, mainly in a test setting where multiple * services are being created within a single process, and those services * are being opened and closed, so check if the logger is null. * * @return true if the socket is active, false if it isn't and has been * closed */ public boolean isActive(long timeMs, Logger logger) { if (isClosed()) { /* The socket is closed */ return false; } if (!isConnected()) { /* Not yet connected, wait for it to be connected. */ return true; } if (readActivity) { resetActivityCounter(timeMs); return true; } if ((timeoutMs == 0) || (timeMs - lastCheckMs) < timeoutMs) { return true; } /* Don't timeout if the socket is idle */ synchronized (pendingReadsSync) { if (pendingReads == 0) { return true; } /* * Set while synchronized on pendingReadsSync so that the close * will be noticed by the pending read */ closedTimeout = true; } /* * No activity, force the socket closed thus generating an * AsynchronousCloseException in the read/write threads. Note that when * multiple services are instantiated in a single process, which only * happens in test cases, because there is a single static logger in * the ClientServiceLibrary, the provided logger may come from a * different service, and the component id prefix on the logging * message may be confusing. We toString() the socket so the message is * as clear as possible about the target socket. */ final String message = "Inactive socket " + this + " timed out and was forcibly closed, timeout: " + timeoutMs + " ms"; if (logger == null) { System.err.println(message); } else { logger.info(message); } try { close(); } catch (IOException e) { if (logger != null) { logger.log(Level.FINEST, "Exception on close", e); } } return false; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy