blob: 7fa7dcbd5466262cf934da86c546261dcdf2ecd5 [file] [log] [blame]
/*
* 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.cassandra.concurrent;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.compaction.CompactionInterruptedException;
import org.apache.cassandra.utils.Closeable;
import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.WithResources;
/**
* Standardised handling of failures during execution - mostly this involves invoking a thread's
* {@link java.lang.Thread.UncaughtExceptionHandler} or
* {@link JVMStabilityInspector#uncaughtException(Thread, Throwable)},
* with special handling for {@link CompactionInterruptedException}.
* This class also provides wrappers for {@link WithResources} with {@link Runnable} and {@link Callable}.
*/
public class ExecutionFailure
{
private static final Logger logger = LoggerFactory.getLogger(ExecutionFailure.class);
/**
* Invoke the relevant {@link java.lang.Thread.UncaughtExceptionHandler},
* ignoring (except for logging) any {@link CompactionInterruptedException}
*/
public static void handle(Throwable t)
{
try
{
if (t instanceof CompactionInterruptedException)
{
// TODO: should we check to see there aren't nested CompactionInterruptedException?
logger.info(t.getMessage());
if (t.getSuppressed() != null && t.getSuppressed().length > 0)
logger.warn("Interruption of compaction encountered exceptions:", t);
else
logger.trace("Full interruption stack trace:", t);
}
else
{
Thread thread = Thread.currentThread();
Thread.UncaughtExceptionHandler handler = thread.getUncaughtExceptionHandler();
if (handler == null)
handler = JVMStabilityInspector::uncaughtException;
handler.uncaughtException(thread, t);
}
}
catch (Throwable shouldNeverHappen)
{
logger.error("Unexpected error while handling unexpected error", shouldNeverHappen);
}
}
/**
* See {@link #propagating(WithResources, Runnable)}
*/
static Runnable propagating(Runnable wrap)
{
return wrap instanceof FutureTask<?> ? wrap : propagating(WithResources.none(), wrap);
}
/**
* In the case of plain executions, we want to handle exceptions without the full {@link FutureTask} machinery
* while still propagating the exception to the encapsulating Future
*/
static Runnable propagating(WithResources withResources, Runnable wrap)
{
return enforceOptions(withResources, wrap, true);
}
/**
* See {@link #suppressing(WithResources, Runnable)}
*/
static Runnable suppressing(Runnable wrap)
{
return wrap instanceof FutureTask<?> ? wrap : suppressing(WithResources.none(), wrap);
}
/**
* In the case of scheduled periodic tasks, we don't want exceptions propagating to cancel the recurring execution.
*/
static Runnable suppressing(WithResources withResources, Runnable wrap)
{
return enforceOptions(withResources, wrap, false);
}
/**
* Encapsulate the execution, propagating or suppressing any exceptions as requested.
*
* note that if {@code wrap} is a {@link java.util.concurrent.Future} its exceptions may not be captured,
* however the codebase should be using our internal {@link Future} variants which handle exceptions in the
* desired way.
*/
private static Runnable enforceOptions(WithResources withResources, Runnable wrap, boolean propagate)
{
return new Runnable()
{
@Override
public void run()
{
try (Closeable close = withResources.get())
{
wrap.run();
}
catch (Throwable t)
{
handle(t);
if (propagate)
throw t;
}
}
@Override
public String toString()
{
return wrap.toString();
}
};
}
/**
* See {@link #enforceOptions(WithResources, Callable)}
*/
static <V> Callable<V> propagating(Callable<V> wrap)
{
return enforceOptions(WithResources.none(), wrap);
}
/**
* In the case of non-recurring scheduled tasks, we want to handle exceptions without the full {@link FutureTask}
* machinery, while still propagating the exception to the encapsulating Future
*/
static <V> Callable<V> enforceOptions(WithResources withResources, Callable<V> wrap)
{
return new Callable<V>()
{
@Override
public V call() throws Exception
{
try (Closeable close = withResources.get())
{
return wrap.call();
}
catch (Throwable t)
{
handle(t);
throw t;
}
}
@Override
public String toString()
{
return wrap.toString();
}
};
}
}