blob: 643a5bdcac1e55fd6aeb7323b0c14cf8fb579232 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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 org.apache.geode.internal;
import static org.apache.geode.distributed.internal.DistributionConfig.DEFAULT_MEMBERSHIP_PORT_RANGE;
import static org.apache.geode.internal.AvailablePort.AVAILABLE_PORTS_LOWER_BOUND;
import static org.apache.geode.internal.AvailablePort.AVAILABLE_PORTS_UPPER_BOUND;
import static org.apache.geode.internal.AvailablePort.getAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.geode.internal.AvailablePort.Keeper;
/**
* Provides helper methods for acquiring a set of unique available ports. It is not safe to simply
* call AvailablePort.getRandomAvailablePort several times in a row without doing something to
* ensure that they are unique. Although they are random, it is possible for subsequent calls to
* getRandomAvailablePort to return the same integer, unless that port is put into use before
* further calls to getRandomAvailablePort.
*/
public class AvailablePortHelper {
private final AtomicInteger currentMembershipPort;
private final AtomicInteger currentAvailablePort;
// Singleton object is only used to track the current ports
private static final AvailablePortHelper singleton = new AvailablePortHelper();
AvailablePortHelper() {
Random rand;
boolean fast = Boolean.getBoolean("AvailablePort.fastRandom");
if (fast)
rand = new Random();
else
rand = new java.security.SecureRandom();
currentMembershipPort = new AtomicInteger(
rand.nextInt(DEFAULT_MEMBERSHIP_PORT_RANGE[1] - DEFAULT_MEMBERSHIP_PORT_RANGE[0])
+ DEFAULT_MEMBERSHIP_PORT_RANGE[0]);
currentAvailablePort =
new AtomicInteger(rand.nextInt(AVAILABLE_PORTS_UPPER_BOUND - AVAILABLE_PORTS_LOWER_BOUND)
+ AVAILABLE_PORTS_LOWER_BOUND);
}
/**
* Returns array of unique randomly available tcp ports of specified count.
*/
public static int[] getRandomAvailableTCPPorts(int count) {
return getRandomAvailableTCPPorts(count, false);
}
/**
* Returns an array of unique randomly available tcp ports
*
* @param count number of desired ports
* @param useMembershipPortRange whether to use the configured membership-port-range
* @return the ports
*/
public static int[] getRandomAvailableTCPPorts(int count, boolean useMembershipPortRange) {
List<Keeper> list = getRandomAvailableTCPPortKeepers(count, useMembershipPortRange);
int[] ports = new int[list.size()];
int i = 0;
for (Keeper k : list) {
ports[i] = k.getPort();
k.release();
i++;
}
return ports;
}
public static List<Keeper> getRandomAvailableTCPPortKeepers(int count) {
return getRandomAvailableTCPPortKeepers(count, false);
}
private static List<Keeper> getRandomAvailableTCPPortKeepers(int count,
boolean useMembershipPortRange) {
List<Keeper> result = new ArrayList<>();
while (result.size() < count) {
result.add(getUniquePortKeeper(useMembershipPortRange, AvailablePort.SOCKET));
}
return result;
}
public static int[] getRandomAvailableTCPPortRange(final int count) {
return getRandomAvailableTCPPortRange(count, false);
}
public static int[] getRandomAvailableTCPPortRange(final int count,
final boolean useMembershipPortRange) {
List<Keeper> list =
getUniquePortRangeKeepers(useMembershipPortRange, AvailablePort.SOCKET,
count);
int[] ports = new int[list.size()];
int i = 0;
for (Keeper k : list) {
ports[i] = k.getPort();
k.release();
i++;
}
return ports;
}
public static List<Keeper> getRandomAvailableTCPPortRangeKeepers(final int count) {
return getRandomAvailableTCPPortRangeKeepers(count, false);
}
public static List<Keeper> getRandomAvailableTCPPortRangeKeepers(final int count,
final boolean useMembershipPortRange) {
return getUniquePortRangeKeepers(useMembershipPortRange, AvailablePort.SOCKET,
count);
}
private static void releaseKeepers(List<Keeper> keepers) {
for (Keeper keeper : keepers) {
keeper.release();
}
}
/**
* Returns array of unique randomly available tcp ports of specified count.
*/
public static int[] getRandomAvailableTCPPortsForDUnitSite(int count) {
int site = 1;
String hostName = System.getProperty("hostName");
if (hostName != null && hostName.startsWith("host") && hostName.length() > 4) {
site = Integer.parseInt(hostName.substring(4));
}
int[] ports = new int[count];
int i = 0;
while (i < count) {
int port = getUniquePort(false, AvailablePort.SOCKET);
// This logic is from AvailablePort.getRandomAvailablePortWithMod which this method used to
// call. It seems like the check should be (port % FOO == site) for some FOO, but given how
// widely this is used, it's not at all clear that no one's depending on the current behavior.
while (port % site != 0) {
port = getUniquePort(false, AvailablePort.SOCKET);
}
ports[i] = port;
++i;
}
return ports;
}
/**
* Returns array of unique randomly available tcp ports of specified count.
*/
public static int getRandomAvailablePortForDUnitSite() {
return getRandomAvailableTCPPortsForDUnitSite(1)[0];
}
/**
* Returns randomly available tcp port.
*/
public static int getRandomAvailableTCPPort() {
return getRandomAvailableTCPPorts(1)[0];
}
/**
* Returns array of unique randomly available udp ports of specified count.
*/
private static int[] getRandomAvailableUDPPorts(int count) {
int[] ports = new int[count];
int i = 0;
while (i < count) {
ports[i] = getUniquePort(false, AvailablePort.MULTICAST);
++i;
}
return ports;
}
/**
* Returns randomly available udp port.
*/
public static int getRandomAvailableUDPPort() {
return getRandomAvailableUDPPorts(1)[0];
}
public static void initializeUniquePortRange(int rangeNumber) {
if (rangeNumber < 0) {
throw new RuntimeException("Range number cannot be negative.");
}
singleton.currentMembershipPort.set(DEFAULT_MEMBERSHIP_PORT_RANGE[0]);
singleton.currentAvailablePort.set(AVAILABLE_PORTS_LOWER_BOUND);
if (rangeNumber != 0) {
// This code will generate starting points such that range 0 starts at the lowest possible
// value, range 1 starts halfway through the total available ports, 2 starts 1/4 of the way
// through, then further ranges are 3/4, 1/8, 3/8, 5/8, 7/8, 1/16, etc.
// This spaces the ranges out as much as possible for low numbers of ranges, while also making
// it possible to grow the number of ranges without bound (within some reasonable fraction of
// the number of available ports)
int membershipRange = DEFAULT_MEMBERSHIP_PORT_RANGE[1] - DEFAULT_MEMBERSHIP_PORT_RANGE[0];
int availableRange = AVAILABLE_PORTS_UPPER_BOUND - AVAILABLE_PORTS_LOWER_BOUND;
int numChunks = Integer.highestOneBit(rangeNumber) << 1;
int chunkNumber = 2 * (rangeNumber - Integer.highestOneBit(rangeNumber)) + 1;
singleton.currentMembershipPort.addAndGet(chunkNumber * membershipRange / numChunks);
singleton.currentAvailablePort.addAndGet(chunkNumber * availableRange / numChunks);
}
}
/**
* Get keeper objects for the next unused, consecutive 'rangeSize' ports on this machine.
*
* @param useMembershipPortRange - if true, select ports from the
* DistributionConfig.DEFAULT_MEMBERSHIP_PORT_RANGE
* @param protocol - either AvailablePort.SOCKET (TCP) or AvailablePort.MULTICAST (UDP)
* @param rangeSize - number of contiguous ports needed
* @return Keeper objects associated with a range of ports satisfying the request
*/
private static List<Keeper> getUniquePortRangeKeepers(boolean useMembershipPortRange,
int protocol, int rangeSize) {
AtomicInteger targetRange =
useMembershipPortRange ? singleton.currentMembershipPort : singleton.currentAvailablePort;
int targetBound =
useMembershipPortRange ? DEFAULT_MEMBERSHIP_PORT_RANGE[1] : AVAILABLE_PORTS_UPPER_BOUND;
while (true) {
int uniquePort = targetRange.getAndAdd(rangeSize);
if (uniquePort + rangeSize > targetBound) {
targetRange.set(useMembershipPortRange ? DEFAULT_MEMBERSHIP_PORT_RANGE[0]
: AVAILABLE_PORTS_LOWER_BOUND);
continue;
}
List<Keeper> keepers = new ArrayList<>();
int validPortsFound = 0;
while (validPortsFound < rangeSize) {
Keeper testKeeper =
AvailablePort.isPortKeepable(uniquePort++, protocol,
getAddress(protocol));
if (testKeeper == null) {
break;
}
keepers.add(testKeeper);
++validPortsFound;
}
if (validPortsFound == rangeSize) {
return keepers;
}
releaseKeepers(keepers);
}
}
private static Keeper getUniquePortKeeper(boolean useMembershipPortRange, int protocol) {
return getUniquePortRangeKeepers(useMembershipPortRange, protocol, 1).get(0);
}
/**
* Get the next available port on this machine.
*/
private static int getUniquePort(boolean useMembershipPortRange, int protocol) {
AtomicInteger targetRange =
useMembershipPortRange ? singleton.currentMembershipPort : singleton.currentAvailablePort;
int targetBound =
useMembershipPortRange ? DEFAULT_MEMBERSHIP_PORT_RANGE[1] : AVAILABLE_PORTS_UPPER_BOUND;
while (true) {
int uniquePort = targetRange.getAndIncrement();
if (uniquePort > targetBound) {
targetRange.set(useMembershipPortRange ? DEFAULT_MEMBERSHIP_PORT_RANGE[0]
: AVAILABLE_PORTS_LOWER_BOUND);
continue;
}
if (AvailablePort.isPortAvailable(uniquePort, protocol, getAddress(protocol))) {
return uniquePort;
}
}
}
}