/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.bus.client.tests;
import java.util.IdentityHashMap;
import java.util.Map;
import org.jboss.errai.bus.client.api.RetryInfo;
import org.jboss.errai.bus.client.api.TransportError;
import org.jboss.errai.bus.client.api.TransportErrorHandler;
import org.jboss.errai.bus.client.api.base.MessageBuilder;
import org.jboss.errai.bus.client.api.messaging.Message;
import org.jboss.errai.bus.client.api.messaging.MessageCallback;
import org.jboss.errai.bus.client.framework.transports.TransportHandler;
import org.jboss.errai.bus.common.AbstractErraiTest;
import org.jboss.errai.common.client.api.Assert;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Timer;
public class BusReconnectBackoffTest extends AbstractErraiTest {
private TransportErrorCounter transportErrorCounter;
@Override
public String getModuleName() {
return "org.jboss.errai.bus.ErraiBusTests";
}
@Override
protected void gwtSetUp() throws Exception {
super.gwtSetUp();
transportErrorCounter = new TransportErrorCounter();
bus.addTransportErrorHandler(transportErrorCounter);
}
@Override
protected void gwtTearDown() throws Exception {
bus.removeTransportErrorHandler(transportErrorCounter);
Runnable tearDown = new Runnable() {
@Override
public void run() {
}
};
changeSimulatedErrorCode(0, tearDown);
super.gwtTearDown();
}
public void testBusReconnectFrequencyWhenIdle() {
runAfterInit(90000, new Runnable() {
@Override
public void run() {
Runnable test = new Runnable() {
@Override
public void run() {
final long startTime = System.currentTimeMillis();
new Timer() {
@Override
public void run() {
final int totalRetries = transportErrorCounter.getTotalRetryCount();
final long elapsedClockTime = System.currentTimeMillis() - startTime;
double retriesPerSecond = totalRetries / (elapsedClockTime / 1000.0);
System.out.println(transportErrorCounter);
System.out.println(elapsedClockTime + "ms elapsed; retries per second is now " + retriesPerSecond);
assertTrue(retriesPerSecond < 3);
if (elapsedClockTime > 16000) {
finishTest();
cancel();
}
}
}.scheduleRepeating(2000);
}
};
changeSimulatedErrorCode(404, test);
}
});
}
public void testBusReconnectFrequencyWhenSending() {
runAfterInit(90000, new Runnable() {
@Override
public void run() {
Runnable test = new Runnable() {
public void run() {
MessageBuilder.createMessage().toSubject("TestService3").done()
.repliesTo(new MessageCallback() {
@Override
public void callback(Message message) {
System.out.println("I just totally got a " + message);
finishTest();
}
}).sendNowWith(bus);
final long startTime = System.currentTimeMillis();
new Timer() {
@Override
public void run() {
final int totalRetries = transportErrorCounter.getTotalRetryCount();
final long elapsedClockTime = System.currentTimeMillis() - startTime;
double retriesPerSecond = totalRetries / (elapsedClockTime / 1000.0);
System.out.println(transportErrorCounter);
System.out.println(elapsedClockTime + "ms elapsed; retries per second is now " + retriesPerSecond);
// NOTE TO MAINTAINERS: Hi! If this assertion fails, it could be
// because throttling is broken, but it might also be because
// transport handlers left over from the previous test have not been
// shut down properly, when the bus was reset between tests. Or they
// have been shut down but their stop() method isn't entirely
// effective. Especially suspect would be pending timers that don't
// get canceled when you call stop().
assertTrue("Retries per second is now " + retriesPerSecond, retriesPerSecond < 3);
if (elapsedClockTime > 16000) {
finishTest();
cancel();
}
}
}.scheduleRepeating(2000);
}
};
changeSimulatedErrorCode(404, test);
}
});
}
/**
* Sends a request to the server which sets the servlet filter to
* respond to further requests with the given HTTP status code.
*
* @param newCode
* The HTTP status code that will be sent in response to all
* subsequent ErraiBus requests.
* @para runnable
* The runnable to execute after the response was received.
*/
private void changeSimulatedErrorCode(int newCode, final Runnable runnable) {
RequestBuilder request = new RequestBuilder(RequestBuilder.GET, "errorSimulator.erraiBus?errorCode=" + newCode);
try {
request.setCallback(new RequestCallback() {
@Override
public void onResponseReceived(Request request, Response response) {
runnable.run();
}
@Override
public void onError(Request request, Throwable exception) {
throw new RuntimeException(exception);
}
});
request.send();
}
catch (RequestException e) {
throw new RuntimeException(e);
}
}
private class TransportErrorCounter implements TransportErrorHandler {
/**
* The bus uses more than one transport handler at a time, and RetryInfo
* can't be accumulated without knowledge of prior state. So we accumulate
* retry data by transport.
*/
private final Map<TransportHandler, StatsForHandler> statsPerHandler =
new IdentityHashMap<TransportHandler, BusReconnectBackoffTest.TransportErrorCounter.StatsForHandler>();
class StatsForHandler {
final TransportHandler handler;
long totalTimeBetweenRetries = 0;
int retries = 0;
int lastRetryCount = 0;
public StatsForHandler(TransportHandler handler) {
this.handler = Assert.notNull(handler);
}
void accumulate(TransportError error) {
if (error.getSource() != handler) {
throw new IllegalArgumentException("wrong handler. " + error.getSource() + " != " + handler);
}
RetryInfo retryInfo = error.getRetryInfo();
totalTimeBetweenRetries += Math.max(0, retryInfo.getDelayUntilNextRetry());
int retryCount = retryInfo.getRetryCount();
if (retryCount == 1 || retryCount == lastRetryCount + 1) {
retries++;
}
else {
retries += retryCount;
}
lastRetryCount = retryCount;
}
public long getTotalTimeBetweenRetries() {
return totalTimeBetweenRetries;
}
public int getRetries() {
return retries;
}
public int getLastRetryCount() {
return lastRetryCount;
}
@Override
public String toString() {
return "\nStatsForHandler [handler=" + handler + "@" + System.identityHashCode(handler)
+ ", totalTimeBetweenRetries=" + totalTimeBetweenRetries
+ ", retries=" + retries + ", lastRetryCount=" + lastRetryCount
+ "]";
}
}
@Override
public void onError(TransportError error) {
TransportHandler source = error.getSource();
StatsForHandler stats = statsPerHandler.get(source);
if (stats == null) {
stats = new StatsForHandler(source);
statsPerHandler.put(source, stats);
}
stats.accumulate(error);
}
/**
* Returns the sum of the rety attempts made by all transport handlers that have produced errors.
*/
public int getTotalRetryCount() {
int total = 0;
for (StatsForHandler stats : statsPerHandler.values()) {
total += stats.getRetries();
}
return total;
}
/**
* Returns the sum of the delay time between rety attempts made by all
* transport handlers that have produced errors.
*/
public long getTotalTimeBetweenRetries() {
long total = 0;
for (StatsForHandler stats : statsPerHandler.values()) {
total += stats.getTotalTimeBetweenRetries();
}
return total;
}
@Override
public String toString() {
return "TransportErrorCounter [statsPerHandler=" + statsPerHandler + "]";
}
}
}