org.apache.geode.internal.process.lang.AvailablePid.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.geode.internal.process.lang.AvailablePid.java

Source

/*
 * 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.process.lang;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.apache.geode.internal.process.ProcessUtils.isProcessAlive;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import com.google.common.base.Stopwatch;

/**
 * Finds unused pids for use in tests.
 */
public class AvailablePid {

    static final int DEFAULT_LOWER_BOUND = 1;
    static final int DEFAULT_UPPER_BOUND = 64000;
    static final int DEFAULT_TIMEOUT_MILLIS = 60 * 1000;

    private final int lowerBound;
    private final int upperBound;
    private final Random random;
    private final int timeoutMillis;

    /**
     * Construct with:
     * <ul>
     * <li>default {@link Bounds} of {@link #DEFAULT_LOWER_BOUND} (inclusive) and
     * {@link #DEFAULT_UPPER_BOUND} (inclusive)
     * <li>Random with no see
     * <li>default timeout of 1 minute.
     * </ul>
     */
    public AvailablePid() {
        this(new Bounds(DEFAULT_LOWER_BOUND, DEFAULT_UPPER_BOUND), new Random(), DEFAULT_TIMEOUT_MILLIS);
    }

    /**
     * Construct with:
     * <ul>
     * <li>default {@link Bounds} of {@link #DEFAULT_LOWER_BOUND} (inclusive) and
     * {@link #DEFAULT_UPPER_BOUND} (inclusive)
     * <li>Random with specified seed
     * <li>default timeout of 1 minute
     * </ul>
     */
    public AvailablePid(final long seed) {
        this(new Bounds(DEFAULT_LOWER_BOUND, DEFAULT_UPPER_BOUND), new Random(seed), DEFAULT_TIMEOUT_MILLIS);
    }

    /**
     * Construct with:
     * <ul>
     * <li>default {@link Bounds} of {@link #DEFAULT_LOWER_BOUND} (inclusive) and
     * {@link #DEFAULT_UPPER_BOUND} (inclusive)
     * <li>specified Random instance
     * <li>default timeout of 1 minute
     * </ul>
     */
    public AvailablePid(final Random random) {
        this(new Bounds(DEFAULT_LOWER_BOUND, DEFAULT_UPPER_BOUND), random, DEFAULT_TIMEOUT_MILLIS);
    }

    /**
     * Construct with:
     * <ul>
     * <li>specified {@link Bounds} of {@code lowerBound} (inclusive) and {@code upperBound}
     * (inclusive)
     * <li>Random with no seed
     * <li>default timeout of 1 minute
     * </ul>
     */
    public AvailablePid(final Bounds bounds) {
        this(bounds, new Random(), DEFAULT_TIMEOUT_MILLIS);
    }

    /**
     * Construct with:
     * <ul>
     * <li>specified {@link Bounds} of {@code lowerBound} (inclusive) and {@code upperBound}
     * (inclusive)
     * <li>specified Random instance
     * <li>specified default timeout millis
     * </ul>
     */
    public AvailablePid(final Bounds bounds, final Random random, final int timeoutMillis) {
        this.lowerBound = bounds.lowerBound;
        this.upperBound = bounds.upperBound;
        this.random = random;
        this.timeoutMillis = timeoutMillis;
    }

    /**
     * Returns specified pid if it's unused. Else returns randomly unused pid between
     * {@code lowerBound} (inclusive) and {@code upperBound} (inclusive).
     */
    public int findAvailablePid(final int pid) throws TimeoutException {
        if (isProcessAlive(pid)) {
            return pid;
        }
        return findAvailablePid();
    }

    /**
     * Returns randomly unused pid between {@code lowerBound} (inclusive) and {@code upperBound}
     * (inclusive).
     */
    public int findAvailablePid() throws TimeoutException {
        Stopwatch stopwatch = Stopwatch.createStarted();
        int pid = random();
        while (isProcessAlive(pid)) {
            if (stopwatch.elapsed(MILLISECONDS) > timeoutMillis) {
                throw new TimeoutException("Failed to find available pid within " + timeoutMillis + " millis.");
            }
            pid = random();
        }
        return pid;
    }

    /**
     * Returns specified number of unique, randomly unused pids between {@code lowerBound} (inclusive)
     * and {@code upperBound} (inclusive).
     */
    public int[] findAvailablePids(final int number) throws TimeoutException {
        Set<Integer> pids = new HashSet<>();
        while (pids.size() < number) {
            int pid = new AvailablePid().findAvailablePid();
            if (!pids.contains(pid)) {
                pids.add(pid);
            }
        }
        return Arrays.stream(pids.toArray(new Integer[0])).mapToInt(Integer::intValue).toArray();
    }

    /**
     * Returns a value between {@code lowerBound} (inclusive) and {@code upperBound} (inclusive)
     */
    int random() {
        return random.nextInt(upperBound + 1 - lowerBound) + lowerBound;
    }

    /**
     * Lower and upper bounds for desired PIDs. Both are inclusive -- if you specify
     * {@code new Bounds(1, 100)} then {@code AvailablePid} will return values of 1 through 100.
     *
     * <ul>
     * <li>{@code lowerBound} must be an integer greater than zero.
     * <li>{@code upperBound} must be an integer greater than {@code lowerBound}.
     * </ul>
     */
    static class Bounds {
        final int lowerBound;
        final int upperBound;

        Bounds(final int lowerBound, final int upperBound) {
            if (lowerBound < 1) {
                throw new IllegalArgumentException("lowerBound must be greater than '0'");
            }
            if (upperBound <= lowerBound) {
                throw new IllegalArgumentException(
                        "upperBound must be greater than lowerBound '" + lowerBound + "'");
            }
            this.lowerBound = lowerBound;
            this.upperBound = upperBound;
        }
    }

}