001package ca.uhn.fhir.util; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2019 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import com.google.common.annotations.VisibleForTesting; 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027import java.io.IOException; 028import java.net.DatagramSocket; 029import java.net.InetSocketAddress; 030import java.net.ServerSocket; 031import java.util.ArrayList; 032import java.util.Arrays; 033import java.util.List; 034 035/** 036 * Provides server ports that are free, in order for tests to use them 037 * 038 * <p><b> 039 * This class is ONLY designed for unit-testing usage, as it holds on to server ports 040 * for a long time (potentially lots of them!) and will leave your system low on 041 * ports if you put it into production. 042 * </b></p> 043 * <p> 044 * How it works: 045 * <p> 046 * We have lots of tests that need a free port because they want to open up 047 * a server, and need the port to be unique and unused so that the tests can 048 * run multithreaded. This turns out to just be an awful problem to solve for 049 * lots of reasons: 050 * <p> 051 * 1. You can request a free port from the OS by calling <code>new ServerSocket(0);</code> 052 * and this seems to work 99% of the time, but occasionally on a heavily loaded 053 * server if two processes ask at the exact same time they will receive the 054 * same port assignment, and one will fail. 055 * 2. Tests run in separate processes, so we can't just rely on keeping a collection 056 * of assigned ports or anything like that. 057 * <p> 058 * So we solve this like this: 059 * <p> 060 * At random, this class will pick a "control port" and bind it. A control port 061 * is just a randomly chosen port that is a multiple of 100. If we can bind 062 * successfully to that port, we now own the range of "n+1 to n+99". If we can't 063 * bind that port, it means some other process has probably taken it so 064 * we'll just try again until we find an available control port. 065 * <p> 066 * Assuming we successfully bind a control port, we'll give out any available 067 * ports in the range "n+1 to n+99" until we've exhausted the whole set, and 068 * then we'll pick another control port (if we actually get asked for over 069 * 100 ports.. this should be a rare event). 070 * <p> 071 * This mechanism has the benefit of (fingers crossed) being bulletproof 072 * in terms of its ability to give out ports that are actually free, thereby 073 * preventing random test failures. 074 * <p> 075 * This mechanism has the drawback of never giving up a control port once 076 * it has assigned one. To be clear, this class is deliberately leaking 077 * resources. Again, no production use! 078 */ 079public class PortUtil { 080 private static final int SPACE_SIZE = 100; 081 private static final Logger ourLog = LoggerFactory.getLogger(PortUtil.class); 082 private static final PortUtil INSTANCE = new PortUtil(); 083 private static int ourPortDelay = 500; 084 private List<ServerSocket> myControlSockets = new ArrayList<>(); 085 private Integer myCurrentControlSocketPort = null; 086 private int myCurrentOffset = 0; 087 /** 088 * Constructor - 089 */ 090 PortUtil() { 091 // nothing 092 } 093 094 /** 095 * Clear and release all control sockets 096 */ 097 synchronized void clearInstance() { 098 for (ServerSocket next : myControlSockets) { 099 ourLog.info("Releasing control port: {}", next.getLocalPort()); 100 try { 101 next.close(); 102 } catch (IOException theE) { 103 // ignore 104 } 105 } 106 myControlSockets.clear(); 107 myCurrentControlSocketPort = null; 108 } 109 110 /** 111 * Clear and release all control sockets 112 */ 113 synchronized int getNextFreePort() { 114 115 while (true) { 116 117 // Acquire a control port 118 while (myCurrentControlSocketPort == null) { 119 int nextCandidate = (int) (Math.random() * 65000.0); 120 nextCandidate = nextCandidate - (nextCandidate % SPACE_SIZE); 121 122 if (nextCandidate < 10000) { 123 continue; 124 } 125 126 try { 127 ServerSocket server = new ServerSocket(); 128 server.setReuseAddress(true); 129 server.bind(new InetSocketAddress("localhost", nextCandidate)); 130 myControlSockets.add(server); 131 ourLog.info("Acquired control socket on port {}", nextCandidate); 132 myCurrentControlSocketPort = nextCandidate; 133 myCurrentOffset = 0; 134 } catch (IOException theE) { 135 ourLog.info("Candidate control socket {} is already taken", nextCandidate); 136 continue; 137 } 138 } 139 140 // Find a free port within the allowable range 141 while (true) { 142 myCurrentOffset++; 143 144 if (myCurrentOffset == SPACE_SIZE) { 145 // Current space is exhausted 146 myCurrentControlSocketPort = null; 147 break; 148 } 149 150 int nextCandidatePort = myCurrentControlSocketPort + myCurrentOffset; 151 152 // Try to open a port on this socket and use it 153 if (!isAvailable(nextCandidatePort)) { 154 continue; 155 } 156 157 // Log who asked for the port, just in case that's useful 158 StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); 159 StackTraceElement previousElement = Arrays.stream(stackTraceElements) 160 .filter(t -> !t.toString().contains("PortUtil.") && !t.toString().contains("getStackTrace")) 161 .findFirst() 162 .orElse(stackTraceElements[2]); 163 ourLog.info("Returned available port {} for: {}", nextCandidatePort, previousElement.toString()); 164 165 try { 166 Thread.sleep(ourPortDelay); 167 } catch (InterruptedException theE) { 168 // ignore 169 } 170 171 return nextCandidatePort; 172 173 } 174 175 176 } 177 } 178 179 @VisibleForTesting 180 public static void setPortDelay(Integer thePortDelay) { 181 if (thePortDelay == null) { 182 thePortDelay = 500; 183 } else { 184 ourPortDelay = thePortDelay; 185 } 186 } 187 188 /** 189 * This method checks if we are able to bind a given port to both 190 * 0.0.0.0 and localhost in order to be sure it's truly available. 191 */ 192 private static boolean isAvailable(int thePort) { 193 ourLog.info("Testing a bind on thePort {}", thePort); 194 try (ServerSocket ss = new ServerSocket()) { 195 ss.setReuseAddress(true); 196 ss.bind(new InetSocketAddress("0.0.0.0", thePort)); 197 try (DatagramSocket ds = new DatagramSocket()) { 198 ds.setReuseAddress(true); 199 ds.connect(new InetSocketAddress("127.0.0.1", thePort)); 200 ourLog.info("Successfully bound thePort {}", thePort); 201 } catch (IOException e) { 202 ourLog.info("Failed to bind thePort {}: {}", thePort, e.toString()); 203 return false; 204 } 205 } catch (IOException e) { 206 ourLog.info("Failed to bind thePort {}: {}", thePort, e.toString()); 207 return false; 208 } 209 210 try (ServerSocket ss = new ServerSocket()) { 211 ss.setReuseAddress(true); 212 ss.bind(new InetSocketAddress("localhost", thePort)); 213 } catch (IOException e) { 214 ourLog.info("Failed to bind thePort {}: {}", thePort, e.toString()); 215 return false; 216 } 217 218 return true; 219 } 220 221 /** 222 * The entire purpose here is to find an available port that can then be 223 * bound for by server in a unit test without conflicting with other tests. 224 * <p> 225 * This is really only used for unit tests but is included in the library 226 * so it can be reused across modules. Use with caution. 227 */ 228 public static int findFreePort() { 229 return INSTANCE.getNextFreePort(); 230 } 231 232} 233