diff -r 7c04fcb12bd4 -r 02589df0999a jdk/src/java.httpclient/share/classes/java/net/http/WSSignalHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.httpclient/share/classes/java/net/http/WSSignalHandler.java Mon May 09 23:33:09 2016 +0100 @@ -0,0 +1,137 @@ +/* + * 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; + } + } +}