/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.test.mt.util;
import com.foundationdb.server.service.dxl.OnlineDDLMonitor;
import com.foundationdb.server.test.mt.OnlineCreateTableAsMT;
import com.foundationdb.server.test.mt.util.ThreadMonitor.Stage;
import com.foundationdb.sql.server.ServerSession;
import com.foundationdb.sql.types.DataTypeDescriptor;
import com.foundationdb.util.RandomRule;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import org.junit.ClassRule;
import org.junit.Rule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class ConcurrentTestBuilderImpl implements ConcurrentTestBuilder
{
private static final Logger LOG = LoggerFactory.getLogger(ConcurrentTestBuilderImpl.class);
private final Map<String,ThreadState> threadStateMap = new LinkedHashMap<>();
private final ListMultimap<String,String> syncToThreadState = ArrayListMultimap.create();
private String lastThreadName = null;
public static ConcurrentTestBuilder create() {
return new ConcurrentTestBuilderImpl();
}
//
// ConcurrentTestBuilder
//
@Override
public ConcurrentTestBuilder add(String name, OperatorCreator creator) {
add(name, new ThreadState(creator));
return this;
}
@Override
public ConcurrentTestBuilder mark(ThreadMonitor.Stage... stages) {
ThreadState state = getLastCreatorState(false);
List<ThreadMonitor.Stage> list = Arrays.asList(stages);
LOG.debug("mark {} at thread {}", lastThreadName, list);
state.threadStageMarks.addAll(list);
return this;
}
@Override
public List<MonitoredThread> build(ServiceHolder serviceHolder){
return build(serviceHolder, null, null, null);
}
@Override
public List<MonitoredThread> build(ServiceHolder serviceHolder, List<DataTypeDescriptor> descriptors,
List<String> columnNames, OnlineCreateTableAsBase.TestSession server) {
LOG.debug("build {}", threadStateMap.keySet());
Map<String,CyclicBarrier> barriers = new HashMap<>();
for(Entry<String,Collection<String>> entry : syncToThreadState.asMap().entrySet()) {
LOG.debug("barrier '{}' has parties {}", entry.getKey(), entry.getValue());
int parties = entry.getValue().size();
barriers.put(entry.getKey(), new CyclicBarrier(parties));
}
List<MonitoredThread> threads = new ArrayList<>();
for(Entry<String, ThreadState> entry : threadStateMap.entrySet()) {
String name = entry.getKey();
ThreadState state = entry.getValue();
StageMonitor monitor = new StageMonitor(barriers, state.threadStageToSyncName, state.onlineStageToSyncName);
final MonitoredThread thread;
if(state.creator != null) {
thread = new MonitoredOperatorThread(name,
serviceHolder,
state.creator,
monitor,
state.threadStageMarks,
state.retryOnRollback);
} else {
thread = new MonitoredDDLThread(name,
serviceHolder,
monitor,
state.threadStageMarks,
monitor,
state.onlineStageMarks,
state.schema,
state.ddl,
descriptors,
columnNames,
server);
}
threads.add(thread);
}
return threads;
}
@Override
public ConcurrentTestBuilder sync(String name, ThreadMonitor.Stage stage) {
return sync(lastThreadName, name, stage);
}
@Override
public ConcurrentTestBuilder rollbackRetry(boolean doRetry) {
ThreadState state = getThreadState(lastThreadName, false);
state.retryOnRollback = doRetry;
return this;
}
@Override
public ConcurrentTestBuilder sync(String testName, String syncName, Stage stage) {
LOG.debug("sync {}/{} on '{}'", new Object[] { testName, stage, syncName });
ThreadState state = getThreadState(testName, false);
String prev = state.threadStageToSyncName.put(stage, syncName);
if(prev != null) {
throw new IllegalArgumentException("Thread stage " + stage + " already latched to " + prev);
}
syncToThreadState.put(syncName, testName);
return this;
}
@Override
public ConcurrentTestBuilder add(String name, String schema, String ddl) {
add(name, new ThreadState(schema, ddl));
return this;
}
@Override
public ConcurrentTestBuilder mark(OnlineDDLMonitor.Stage... stages) {
ThreadState state = getLastCreatorState(true);
List<OnlineDDLMonitor.Stage> list = Arrays.asList(stages);
LOG.debug("mark {} at online {}", lastThreadName, list);
state.onlineStageMarks.addAll(list);
return this;
}
@Override
public ConcurrentTestBuilder sync(String name, OnlineDDLMonitor.Stage stage) {
return sync(lastThreadName, name, stage);
}
@Override
public ConcurrentTestBuilder sync(String testName, String syncName, OnlineDDLMonitor.Stage stage) {
LOG.debug("sync {}/{} on '{}'", new Object[] { testName, stage, syncName });
ThreadState state = getThreadState(testName, false);
String prev = state.onlineStageToSyncName.put(stage, syncName);
if(prev != null) {
throw new IllegalArgumentException("Online stage " + stage + " already latched to " + prev);
}
syncToThreadState.put(syncName, testName);
return this;
}
//
// Internal
//
private ConcurrentTestBuilderImpl() {
}
private void add(String name, ThreadState state) {
LOG.debug("add {}", name);
if(threadStateMap.containsKey(name)) {
throw new IllegalArgumentException("Thread already exists: " + name);
}
threadStateMap.put(name, state);
lastThreadName = name;
}
private ThreadState getLastCreatorState(boolean ddlRequired) {
if(lastThreadName == null) {
throw new IllegalStateException("No plans added");
}
return getThreadState(lastThreadName, ddlRequired);
}
private ThreadState getThreadState(String name, boolean ddlRequired) {
ThreadState state = threadStateMap.get(name);
if(state == null) {
throw new IllegalArgumentException("Unknown thread name: " + name);
}
if(ddlRequired && (state.ddl == null)) {
throw new IllegalStateException("Not a DDL thread");
}
return state;
}
private class ThreadState
{
public final OperatorCreator creator;
private final String schema;
private final String ddl;
public final Set<ThreadMonitor.Stage> threadStageMarks = new HashSet<>();
public final Map<ThreadMonitor.Stage,String> threadStageToSyncName = new HashMap<>();
public final Set<OnlineDDLMonitor.Stage> onlineStageMarks = new HashSet<>();
public final Map<OnlineDDLMonitor.Stage,String> onlineStageToSyncName = new HashMap<>();
public boolean retryOnRollback = true;
private ThreadState(OperatorCreator creator) {
this.creator = creator;
this.schema = this.ddl = null;
}
private ThreadState(String schema, String ddl) {
this.creator = null;
this.schema = schema;
this.ddl = ddl;
}
}
private static class StageMonitor implements ThreadMonitor, OnlineDDLMonitor
{
private final Map<String,CyclicBarrier> barriers;
private final Map<ThreadMonitor.Stage,String> threadStageToBarrier;
private final Map<OnlineDDLMonitor.Stage,String> onlineStageToBarrier;
private StageMonitor(Map<String, CyclicBarrier> barriers,
Map<ThreadMonitor.Stage, String> threadStageToBarrier,
Map<OnlineDDLMonitor.Stage, String> onlineStageToBarrier) {
this.barriers = barriers;
this.threadStageToBarrier = threadStageToBarrier;
this.onlineStageToBarrier = onlineStageToBarrier;
}
@Override
public void at(ThreadMonitor.Stage stage) throws InterruptedException, BrokenBarrierException {
delay(true, stage == DELAY_THREAD_STAGE);
String barrierName = threadStageToBarrier.get(stage);
atBarrier(barrierName);
delay(false, stage == DELAY_THREAD_STAGE);
}
@Override
public void at(OnlineDDLMonitor.Stage stage) {
delay(true, stage == DELAY_DDL_STAGE);
String barrierName = onlineStageToBarrier.get(stage);
try {
atBarrier(barrierName);
} catch(Exception e) {
throw new RuntimeException(e);
}
delay(false, stage == DELAY_DDL_STAGE);
}
private void atBarrier(String barrierName) throws BrokenBarrierException, InterruptedException {
if(barrierName != null) {
CyclicBarrier barrier = barriers.get(barrierName);
barrier.await();
}
}
private void delay(boolean isBefore, boolean stageMatched) {
if((isBefore == DELAY_BEFORE) && stageMatched) {
try {
Thread.sleep(50);
} catch(InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
@ClassRule
public static final RandomRule randomRule = new RandomRule();
@Rule
public final RandomRule delayRule = randomRule;
@SafeVarargs
private static <T> T choose(T... values) {
return values[randomRule.getRandom().nextInt(values.length)];
}
public static final boolean DELAY_BEFORE = randomRule.getRandom().nextBoolean();
public static final OnlineDDLMonitor.Stage DELAY_DDL_STAGE = choose(OnlineDDLMonitor.Stage.values());
public static final ThreadMonitor.Stage DELAY_THREAD_STAGE = choose(ThreadMonitor.Stage.values());
}