/* * 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.sling.discovery.commons.providers.base; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import java.util.Random; import java.util.UUID; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.ReentrantLock; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.sling.commons.testing.junit.categories.Slow; import org.apache.sling.discovery.TopologyEvent; import org.apache.sling.discovery.commons.providers.DefaultClusterView; import org.apache.sling.discovery.commons.providers.DummyTopologyView; import org.apache.sling.discovery.commons.providers.EventHelper; import org.junit.Test; import org.junit.experimental.categories.Category; @Category(Slow.class) public class TestSlowViewStateManager extends TestViewStateManager { /** does couple loops randomly calling handleChanging() (or not) and then handleNewView(). * Note: random is passed to allow customizing and not hardcoding this method to a particular random * @throws InterruptedException **/ protected void randomEventLoop(final Random random, DummyListener... listeners) throws InterruptedException { TestHelper.randomEventLoop(mgr, null, 100, -1, random, listeners); } @Category(Slow.class) //TODO test takes env 10sec @Test public void testClusterSyncService_withConcurrency() throws Exception { final org.apache.log4j.Logger commonsLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery.commons.providers"); final org.apache.log4j.Level logLevel = commonsLogger.getLevel(); commonsLogger.setLevel(Level.INFO); // change here to DEBUG in case of issues with this test final Semaphore serviceSemaphore = new Semaphore(0); final Semaphore testSemaphore = new Semaphore(0); final ReentrantLock lock = new ReentrantLock(); final ClusterSyncServiceWithSemaphore cs = new ClusterSyncServiceWithSemaphore(lock, serviceSemaphore ); mgr = new ViewStateManagerImpl(lock, cs); final DummyListener listener = new DummyListener(); mgr.bind(listener); TestHelper.assertNoEvents(listener); mgr.handleActivated(); TestHelper.assertNoEvents(listener); final String slingId1 = UUID.randomUUID().toString(); final String slingId2 = UUID.randomUUID().toString(); final String slingId3 = UUID.randomUUID().toString(); final String clusterId = UUID.randomUUID().toString(); final DefaultClusterView cluster = new DefaultClusterView(clusterId); final DummyTopologyView view1 = new DummyTopologyView() .addInstance(slingId1, cluster, true, true) .addInstance(slingId2, cluster, false, false) .addInstance(slingId3, cluster, false, false); final DummyTopologyView view2 = DummyTopologyView.clone(view1).removeInstance(slingId2); final DummyTopologyView view3 = DummyTopologyView.clone(view1).removeInstance(slingId2).removeInstance(slingId3); async(new Runnable() { public void run() { mgr.handleNewView(view1); } }); Thread.sleep(1000); TestHelper.assertNoEvents(listener); assertEquals("should have one thread now waiting", 1, serviceSemaphore.getQueueLength()); serviceSemaphore.release(1); // release the first one only Thread.sleep(1000); assertEvents(listener, EventHelper.newInitEvent(view1)); mgr.handleChanging(); assertEquals(0, mgr.waitForAsyncEvents(500)); assertEvents(listener, EventHelper.newChangingEvent(view1)); async(new Runnable() { public void run() { mgr.handleNewView(view2); } }); logger.debug("run: waiting 1sec"); Thread.sleep(1000); logger.debug("run: asserting no events"); TestHelper.assertNoEvents(listener); assertEquals("should have one thread now waiting", 1, serviceSemaphore.getQueueLength()); assertFalse("should not be locked", lock.isLocked()); logger.debug("run: issuing a second event"); // before releasing, issue another event, lets do a combination of changing/changed async(new Runnable() { public void run() { logger.debug("run2: calling handleChanging..."); mgr.handleChanging(); try { logger.debug("run2: done with handleChanging, acquiring testSemaphore..."); testSemaphore.acquire(); logger.debug("run2: calling handleNewView..."); mgr.handleNewView(view3); logger.debug("run2: done with handleNewView..."); } catch (InterruptedException e) { // fail logger.error("interrupted: "+e, e); } } }); logger.debug("run: waiting 1sec"); Thread.sleep(1000); int remainingAsyncEvents = mgr.waitForAsyncEvents(2000); logger.info("run: result of waitForAsyncEvent is: "+remainingAsyncEvents); assertEquals("should have one thread now waiting", 1, serviceSemaphore.getQueueLength()); assertEquals("should be acquiring (by thread2)", 1, testSemaphore.getQueueLength()); // releasing the testSemaphore testSemaphore.release(); logger.debug("run: waiting 1sec"); Thread.sleep(1000); assertEquals("should have two async events now in the queue or being sent", 2, mgr.waitForAsyncEvents(500)); assertEquals("but should only have 1 thread actually sitting on the semaphore waiting", 1, serviceSemaphore.getQueueLength()); logger.debug("run: releasing consistencyService"); serviceSemaphore.release(1); // release the first one only logger.debug("run: waiting 1sec"); Thread.sleep(1000); assertFalse("should not be locked", lock.isLocked()); TestHelper.assertNoEvents(listener); // this should not have triggered any event serviceSemaphore.release(1); // then release the 2nd one logger.debug("run: waiting 1sec"); Thread.sleep(1000); logger.debug("run: asserting 1 event"); final TopologyEvent changedEvent = EventHelper.newChangedEvent(view1, view3); assertEvents(listener, changedEvent); commonsLogger.setLevel(Level.INFO); // back to default } }