jdk/src/java.httpclient/share/classes/java/net/http/WSSignalHandler.java
author prappo
Mon, 09 May 2016 23:33:09 +0100
changeset 37874 02589df0999a
child 39730 196f4e25d9f5
permissions -rw-r--r--
8087113: Websocket API and implementation Reviewed-by: chegar

/*
 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General  License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General  License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General  License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.net.http;

import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

import static java.util.Objects.requireNonNull;

//
// The problem:
// ------------
//   1. For every invocation of 'signal()' there must be at least
//      1 invocation of 'handler.run()' that goes after
//   2. There must be no more than 1 thread running the 'handler.run()'
//      at any given time
//
// For example, imagine each signal increments (+1) some number. Then the
// handler responds (eventually) the way that makes the number 0.
//
// For each signal there's a response. Several signals may be handled by a
// single response.
//
final class WSSignalHandler {

    // In this state the task is neither submitted nor running.
    // No one is handling signals. If a new signal has been received, the task
    // has to be submitted to the executor in order to handle this signal.
    private static final int DONE    = 0;

    // In this state the task is running.
    // * If the signaller has found the task in this state it will try to change
    //   the state to RERUN in order to make the already running task to handle
    //   the new signal before exiting.
    // * If the task has found itself in this state it will exit.
    private static final int RUNNING = 1;

    // A signal to the task, that it must rerun on the spot (without being
    // resubmitted to the executor).
    // If the task has found itself in this state it resets the state to
    // RUNNING and repeats the pass.
    private static final int RERUN   = 2;

    private final AtomicInteger state = new AtomicInteger(DONE);

    private final Executor executor;
    private final Runnable task;

    WSSignalHandler(Executor executor, Runnable handler) {
        this.executor = requireNonNull(executor);
        requireNonNull(handler);

        task = () -> {
            while (!Thread.currentThread().isInterrupted()) {

                try {
                    handler.run();
                } catch (Exception e) {
                    // Sorry, the task won't be automatically retried;
                    // hope next signals (if any) will kick off the handling
                    state.set(DONE);
                    throw e;
                }

                int prev = state.getAndUpdate(s -> {
                    if (s == RUNNING) {
                        return DONE;
                    } else {
                        return RUNNING;
                    }
                });

                // Can't be DONE, since only the task itself may transit state
                // into DONE (with one exception: RejectedExecution in signal();
                // but in that case we couldn't be here at all)
                assert prev == RUNNING || prev == RERUN;

                if (prev == RUNNING) {
                    break;
                }
            }
        };
    }

    // Invoked by outer code to signal
    void signal() {

        int prev = state.getAndUpdate(s -> {
            switch (s) {
                case RUNNING:
                    return RERUN;
                case DONE:
                    return RUNNING;
                case RERUN:
                    return RERUN;
                default:
                    throw new InternalError(String.valueOf(s));
            }
        });

        if (prev != DONE) {
            // Nothing to do! piggybacking on previous signal
            return;
        }
        try {
            executor.execute(task);
        } catch (RejectedExecutionException e) {
            // Sorry some signal() invocations may have been accepted, but won't
            // be done, since the 'task' couldn't be submitted
            state.set(DONE);
            throw e;
        }
    }
}