/*
* (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.ecm.core.storage.sql;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
/**
* Helper class to run jobs in lock step in several threads.
* <p>
* You should override the {@link #job} method and make it execute code where blocks are wrapped in:
*
* <pre>
* if (thread(1)) {
* // code to execute only in thread 1
* }
* </pre>
*
* The parameter to {@link #thread} should be 1, 2, 3... depending on the thread you want this block to be executed in.
* <p>
* After you created the job instances, run the whole process by calling:
*
* <pre>
* LockStepJob.run(job1, job2, job3...);
* </pre>
*
* @since 5.7
*/
public abstract class LockStepJob implements Runnable {
protected int n;
protected CyclicBarrier barrier;
protected Throwable throwable;
/** Run the thread n (1, 2...). */
public void initialize(int n, CyclicBarrier barrier) {
this.n = n;
this.barrier = barrier;
}
@Override
public void run() {
try {
job();
} catch (Throwable t) {
// System.err.println("Exception in thread " +
// Thread.currentThread());
// t.printStackTrace();
throwable = t;
}
}
/**
* Method to call around each part to be executed by a single thread.
*
* @param which which thread is concerned
* @return {@code true} if the code should be executed
*/
public boolean thread(int which) throws Exception {
// sync all threads
barrier.await(30, TimeUnit.SECONDS); // throws on timeout
// execute this step if in the right thread
return which == n;
}
/**
* Override this to define the actual job to execute in multiple threads.
*/
public abstract void job() throws Exception;
public static void run(LockStepJob... jobs) throws Exception {
int n = jobs.length;
CyclicBarrier barrier = new CyclicBarrier(n);
for (int i = 0; i < n; i++) {
jobs[i].initialize(i + 1, barrier);
}
Thread[] threads = new Thread[n];
try {
for (int i = 0; i < n; i++) {
threads[i] = new Thread(jobs[i], "test-" + (i + 1));
threads[i].start();
}
for (int i = 0; i < n; i++) {
threads[i].join();
threads[i] = null;
}
Exception exception = new RuntimeException("failed");
for (int i = 0; i < n; i++) {
Throwable t = jobs[i].throwable;
if (t != null) {
exception.addSuppressed(t);
}
}
if (exception.getSuppressed().length > 0) {
throw exception;
}
} finally {
// error condition recovery
for (int i = 0; i < n; i++) {
if (threads[i] != null) {
threads[i].interrupt();
}
}
}
}
}