/*
* 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.activemq.artemis.tests.integration.cluster.reattach;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException;
import org.apache.activemq.artemis.api.core.client.ClientSession;
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
import org.apache.activemq.artemis.api.core.client.ServerLocator;
import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryInternal;
import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
import org.apache.activemq.artemis.core.protocol.core.impl.RemotingConnectionImpl;
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnector;
import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
public abstract class MultiThreadReattachSupportTestBase extends ActiveMQTestBase {
private final IntegrationTestLogger log = IntegrationTestLogger.LOGGER;
private Timer timer;
protected abstract void start() throws Exception;
protected abstract void stop() throws Exception;
protected abstract ServerLocator createLocator() throws Exception;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
timer = new Timer();
}
@Override
@After
public void tearDown() throws Exception {
timer.cancel();
timer = null;
super.tearDown();
}
protected boolean shouldFail() {
return true;
}
protected void runMultipleThreadsFailoverTest(final RunnableT runnable,
final int numThreads,
final int numIts,
final boolean failOnCreateConnection,
final long failDelay) throws Exception {
for (int its = 0; its < numIts; its++) {
log.info("Beginning iteration " + its);
start();
final ServerLocator locator = createLocator();
final ClientSessionFactoryInternal sf = (ClientSessionFactoryInternal) createSessionFactory(locator);
final ClientSession session = addClientSession(sf.createSession(false, true, true));
Failer failer = startFailer(failDelay, session, failOnCreateConnection);
class Runner extends Thread {
private volatile Throwable throwable;
private final RunnableT test;
private final int threadNum;
Runner(final RunnableT test, final int threadNum) {
this.test = test;
this.threadNum = threadNum;
}
@Override
public void run() {
try {
test.run(sf, threadNum);
} catch (Throwable t) {
throwable = t;
log.error("Failed to run test", t);
// Case a failure happened here, it should print the Thread dump
// Sending it to System.out, as it would show on the Tests report
System.out.println(ActiveMQTestBase.threadDump(" - fired by MultiThreadRandomReattachTestBase::runTestMultipleThreads (" + t.getLocalizedMessage() +
")"));
}
}
}
do {
List<Runner> threads = new ArrayList<>();
for (int i = 0; i < numThreads; i++) {
Runner runner = new Runner(runnable, i);
threads.add(runner);
runner.start();
}
for (Runner thread : threads) {
thread.join();
if (thread.throwable != null) {
throw new Exception("Exception on thread " + thread, thread.throwable);
}
}
runnable.checkFail();
} while (!failer.isExecuted());
InVMConnector.resetFailures();
session.close();
locator.close();
Assert.assertEquals(0, sf.numSessions());
Assert.assertEquals(0, sf.numConnections());
sf.close();
stop();
}
}
// Private -------------------------------------------------------
private Failer startFailer(final long time, final ClientSession session, final boolean failOnCreateConnection) {
Failer failer = new Failer(session, failOnCreateConnection);
// This is useful for debugging.. just change shouldFail to return false, and Failer will not be executed
if (shouldFail()) {
timer.schedule(failer, (long) (time * Math.random()), 100);
}
return failer;
}
// Inner classes -------------------------------------------------
protected abstract class RunnableT extends Thread {
private volatile String failReason;
private volatile Throwable throwable;
public void setFailed(final String reason, final Throwable throwable) {
failReason = reason;
this.throwable = throwable;
}
public void checkFail() {
if (throwable != null) {
log.error("Test failed: " + failReason, throwable);
}
if (failReason != null) {
Assert.fail(failReason);
}
}
public abstract void run(final ClientSessionFactory sf, final int threadNum) throws Exception;
}
private class Failer extends TimerTask {
private final ClientSession session;
private boolean executed;
private final boolean failOnCreateConnection;
private Failer(final ClientSession session, final boolean failOnCreateConnection) {
this.session = session;
this.failOnCreateConnection = failOnCreateConnection;
}
@Override
public synchronized void run() {
log.info("** Failing connection");
RemotingConnectionImpl conn = (RemotingConnectionImpl) ((ClientSessionInternal) session).getConnection();
if (failOnCreateConnection) {
InVMConnector.numberOfFailures = 1;
InVMConnector.failOnCreateConnection = true;
} else {
conn.fail(new ActiveMQNotConnectedException("blah"));
}
log.info("** Fail complete");
cancel();
executed = true;
}
public synchronized boolean isExecuted() {
return executed;
}
}
}