| /* |
| * 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.tomee.server.composer; |
| |
| |
| import java.io.IOException; |
| import java.net.ServerSocket; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import java.util.concurrent.TimeUnit; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| /** |
| * Will allocate a block of ports as an iterable. The ports are held open |
| * and reserved until the time the user calls `next()` on the iterable. |
| * |
| * After the user calls `next()` the port stays in a reserved status for |
| * one minute and will not be issued again by this class within that time. |
| * |
| * Ports returned are guaranteed to be unique with no duplicates. |
| * |
| * It is important to note once the user calls `next()` the port is released |
| * and nothing stops an external process from grabbing the port. The one minute |
| * reserved status is only effective for code in this VM using this utility. |
| */ |
| public class Ports { |
| |
| private static final List<Port> allocated = new CopyOnWriteArrayList<>(); |
| |
| private Ports() { |
| } |
| |
| /** |
| * Allocates a single port which is immediately released |
| * and available. |
| */ |
| public static int allocate() { |
| return allocate(1).iterator().next(); |
| } |
| |
| /** |
| * Allocates N ports all of which are open and being held |
| * and not available until the iterable is consumed. |
| */ |
| public static Iterable<Integer> allocate(final int count) { |
| // Trim any old ports from the list |
| allocated.removeIf(Port::isOld); |
| |
| // Allocate new ports |
| final List<Port> ports = Stream.generate(Port::new) |
| .filter(Ports::isNotReserved) |
| .limit(count) |
| .collect(Collectors.toList()); |
| |
| // Add them to the allocated list so we don't issue them |
| // again for at least a minute |
| allocated.addAll(ports); |
| |
| return () -> new Iterator<Integer>() { |
| @Override |
| public boolean hasNext() { |
| return ports.size() > 0; |
| } |
| |
| @Override |
| public Integer next() { |
| final Port port = ports.remove(0); |
| // A port's "age" starts right now |
| // |
| // The person who called this method |
| // now has one minute to consume the port |
| return port.release(); |
| } |
| }; |
| } |
| |
| /** |
| * If this port is in the allocated list, we must immediately |
| * release the port and not include it in the new list |
| */ |
| private static boolean isNotReserved(Port port) { |
| if (allocated.contains(port)) { |
| port.release(); |
| return false; |
| } |
| return true; |
| } |
| |
| static class Port { |
| |
| private final ServerSocket serverSocket; |
| private final int port; |
| private volatile long closed; |
| private long tolerance; |
| |
| Port() { |
| this(TimeUnit.MINUTES.toNanos(1)); |
| } |
| |
| Port(final long nanoseconds) { |
| this.tolerance = nanoseconds; |
| try { |
| // When the system is out of ports the following exception will be thrown and caught here |
| // java.net.SocketException: Too many open files in system |
| this.serverSocket = new ServerSocket(0); |
| this.port = this.serverSocket.getLocalPort(); |
| this.closed = 0; |
| } catch (final IOException e) { |
| throw new IllegalStateException("Unable to create a server socket with random port", e); |
| } |
| } |
| |
| /** |
| * If this port has been released more than a minute ago, it is old |
| * and should be removed from the list |
| */ |
| public boolean isOld() { |
| return closed != 0 && closed < System.nanoTime(); |
| } |
| |
| public int get() { |
| return port; |
| } |
| |
| public int release() { |
| if (serverSocket.isClosed()) throw new IllegalStateException("Port has already been consumed"); |
| |
| final int port = serverSocket.getLocalPort(); |
| try { |
| serverSocket.close(); |
| this.closed = System.nanoTime() + tolerance; |
| return port; |
| } catch (IOException e) { |
| throw new IllegalStateException("Unable to close server socket and free port " + port, e); |
| } |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| Port port1 = (Port) o; |
| |
| if (port != port1.port) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| return port; |
| } |
| } |
| } |