/*
* 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.ignite.internal.util.worker;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import org.apache.ignite.IgniteInterruptedException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.internal.IgniteInterruptedCheckedException;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.jetbrains.annotations.Nullable;
/**
* Extension to standard {@link Runnable} interface. Adds proper details to be used
* with {@link Executor} implementations. Only for internal use.
*/
public abstract class GridWorker implements Runnable {
/** Ignite logger. */
protected final IgniteLogger log;
/** Thread name. */
private final String name;
/** */
private final String igniteInstanceName;
/** */
private final GridWorkerListener lsnr;
/** */
private volatile boolean finished;
/** Whether or not this runnable is cancelled. */
protected volatile boolean isCancelled;
/** Actual thread runner. */
private volatile Thread runner;
/** */
private final Object mux = new Object();
/**
* Creates new grid worker with given parameters.
*
* @param igniteInstanceName Name of the Ignite instance this runnable is used in.
* @param name Worker name. Note that in general thread name and worker (runnable) name are two
* different things. The same worker can be executed by multiple threads and therefore
* for logging and debugging purposes we separate the two.
* @param log Grid logger to be used.
* @param lsnr Listener for life-cycle events.
*/
protected GridWorker(String igniteInstanceName, String name, IgniteLogger log, @Nullable GridWorkerListener lsnr) {
assert name != null;
assert log != null;
this.igniteInstanceName = igniteInstanceName;
this.name = name;
this.lsnr = lsnr;
this.log = log;
}
/**
* Creates new grid worker with given parameters.
*
* @param igniteInstanceName Name of the Ignite instance this runnable is used in.
* @param name Worker name. Note that in general thread name and worker (runnable) name are two
* different things. The same worker can be executed by multiple threads and therefore
* for logging and debugging purposes we separate the two.
* @param log Grid logger to be used.
*/
protected GridWorker(@Nullable String igniteInstanceName, String name, IgniteLogger log) {
this(igniteInstanceName, name, log, null);
}
/** {@inheritDoc} */
@Override public final void run() {
// Runner thread must be recorded first as other operations
// may depend on it being present.
runner = Thread.currentThread();
if (log.isDebugEnabled())
log.debug("Grid runnable started: " + name);
try {
// Special case, when task gets cancelled before it got scheduled.
if (isCancelled)
runner.interrupt();
// Listener callback.
if (lsnr != null)
lsnr.onStarted(this);
body();
}
catch (IgniteInterruptedCheckedException e) {
if (log.isDebugEnabled())
log.debug("Caught interrupted exception: " + e);
}
catch (InterruptedException e) {
if (log.isDebugEnabled())
log.debug("Caught interrupted exception: " + e);
Thread.currentThread().interrupt();
}
// Catch everything to make sure that it gets logged properly and
// not to kill any threads from the underlying thread pool.
catch (Throwable e) {
if (!X.hasCause(e, InterruptedException.class) && !X.hasCause(e, IgniteInterruptedCheckedException.class) && !X.hasCause(e, IgniteInterruptedException.class))
U.error(log, "Runtime error caught during grid runnable execution: " + this, e);
else
U.warn(log, "Runtime exception occurred during grid runnable execution caused by thread interruption: " + e.getMessage());
if (e instanceof Error)
throw e;
}
finally {
synchronized (mux) {
finished = true;
mux.notifyAll();
}
cleanup();
if (lsnr != null)
lsnr.onStopped(this);
if (log.isDebugEnabled())
if (isCancelled)
log.debug("Grid runnable finished due to cancellation: " + name);
else if (runner.isInterrupted())
log.debug("Grid runnable finished due to interruption without cancellation: " + name);
else
log.debug("Grid runnable finished normally: " + name);
// Need to set runner to null, to make sure that
// further operations on this runnable won't
// affect the thread which could have been recycled
// by thread pool.
runner = null;
}
}
/**
* The implementation should provide the execution body for this runnable.
*
* @throws InterruptedException Thrown in case of interruption.
* @throws IgniteInterruptedCheckedException If interrupted.
*/
protected abstract void body() throws InterruptedException, IgniteInterruptedCheckedException;
/**
* Optional method that will be called after runnable is finished. Default
* implementation is no-op.
*/
protected void cleanup() {
/* No-op. */
}
/**
* @return Runner thread.
*/
public Thread runner() {
return runner;
}
/**
* Gets name of the Ignite instance this runnable belongs to.
*
* @return Name of the Ignite instance this runnable belongs to.
*/
public String igniteInstanceName() {
return igniteInstanceName;
}
/**
* Gets this runnable name.
*
* @return This runnable name.
*/
public String name() {
return name;
}
/**
* Cancels this runnable interrupting actual runner.
*/
public void cancel() {
if (log.isDebugEnabled())
log.debug("Cancelling grid runnable: " + this);
isCancelled = true;
Thread runner = this.runner;
// Cannot apply Future.cancel() because if we do, then Future.get() would always
// throw CancellationException and we would not be able to wait for task completion.
if (runner != null)
runner.interrupt();
}
/**
* Joins this runnable.
*
* @throws InterruptedException Thrown in case of interruption.
*/
public void join() throws InterruptedException {
if (log.isDebugEnabled())
log.debug("Joining grid runnable: " + this);
if ((runner == null && isCancelled) || finished)
return;
synchronized (mux) {
while (!finished)
mux.wait();
}
}
/**
* Tests whether or not this runnable is cancelled.
*
* @return {@code true} if this runnable is cancelled - {@code false} otherwise.
* @see Future#isCancelled()
*/
public boolean isCancelled() {
Thread runner = this.runner;
return isCancelled || (runner != null && runner.isInterrupted());
}
/**
* Tests whether or not this runnable is finished.
*
* @return {@code true} if this runnable is finished - {@code false} otherwise.
*/
public boolean isDone() {
return finished;
}
/** {@inheritDoc} */
@Override public String toString() {
Thread runner = this.runner;
return S.toString(GridWorker.class, this,
"hashCode", hashCode(),
"interrupted", (runner != null ? runner.isInterrupted() : "unknown"),
"runner", (runner == null ? "null" : runner.getName()));
}
}