blob: 5b69e3602e9de98a4920dadabfa69949be9b7237 [file] [log] [blame]
/*
* 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);
}
}
}