View Javadoc
1   package ca.uhn.fhir.util;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2019 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import com.google.common.annotations.VisibleForTesting;
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  import java.io.IOException;
28  import java.net.DatagramSocket;
29  import java.net.InetSocketAddress;
30  import java.net.ServerSocket;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.List;
34  
35  /**
36   * Provides server ports that are free, in order for tests to use them
37   *
38   * <p><b>
39   * This class is ONLY designed for unit-testing usage, as it holds on to server ports
40   * for a long time (potentially lots of them!) and will leave your system low on
41   * ports if you put it into production.
42   * </b></p>
43   * <p>
44   * How it works:
45   * <p>
46   * We have lots of tests that need a free port because they want to open up
47   * a server, and need the port to be unique and unused so that the tests can
48   * run multithreaded. This turns out to just be an awful problem to solve for
49   * lots of reasons:
50   * <p>
51   * 1. You can request a free port from the OS by calling <code>new ServerSocket(0);</code>
52   * and this seems to work 99% of the time, but occasionally on a heavily loaded
53   * server if two processes ask at the exact same time they will receive the
54   * same port assignment, and one will fail.
55   * 2. Tests run in separate processes, so we can't just rely on keeping a collection
56   * of assigned ports or anything like that.
57   * <p>
58   * So we solve this like this:
59   * <p>
60   * At random, this class will pick a "control port" and bind it. A control port
61   * is just a randomly chosen port that is a multiple of 100. If we can bind
62   * successfully to that port, we now own the range of "n+1 to n+99". If we can't
63   * bind that port, it means some other process has probably taken it so
64   * we'll just try again until we find an available control port.
65   * <p>
66   * Assuming we successfully bind a control port, we'll give out any available
67   * ports in the range "n+1 to n+99" until we've exhausted the whole set, and
68   * then we'll pick another control port (if we actually get asked for over
69   * 100 ports.. this should be a rare event).
70   * <p>
71   * This mechanism has the benefit of (fingers crossed) being bulletproof
72   * in terms of its ability to give out ports that are actually free, thereby
73   * preventing random test failures.
74   * <p>
75   * This mechanism has the drawback of never giving up a control port once
76   * it has assigned one. To be clear, this class is deliberately leaking
77   * resources. Again, no production use!
78   */
79  public class PortUtil {
80  	private static final int SPACE_SIZE = 100;
81  	private static final Logger ourLog = LoggerFactory.getLogger(PortUtil.class);
82  	private static final PortUtilortUtil">PortUtil INSTANCE = new PortUtil();
83  	private static int ourPortDelay = 500;
84  	private List<ServerSocket> myControlSockets = new ArrayList<>();
85  	private Integer myCurrentControlSocketPort = null;
86  	private int myCurrentOffset = 0;
87  	/**
88  	 * Constructor -
89  	 */
90  	PortUtil() {
91  		// nothing
92  	}
93  
94  	/**
95  	 * Clear and release all control sockets
96  	 */
97  	synchronized void clearInstance() {
98  		for (ServerSocket next : myControlSockets) {
99  			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