blob: 55c5dfd081d2535a3dc2e84e24ece11d68ba4e5f [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.je.rep.utilint;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* An iterator to iterate over the free ports on an interface.
*/
public class FreePortLocator {
/**
* Whether to print debugging messages -- use this to find tests that are
* not closing ports.
*/
private static final boolean debug =
Boolean.getBoolean("test.debugFreePortLocator");
private final String hostname;
private final int portStart;
private final int portEnd;
private int currPort;
/**
* Constructor identifying the interface and the port range within which
* to look for free ports. The port range specified by the arguments
* must be < 32768, that is, it should be outside the dynamic port range
* that is typically configured on most machines.
*
* @see <a href="https://sleepycat.oracle.com/trac/wiki/JEKV/UnitTest#Avoidingproblemswithanonymousports.html">Anonymous ports</a>
* for details regarding port configuration for tests.
*/
public FreePortLocator(String hostname, int portStart, int portEnd) {
super();
assert portStart < portEnd;
if ((portStart > 0x7fff) || (portEnd > 0x7fff)) {
throw new IllegalArgumentException
("Invalid port range:" + portStart + " - " + portEnd + ". " +
"The port range must not extend past:" + 0x7fff +
" since the allocated ports could then overlap with " +
"dynamically assigned ports used by other services. ");
}
this.hostname = hostname;
this.portStart = portStart;
this.portEnd = portEnd;
currPort = portStart;
}
public int getPortStart() {
return portStart;
}
public int getPortEnd() {
return portEnd;
}
/**
* Returns the next free port. Note that it's possible that on a busy
* machine another process may grab the "free" port before it's actually
* used.
*
* There is somewhat AIsh aspect to the code below. In general it tries to
* be very conservative, using different techniques so that it works
* reasonably well on Linux, Mac OS and Windows.
*
* Note: The use of setReuseAddress after a bind operation may look
* dubious, since it runs counter to the API doc, but it helps based on
* actual tests. It's also the idiom used by Apache Camel to find a
* free port. It, at least, can't hurt.
*/
public int next() {
while (++currPort < portEnd) {
/* Try connecting to the port to see if somebody is listening. */
Socket s = null;
try {
s = new Socket(hostname, currPort);
/* Somebody is listening on the port. */
if (debug) {
System.err.println(
"FreePortLocator: " + currPort + " busy - socket");
Thread.dumpStack();
}
continue;
} catch (IOException e) {
/* Nobody is listening, continue with other tests. */
} finally {
if (s != null){
try {
s.close();
} catch (IOException e) {
/* Unexpected, something's wrong, ignore the port. */
if (debug) {
System.err.println(
"FreePortLocator: " + currPort +
" busy - socket close: " + e);
e.printStackTrace();
}
continue;
}
}
}
/* Try without a hostname */
ServerSocket ss = null;
DatagramSocket ds = null;
try {
ss = new ServerSocket(currPort);
ss.setReuseAddress(true);
ds = new DatagramSocket(currPort);
ds.setReuseAddress(true);
} catch (IOException e) {
if (debug) {
System.err.println(
"FreePortLocator: " + currPort +
" busy - server, datagram: " + e);
e.printStackTrace();
}
continue;
} finally {
if (ds != null) {
ds.close();
}
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
if (debug) {
System.err.println(
"FreePortLocator: " + currPort +
" busy - server close: " + e);
e.printStackTrace();
}
continue;
}
}
}
ss = null;
ds = null;
/* try with a hostname */
final InetSocketAddress sa =
new InetSocketAddress(hostname, currPort);
try {
ss = new ServerSocket();
ss.setReuseAddress(true);
ss.bind(sa);
ds = new DatagramSocket(sa);
ds.setReuseAddress(true);
} catch (IOException e) {
if (debug) {
System.err.println(
"FreePortLocator: " + currPort +
" busy - server, datagram hostname: " + e);
e.printStackTrace();
}
continue;
} finally {
if (ds != null) {
ds.close();
}
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
if (debug) {
System.err.println(
"FreePortLocator: " + currPort +
" busy - server hostname close: " + e);
e.printStackTrace();
}
continue;
}
}
}
/* Survived port test gauntlet, return it. */
if (debug) {
System.err.println(
"FreePortLocator: " + currPort + " free");
}
return currPort;
}
throw new IllegalStateException
("No more ports available in the range: " +
portStart + " - " + portEnd);
}
/**
* Skip a number of ports.
*/
public void skip(int num) {
currPort += num;
}
}