/*
* 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;
}
}
}