/* * Copyright 2006-2012 Amazon Technologies, Inc. or its affiliates. * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks * of Amazon Technologies, Inc. or its affiliates. All rights reserved. * * Licensed 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 com.amazon.carbonado.spi; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** * A convenience class for repositories to run dynamic repairs in separate * threads. When a repository detects a consistency error during a user * operation, it should not perform the repair in the same thread. * * <p>If the repair was initiated by an exception, but the original exception * is re-thrown, a transaction exit will rollback the repair! Executing the * repair in a separate thread allows it to wait until the transaction has * exited. * * <p>Other kinds of inconsistencies might be detected during cursor * iteration. The repair will need to acquire write locks, but the open cursor * might not allow that, resulting in deadlock. Executing the repair in a * separate thread allows it to wait until the cursor has released locks. * * <p>This class keeps thread-local references to single-threaded executors. In * other words, each user thread has at most one associated repair thread. Each * repair thread has a fixed size queue, and they exit when they are idle. If * the queue is full, newly added repair tasks are silently discarded. * * <p>The following system properties are supported: * * <ul> * <li>com.amazon.carbonado.spi.RepairExecutor.keepAliveSeconds (default is 10) * <li>com.amazon.carbonado.spi.RepairExecutor.queueSize (default is 10000) * </ul> * * @author Brian S O'Neill */ public class RepairExecutor { static final ThreadLocal<RepairExecutor> cExecutor; static { final int keepAliveSeconds = Integer.getInteger ("com.amazon.carbonado.spi.RepairExecutor.keepAliveSeconds", 10); final int queueSize = Integer.getInteger ("com.amazon.carbonado.spi.RepairExecutor.queueSize", 10000); cExecutor = new ThreadLocal<RepairExecutor>() { @Override protected RepairExecutor initialValue() { return new RepairExecutor(keepAliveSeconds, queueSize); } }; } public static void execute(Runnable repair) { cExecutor.get().executeIt(repair); } /** * Waits for repairs that were executed from the current thread to finish. * * @return true if all repairs are finished */ public static boolean waitForRepairsToFinish(long timeoutMillis) throws InterruptedException { return cExecutor.get().waitToFinish(timeoutMillis); } private final int mKeepAliveSeconds; private BlockingQueue<Runnable> mQueue; private Worker mWorker; private boolean mIdle = true; private RepairExecutor(int keepAliveSeconds, int queueSize) { mKeepAliveSeconds = keepAliveSeconds; mQueue = new LinkedBlockingQueue<Runnable>(queueSize); } private synchronized void executeIt(Runnable repair) { mQueue.offer(repair); if (mWorker == null) { mWorker = new Worker(); mWorker.start(); } } private synchronized boolean waitToFinish(long timeoutMillis) throws InterruptedException { if (mIdle && mQueue.size() == 0) { return true; } if (mWorker == null) { // The worker should never be null if the queue has elements. mWorker = new Worker(); mWorker.start(); } if (timeoutMillis != 0) { if (timeoutMillis < 0) { while (!mIdle || mQueue.size() > 0) { wait(); } } else { long start = System.currentTimeMillis(); while (timeoutMillis > 0 && (!mIdle || mQueue.size() > 0)) { wait(timeoutMillis); long now = System.currentTimeMillis(); timeoutMillis -= (now - start); start = now; } } } return mQueue.size() == 0; } Runnable dequeue() throws InterruptedException { while (true) { synchronized (this) { mIdle = true; // Only one wait condition, so okay to not call notifyAll. notify(); } Runnable task = mQueue.poll(mKeepAliveSeconds, TimeUnit.SECONDS); synchronized (this) { if (task != null) { mIdle = false; return task; } if (mQueue.size() == 0) { // Only one wait condition, so okay to not call notifyAll. notify(); mWorker = null; return null; } } } } private class Worker extends Thread { Worker() { setDaemon(true); setName(Thread.currentThread().getName() + " (repository repair)"); } @Override public void run() { while (true) { try { Runnable task = dequeue(); if (task == null) { break; } task.run(); } catch (InterruptedException e) { break; } catch (ThreadDeath e) { break; } catch (Throwable e) { try { Thread t = Thread.currentThread(); t.getUncaughtExceptionHandler().uncaughtException(t, e); } catch (ThreadDeath e2) { break; } catch (Throwable e2) { // Ignore exceptions thrown while reporting exceptions. } } } } } }