IGNITE-4856 .NET: StopAll on AppDomain unload This closes #1732
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/99842bf1 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/99842bf1 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/99842bf1 Branch: refs/heads/ignite-4985 Commit: 99842bf1986b4cafc7d15ddee7dd2322e65b9c10 Parents: 323e387 Author: Pavel Tupitsyn <[email protected]> Authored: Tue Apr 18 10:41:19 2017 +0300 Committer: Pavel Tupitsyn <[email protected]> Committed: Tue Apr 18 10:41:19 2017 +0300 ---------------------------------------------------------------------- .../ConsoleRedirectTest.cs | 5 +- .../IgniteConfigurationSerializerTest.cs | 3 +- .../IgniteStartStopTest.cs | 24 +++++++ .../Apache.Ignite.Core/IgniteConfiguration.cs | 13 ++++ .../IgniteConfigurationSection.xsd | 5 ++ .../dotnet/Apache.Ignite.Core/Ignition.cs | 67 ++++++++++++++++++-- 6 files changed, 107 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/99842bf1/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ConsoleRedirectTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ConsoleRedirectTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ConsoleRedirectTest.cs index 5a59a37..3ab5ed3 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ConsoleRedirectTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/ConsoleRedirectTest.cs @@ -166,11 +166,12 @@ namespace Apache.Ignite.Core.Tests { public void Run() { - var ignite = Ignition.Start(new IgniteConfiguration(TestUtils.GetTestConfiguration()) + Ignition.Start(new IgniteConfiguration(TestUtils.GetTestConfiguration()) { IgniteInstanceName = "newDomainGrid" }); - Ignition.Stop(ignite.Name, true); + + // Will be stopped automatically on domain unload. } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/99842bf1/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs index cba5647..4a197e5 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteConfigurationSerializerTest.cs @@ -65,7 +65,7 @@ namespace Apache.Ignite.Core.Tests [Test] public void TestPredefinedXml() { - var xml = @"<igniteConfig workDirectory='c:' JvmMaxMemoryMb='1024' MetricsLogFrequency='0:0:10' isDaemon='true' isLateAffinityAssignment='false' springConfigUrl='c:\myconfig.xml'> + var xml = @"<igniteConfig workDirectory='c:' JvmMaxMemoryMb='1024' MetricsLogFrequency='0:0:10' isDaemon='true' isLateAffinityAssignment='false' springConfigUrl='c:\myconfig.xml' autoGenerateIgniteInstanceName='true'> <localhost>127.1.1.1</localhost> <binaryConfiguration compactFooter='false' keepDeserialized='true'> <nameMapper type='Apache.Ignite.Core.Tests.IgniteConfigurationSerializerTest+NameMapper' bar='testBar' /> @@ -158,6 +158,7 @@ namespace Apache.Ignite.Core.Tests Assert.IsFalse(cfg.BinaryConfiguration.CompactFooter); Assert.AreEqual(new[] {42, EventType.TaskFailed, EventType.JobFinished}, cfg.IncludedEventTypes); Assert.AreEqual(@"c:\myconfig.xml", cfg.SpringConfigUrl); + Assert.IsTrue(cfg.AutoGenerateIgniteInstanceName); Assert.AreEqual("secondCache", cfg.CacheConfiguration.Last().Name); http://git-wip-us.apache.org/repos/asf/ignite/blob/99842bf1/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteStartStopTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteStartStopTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteStartStopTest.cs index b863308..bc40f48 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteStartStopTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/IgniteStartStopTest.cs @@ -20,6 +20,7 @@ namespace Apache.Ignite.Core.Tests using System; using System.Collections.Generic; using System.IO; + using System.Linq; using System.Threading; using System.Threading.Tasks; using Apache.Ignite.Core.Common; @@ -104,12 +105,15 @@ namespace Apache.Ignite.Core.Tests var grid1 = Ignition.Start(cfg); Assert.AreEqual("grid1", grid1.Name); + Assert.AreSame(grid1, Ignition.GetIgnite()); + Assert.AreSame(grid1, Ignition.GetAll().Single()); cfg.SpringConfigUrl = cfgs[1]; var grid2 = Ignition.Start(cfg); Assert.AreEqual("grid2", grid2.Name); + Assert.Throws<IgniteException>(() => Ignition.GetIgnite()); cfg.SpringConfigUrl = cfgs[2]; @@ -124,9 +128,12 @@ namespace Apache.Ignite.Core.Tests Assert.AreSame(grid2, Ignition.TryGetIgnite("grid2")); Assert.AreSame(grid3, Ignition.GetIgnite(null)); + Assert.AreSame(grid3, Ignition.GetIgnite()); Assert.AreSame(grid3, Ignition.TryGetIgnite(null)); Assert.AreSame(grid3, Ignition.TryGetIgnite()); + Assert.AreEqual(new[] {grid3, grid1, grid2}, Ignition.GetAll().OrderBy(x => x.Name).ToArray()); + Assert.Throws<IgniteException>(() => Ignition.GetIgnite("invalid_name")); Assert.IsNull(Ignition.TryGetIgnite("invalid_name")); @@ -187,6 +194,23 @@ namespace Apache.Ignite.Core.Tests } /// <summary> + /// Tests automatic grid name generation. + /// </summary> + [Test] + public void TestStartUniqueName() + { + var cfg = TestUtils.GetTestConfiguration(); + cfg.AutoGenerateIgniteInstanceName = true; + + Ignition.Start(cfg); + Assert.IsNotNull(Ignition.GetIgnite()); + + Ignition.Start(cfg); + Assert.Throws<IgniteException>(() => Ignition.GetIgnite()); + Assert.AreEqual(2, Ignition.GetAll().Count); + } + + /// <summary> /// /// </summary> [Test] http://git-wip-us.apache.org/repos/asf/ignite/blob/99842bf1/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs index a22c568..24a4364 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfiguration.cs @@ -518,6 +518,7 @@ namespace Apache.Ignite.Core JvmInitialMemoryMb = cfg.JvmInitialMemoryMb; JvmMaxMemoryMb = cfg.JvmMaxMemoryMb; PluginConfigurations = cfg.PluginConfigurations; + AutoGenerateIgniteInstanceName = cfg.AutoGenerateIgniteInstanceName; } /// <summary> @@ -530,6 +531,18 @@ namespace Apache.Ignite.Core public string IgniteInstanceName { get; set; } /// <summary> + /// Gets or sets a value indicating whether unique <see cref="IgniteInstanceName"/> should be generated. + /// <para /> + /// Set this to true in scenarios where new node should be started regardless of other nodes present within + /// current process. In particular, this setting is useful is ASP.NET and IIS environments, where AppDomains + /// are loaded and unloaded within a single process during application restarts. Ignite stops all nodes + /// on <see cref="AppDomain"/> unload, however, IIS does not wait for previous AppDomain to unload before + /// starting up a new one, which may cause "Ignite instance with this name has already been started" errors. + /// This setting solves the issue. + /// </summary> + public bool AutoGenerateIgniteInstanceName { get; set; } + + /// <summary> /// Gets or sets optional local instance name. /// <para /> /// This name only works locally and has no effect on topology. http://git-wip-us.apache.org/repos/asf/ignite/blob/99842bf1/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd index b503338..1d2a948 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd +++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd @@ -1152,6 +1152,11 @@ <xs:documentation>Local Ignite instance name to be used with Ignition.GetIgnite.</xs:documentation> </xs:annotation> </xs:attribute> + <xs:attribute name="autoGenerateIgniteInstanceName" type="xs:string"> + <xs:annotation> + <xs:documentation>Generate unique igniteInstanceName automatically.</xs:documentation> + </xs:annotation> + </xs:attribute> <xs:attribute name="jvmDllPath" type="xs:string"> <xs:annotation> <xs:documentation>Path jvm.dll file. If not set, it's location will be determined using JAVA_HOME environment variable. If path is neither set nor determined automatically, an exception will be thrown.</xs:documentation> http://git-wip-us.apache.org/repos/asf/ignite/blob/99842bf1/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs index d2be92a..cdb1064 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs @@ -85,6 +85,7 @@ namespace Apache.Ignite.Core static Ignition() { AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + AppDomain.CurrentDomain.DomainUnload += CurrentDomain_DomainUnload; } /// <summary> @@ -231,6 +232,11 @@ namespace Apache.Ignite.Core var gridName = cfg.IgniteInstanceName; + if (cfg.AutoGenerateIgniteInstanceName) + { + gridName = (gridName ?? "ignite-instance-") + Guid.NewGuid(); + } + // 3. Create startup object which will guide us through the rest of the process. _startup = new Startup(cfg, cbs); @@ -576,15 +582,49 @@ namespace Apache.Ignite.Core } /// <summary> - /// Gets an instance of default no-name grid. Note that - /// caller of this method should not assume that it will return the same - /// instance every time. + /// Gets the default Ignite instance with null name, or an instance with any name when there is only one. + /// <para /> + /// Note that caller of this method should not assume that it will return the same instance every time. /// </summary> - /// <returns>An instance of default no-name grid.</returns> - /// <exception cref="IgniteException">When there is no Ignite instance with specified name.</exception> + /// <returns>Default Ignite instance.</returns> + /// <exception cref="IgniteException">When there is no matching Ignite instance.</exception> public static IIgnite GetIgnite() { - return GetIgnite(null); + lock (SyncRoot) + { + if (Nodes.Count == 0) + { + throw new IgniteException("Failed to get default Ignite instance: " + + "there are no instances started."); + } + + if (Nodes.Count == 1) + { + return Nodes.Single().Value; + } + + Ignite result; + + if (Nodes.TryGetValue(new NodeKey(null), out result)) + { + return result; + } + + throw new IgniteException(string.Format("Failed to get default Ignite instance: " + + "there are {0} instances started, and none of them has null name.", Nodes.Count)); + } + } + + /// <summary> + /// Gets all started Ignite instances. + /// </summary> + /// <returns>All Ignite instances.</returns> + public static ICollection<IIgnite> GetAll() + { + lock (SyncRoot) + { + return Nodes.Values.ToArray(); + } } /// <summary> @@ -685,9 +725,22 @@ namespace Apache.Ignite.Core { return LoadedAssembliesResolver.Instance.GetAssembly(args.Name); } + + /// <summary> + /// Handles the DomainUnload event of the CurrentDomain control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> + private static void CurrentDomain_DomainUnload(object sender, EventArgs e) + { + // If we don't stop Ignite.NET on domain unload, + // we end up with broken instances in Java (invalid callbacks, etc). + // IIS, in particular, is known to unload and reload domains within the same process. + StopAll(true); + } /// <summary> - /// Grid key. + /// Grid key. Workaround for non-null key requirement in Dictionary. /// </summary> private class NodeKey {
