/* * 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.nifi.controller.repository.claim; import static org.junit.Assert.assertEquals; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; public class TestStandardResourceClaimManager { @Test @Ignore("Unit test was created to repeat a concurrency bug in StandardResourceClaimManager. " + "However, now that the concurrency bug has been fixed, the test will deadlock. Leaving here for now in case it's valuable before the commit is pushed") public void testIncrementAndDecrementThreadSafety() throws InterruptedException { final AtomicBoolean waitToRemove = new AtomicBoolean(true); final CountDownLatch decrementComplete = new CountDownLatch(1); final StandardResourceClaimManager manager = new StandardResourceClaimManager() { @Override protected void removeClaimantCount(final ResourceClaim claim) { decrementComplete.countDown(); while (waitToRemove.get()) { try { Thread.sleep(10L); } catch (final InterruptedException ie) { Assert.fail("Interrupted while waiting to remove claimant count"); } } super.removeClaimantCount(claim); } }; final ResourceClaim resourceClaim = manager.newResourceClaim("container", "section", "id", false, false); assertEquals(1, manager.incrementClaimantCount(resourceClaim)); // increment claimant count to 1. assertEquals(1, manager.getClaimantCount(resourceClaim)); // Decrement the claimant count. This should decrement the count to 0. However, we have 'waitToRemove' set to true, // so the manager will not actually remove the claimant count (or return from this method) until we set 'waitToRemove' // to false. We do this so that we can increment the claimant count in a separate thread. Because we will be incrementing // the count in 1 thread and decrementing it in another thread, the end result should be that the claimant count is still // at 1. final Runnable decrementCountRunnable = new Runnable() { @Override public void run() { manager.decrementClaimantCount(resourceClaim); } }; final Runnable incrementCountRunnable = new Runnable() { @Override public void run() { // Wait until the count has been decremented try { decrementComplete.await(); } catch (Exception e) { e.printStackTrace(); Assert.fail(e.toString()); } // Increment the claimant count manager.incrementClaimantCount(resourceClaim); // allow the 'decrement Thread' to complete waitToRemove.set(false); } }; // Start the threads so that the claim count is incremented and decremented at the same time final Thread decrementThread = new Thread(decrementCountRunnable); final Thread incrementThread = new Thread(incrementCountRunnable); decrementThread.start(); incrementThread.start(); // Wait for both threads to complete incrementThread.join(); decrementThread.join(); // claimant count should still be 1, since 1 thread incremented it and 1 thread decremented it! assertEquals(1, manager.getClaimantCount(resourceClaim)); } }