/* * Copyright (c) 2008-2017 the original author or authors. * * 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.cometd.tests; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.cometd.bayeux.Channel; import org.cometd.bayeux.Message; import org.cometd.bayeux.client.ClientSessionChannel; import org.cometd.client.BayeuxClient; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class SimulatedNetworkFailureTest extends AbstractClientServerTest { private long timeout = 10000; private long maxInterval = 8000; private long sweepInterval = 1000; public SimulatedNetworkFailureTest(Transport transport) { super(transport); } @Before public void setUp() throws Exception { Map<String, String> options = serverOptions(); options.put("timeout", String.valueOf(timeout)); options.put("maxInterval", String.valueOf(maxInterval)); options.put("sweepIntervalMs", String.valueOf(sweepInterval)); startServer(options); } @Test public void testClientShortNetworkFailure() throws Exception { final CountDownLatch connectLatch = new CountDownLatch(2); final AtomicReference<CountDownLatch> publishLatch = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); TestBayeuxClient client = new TestBayeuxClient() { @Override protected boolean sendConnect() { boolean result = super.sendConnect(); connectLatch.countDown(); return result; } }; client.getChannel(Channel.META_CONNECT).addListener(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { boolean wasConnected = connected.get(); connected.set(message.isSuccessful()); if (!wasConnected && connected.get()) { System.err.printf("BayeuxClient connected %s%n", message); } else if (wasConnected && !connected.get()) { System.err.printf("BayeuxClient unconnected %s%n", message); } } }); String channelName = "/test"; ClientSessionChannel channel = client.getChannel(channelName); channel.addListener(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { if (!message.isSuccessful()) { publishLatch.get().countDown(); } } }); client.handshake(); assertTrue(connectLatch.await(5, TimeUnit.SECONDS)); // Wait for a second connect to be issued Thread.sleep(1000); long networkDown = maxInterval / 2; client.setNetworkDown(networkDown); // Publish, it must succeed publishLatch.set(new CountDownLatch(1)); channel.publish(new HashMap<>()); assertTrue(publishLatch.get().await(5, TimeUnit.SECONDS)); // Wait for the connect to return // We already slept a bit before, so we are sure that the connect returned Thread.sleep(timeout); // Now we are simulating the network is down // Be sure we are disconnected assertFalse(connected.get()); // Another publish, it must fail publishLatch.set(new CountDownLatch(1)); channel.publish(new HashMap<>()); assertTrue(publishLatch.get().await(5, TimeUnit.SECONDS)); // Sleep to allow the next connect to be issued Thread.sleep(networkDown); // Now another connect has been sent (delayed by 'networkDown' ms) Thread.sleep(timeout); assertTrue(connected.get()); // We should be able to publish now publishLatch.set(new CountDownLatch(1)); channel.publish(new HashMap<>()); assertFalse(publishLatch.get().await(1, TimeUnit.SECONDS)); disconnectBayeuxClient(client); } @Test public void testClientLongNetworkFailure() throws Exception { final CountDownLatch connectLatch = new CountDownLatch(2); final CountDownLatch handshakeLatch = new CountDownLatch(2); final AtomicReference<CountDownLatch> publishLatch = new AtomicReference<>(); final AtomicBoolean connected = new AtomicBoolean(false); TestBayeuxClient client = new TestBayeuxClient() { @Override protected boolean sendConnect() { boolean result = super.sendConnect(); connectLatch.countDown(); return result; } }; client.getChannel(Channel.META_HANDSHAKE).addListener(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { if (message.isSuccessful()) { handshakeLatch.countDown(); } } }); client.getChannel(Channel.META_CONNECT).addListener(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { boolean wasConnected = connected.get(); connected.set(message.isSuccessful()); if (!wasConnected && connected.get()) { System.err.println("BayeuxClient connected"); } else if (wasConnected && !connected.get()) { System.err.println("BayeuxClient unconnected"); } } }); String channelName = "/test"; ClientSessionChannel channel = client.getChannel(channelName); channel.addListener(new ClientSessionChannel.MessageListener() { @Override public void onMessage(ClientSessionChannel channel, Message message) { if (!message.isSuccessful()) { publishLatch.get().countDown(); } } }); client.handshake(); assertTrue(connectLatch.await(5, TimeUnit.SECONDS)); // Wait for a second connect to be issued Thread.sleep(1000); // Add some margin since the session is swept every 'sweepIntervalMs' long networkDown = maxInterval + 3 * sweepInterval; client.setNetworkDown(networkDown); // Publish, it must succeed publishLatch.set(new CountDownLatch(1)); channel.publish(new HashMap<>()); assertTrue(publishLatch.get().await(5, TimeUnit.SECONDS)); // Wait for the connect to return // We already slept a bit before, so we are sure that the connect returned Thread.sleep(timeout); // Now we are simulating the network is down // Be sure we are disconnected assertFalse(connected.get()); // Another publish, it must fail publishLatch.set(new CountDownLatch(1)); channel.publish(new HashMap<>()); assertTrue(publishLatch.get().await(5, TimeUnit.SECONDS)); // Sleep to allow the next connect to be issued Thread.sleep(networkDown); // Now another connect has been sent (delayed by 'networkDown' ms) // but the server expired the client, so we handshake again assertTrue(handshakeLatch.await(5, TimeUnit.SECONDS)); // We should be able to publish now publishLatch.set(new CountDownLatch(1)); channel.publish(new HashMap<>()); assertFalse(publishLatch.get().await(1, TimeUnit.SECONDS)); disconnectBayeuxClient(client); } private class TestBayeuxClient extends BayeuxClient { private long networkDown; private TestBayeuxClient() { super(cometdURL, newClientTransport(null)); } public void setNetworkDown(long time) { this.networkDown = time; System.err.println("Set network down"); } @Override protected void processConnect(Message.Mutable connect) { if (networkDown > 0) { connect.setSuccessful(false); } super.processConnect(connect); } @Override protected boolean scheduleConnect(long interval, long backOff) { if (networkDown > 0) { backOff = networkDown; } return super.scheduleConnect(interval, backOff); } @Override protected boolean sendConnect() { if (networkDown > 0) { networkDown = 0; System.err.println("Reset network down"); } return super.sendConnect(); } @Override protected void enqueueSend(Message.Mutable message) { if (networkDown > 0) { List<Message.Mutable> messages = new ArrayList<>(1); messages.add(message); messagesFailure(null, messages); } else { super.enqueueSend(message); } } } }