| /* |
| * 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. |
| */ |
| |
| namespace Apache.Ignite.Core.Tests |
| { |
| using System; |
| using System.Collections.Concurrent; |
| using System.Threading; |
| using System.Threading.Tasks; |
| using Apache.Ignite.Core.Common; |
| using Apache.Ignite.Core.Configuration; |
| using NUnit.Framework; |
| |
| /// <summary> |
| /// Tests for <see cref="IIgniteLock"/>. |
| /// </summary> |
| public class IgniteLockTests : TestBase |
| { |
| /// <summary> |
| /// Initializes a new instance of <see cref="IgniteLockTests"/> class. |
| /// </summary> |
| public IgniteLockTests() : base(2) |
| { |
| // No-op. |
| } |
| |
| /// <summary> |
| /// Tests state changes: unlocked -> locked -> disposed. |
| /// </summary> |
| [Test] |
| public void TestStateChanges() |
| { |
| var lck = Ignite.GetOrCreateLock(TestUtils.TestName); |
| var cfg = lck.Configuration; |
| |
| Assert.IsFalse(cfg.IsFailoverSafe); |
| Assert.IsFalse(cfg.IsFair); |
| Assert.AreEqual(TestUtils.TestName, cfg.Name); |
| |
| Assert.False(lck.IsEntered()); |
| Assert.False(lck.IsBroken()); |
| |
| Assert.IsTrue(lck.TryEnter()); |
| |
| Assert.IsTrue(lck.IsEntered()); |
| Assert.False(lck.IsBroken()); |
| |
| lck.Exit(); |
| |
| Assert.False(lck.IsEntered()); |
| Assert.False(lck.IsBroken()); |
| |
| lck.Remove(); |
| |
| Assert.IsTrue(lck.IsRemoved()); |
| } |
| |
| /// <summary> |
| /// Tests that thread blocks on Enter until lock is released. |
| /// </summary> |
| [Test] |
| public void TestEnterBlocksWhenLockedByAnotherThread() |
| { |
| long state = 0; |
| |
| var lock1 = Ignite.GetOrCreateLock(TestUtils.TestName); |
| lock1.Enter(); |
| |
| // ReSharper disable once AccessToModifiedClosure |
| var task = Task.Factory.StartNew(() => |
| { |
| var lock2 = Ignite.GetOrCreateLock(TestUtils.TestName); |
| Interlocked.Increment(ref state); |
| lock2.Enter(); |
| Interlocked.Increment(ref state); |
| lock2.Exit(); |
| Interlocked.Increment(ref state); |
| }); |
| |
| TestUtils.WaitForTrueCondition(() => Interlocked.Read(ref state) == 1); |
| Assert.AreEqual(1, Interlocked.Read(ref state)); |
| |
| lock1.Exit(); |
| task.Wait(); |
| Assert.AreEqual(3, Interlocked.Read(ref state)); |
| } |
| |
| /// <summary> |
| /// Tests that Exit throws a meaningful exception when lock in not entered. |
| /// </summary> |
| [Test] |
| public void TestExitThrowsCorrectExceptionWhenNotEntered() |
| { |
| var lock1 = Ignite.GetOrCreateLock(TestUtils.TestName); |
| |
| var ex = Assert.Throws<SynchronizationLockException>(() => lock1.Exit()); |
| var innerEx = ex.InnerException as JavaException; |
| |
| Assert.IsNotNull(innerEx); |
| Assert.AreEqual("java.lang.IllegalMonitorStateException", innerEx.JavaClassName); |
| } |
| |
| /// <summary> |
| /// Tests that TryEnter succeeds when lock is not taken. |
| /// </summary> |
| [Test] |
| public void TestTryEnterReturnsTrueWhenUnlocked() |
| { |
| var lock1 = Ignite.GetOrCreateLock(TestUtils.TestName); |
| |
| Assert.IsTrue(lock1.TryEnter()); |
| Assert.IsTrue(lock1.TryEnter(TimeSpan.Zero)); |
| Assert.IsTrue(lock1.TryEnter(TimeSpan.FromMilliseconds(50))); |
| |
| lock1.Exit(); |
| } |
| |
| /// <summary> |
| /// Tests that TryEnter fails when lock is taken by another thread. |
| /// </summary> |
| [Test] |
| public void TestTryEnterReturnsFalseWhenLocked() |
| { |
| var lock1 = Ignite.GetOrCreateLock(TestUtils.TestName); |
| var lock2 = Ignite.GetOrCreateLock(TestUtils.TestName); |
| |
| lock1.Enter(); |
| |
| Task.Factory.StartNew(() => |
| { |
| Assert.IsFalse(lock2.TryEnter()); |
| Assert.IsFalse(lock2.TryEnter(TimeSpan.Zero)); |
| Assert.IsFalse(lock2.TryEnter(TimeSpan.FromMilliseconds(50))); |
| }).Wait(); |
| |
| lock1.Exit(); |
| } |
| |
| /// <summary> |
| /// Tests that lock can be entered multiple times by the same thread. |
| /// </summary> |
| [Test] |
| public void TestReentrancy() |
| { |
| const int count = 10; |
| var lock1 = Ignite.GetOrCreateLock(TestUtils.TestName); |
| |
| for (var i = 0; i < count; i++) |
| { |
| lock1.Enter(); |
| Assert.IsTrue(lock1.IsEntered()); |
| } |
| |
| for (var i = 0; i < count; i++) |
| { |
| Assert.IsTrue(lock1.IsEntered()); |
| lock1.Exit(); |
| } |
| |
| Assert.IsFalse(lock1.IsEntered()); |
| } |
| |
| /// <summary> |
| /// Tests that removed lock throws correct exception. |
| /// </summary> |
| [Test] |
| public void TestRemovedLockThrowsIgniteException() |
| { |
| var lock1 = Ignite.GetOrCreateLock(TestUtils.TestName); |
| var lock2 = Ignite2.GetOrCreateLock(TestUtils.TestName); |
| |
| Assert.IsFalse(lock2.IsEntered()); |
| lock1.Remove(); |
| |
| var ex = Assert.Throws<IgniteException>(() => lock2.Enter()); |
| Assert.AreEqual("Failed to find reentrant lock with given name: " + lock2.Configuration.Name, ex.Message); |
| } |
| |
| /// <summary> |
| /// Tests that removed lock throws correct exception. |
| /// </summary> |
| [Test] |
| public void TestRemovedBeforeUseLockThrowsIgniteException() |
| { |
| var lock1 = Ignite.GetOrCreateLock(TestUtils.TestName); |
| var lock2 = Ignite2.GetOrCreateLock(TestUtils.TestName); |
| |
| lock1.Remove(); |
| |
| var ex = Assert.Throws<IgniteException>(() => lock2.Enter()); |
| Assert.AreEqual("Failed to find reentrant lock with given name: " + lock2.Configuration.Name, ex.Message); |
| } |
| |
| /// <summary> |
| /// Tests that entered lock can't be removed. |
| /// </summary> |
| [Test] |
| public void TestEnteredLockThrowsOnRemove() |
| { |
| var cfg = new LockConfiguration |
| { |
| Name = TestUtils.TestName |
| }; |
| |
| var lck = Ignite.GetOrCreateLock(cfg, true); |
| |
| lck.Enter(); |
| Assert.IsTrue(lck.IsEntered()); |
| |
| var ex = Assert.Throws<IgniteException>(() => lck.Remove()); |
| StringAssert.StartsWith("Failed to remove reentrant lock with blocked threads", ex.Message); |
| |
| lck.Exit(); |
| lck.Remove(); |
| |
| Assert.IsNull(Ignite.GetOrCreateLock(cfg, false)); |
| } |
| |
| /// <summary> |
| /// Tests configuration propagation. |
| /// </summary> |
| [Test] |
| public void TestLockConfigurationCantBeModifiedAfterLockCreation() |
| { |
| var cfg = new LockConfiguration |
| { |
| Name = TestUtils.TestName, |
| IsFair = true, |
| IsFailoverSafe = true |
| }; |
| |
| var lck = Ignite.GetOrCreateLock(cfg, true); |
| |
| // Change original instance. |
| cfg.Name = "y"; |
| cfg.IsFair = false; |
| cfg.IsFailoverSafe = false; |
| |
| // Change returned instance. |
| lck.Configuration.Name = "y"; |
| lck.Configuration.IsFair = false; |
| lck.Configuration.IsFailoverSafe = false; |
| |
| // Verify: actual config has not changed. |
| Assert.AreEqual(TestUtils.TestName, lck.Configuration.Name); |
| Assert.IsTrue(lck.Configuration.IsFair); |
| Assert.IsTrue(lck.Configuration.IsFailoverSafe); |
| } |
| |
| /// <summary> |
| /// Tests that null is returned when lock does not exist and create flag is false. |
| /// </summary> |
| [Test] |
| public void TestGetOrCreateLockReturnsNullOnMissingLockWhenCreateFlagIsNotSet() |
| { |
| Assert.IsNull(Ignite.GetOrCreateLock(new LockConfiguration {Name = TestUtils.TestName}, false)); |
| } |
| |
| /// <summary> |
| /// Tests that fair lock favors granting access to the longest-waiting thread |
| /// </summary> |
| [Test] |
| [Category(TestUtils.CategoryIntensive)] |
| public void TestFairLockGuaranteesOrder() |
| { |
| const int count = 20; |
| |
| var cfg = new LockConfiguration |
| { |
| Name = TestUtils.TestName, |
| IsFair = true, |
| IsFailoverSafe = true |
| }; |
| |
| var lck = Ignite.GetOrCreateLock(cfg, true); |
| lck.Enter(); |
| |
| var locks = new ConcurrentQueue<int>(); |
| var threads = new Thread[count]; |
| |
| var evt = new AutoResetEvent(false); |
| |
| for (int i = 0; i < count; i++) |
| { |
| var id = i; |
| |
| var thread = new Thread(() => |
| { |
| evt.Set(); |
| lck.Enter(); |
| locks.Enqueue(id); |
| lck.Exit(); |
| }); |
| |
| thread.Start(); |
| |
| evt.WaitOne(); |
| |
| Thread.Sleep(100); |
| |
| threads[i] = thread; |
| } |
| |
| lck.Exit(); |
| |
| foreach (var thread in threads) |
| { |
| thread.Join(); |
| } |
| |
| Assert.AreEqual(count, locks.Count); |
| CollectionAssert.IsOrdered(locks); |
| } |
| } |
| } |