diff --git a/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/ApplicationsStorage_Tests.cs b/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/ApplicationsStorage_Tests.cs index 1796312..758c2da 100644 --- a/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/ApplicationsStorage_Tests.cs +++ b/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/ApplicationsStorage_Tests.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using Vostok.Commons.Testing; using Vostok.ServiceDiscovery.Abstractions.Models; -using Vostok.ServiceDiscovery.Models; using Vostok.ServiceDiscovery.ServiceLocatorStorage; using Vostok.ZooKeeper.Client.Abstractions; @@ -20,7 +19,7 @@ public void Should_track_application_properties() CreateApplicationNode("default", "application"); CreateReplicaNode(new ReplicaInfo("default", "application", "https://github.com/vostok")); - using (var storage = GetApplicationsStorage()) + using (var storage = GetApplicationsStorage(out _)) { for (var times = 0; times < 10; times++) { @@ -46,7 +45,7 @@ public void Should_track_application_replicas() CreateEnvironmentNode("default"); CreateApplicationNode("default", "application"); - using (var storage = GetApplicationsStorage()) + using (var storage = GetApplicationsStorage(out _)) { var expectedReplicas = new List(); @@ -69,7 +68,7 @@ public void Should_return_null_without_application() { CreateEnvironmentNode("default"); - using (var storage = GetApplicationsStorage()) + using (var storage = GetApplicationsStorage(out _)) { ShouldReturnImmediately(storage, "default", "application", null); @@ -85,7 +84,7 @@ public void Should_return_empty_list_without_replicas() CreateEnvironmentNode("default"); CreateApplicationNode("default", "application"); - using (var storage = GetApplicationsStorage()) + using (var storage = GetApplicationsStorage(out _)) { ShouldReturnImmediately(storage, "default", "application", ServiceTopology.Build(new Uri[0], null)); @@ -106,7 +105,7 @@ public void Should_store_multiple_environments_and_applications() CreateApplicationNode("environment2", "application1", new Dictionary {{"key", "2/1"}}); CreateApplicationNode("environment2", "application2", new Dictionary {{"key", "2/2"}}); - using (var storage = GetApplicationsStorage()) + using (var storage = GetApplicationsStorage(out _)) { ShouldReturnImmediately( storage, @@ -134,7 +133,7 @@ public void Should_works_disconnected() CreateApplicationNode("default", "application"); CreateReplicaNode(new ReplicaInfo("default", "application", "https://github.com/vostok")); - using (var storage = GetApplicationsStorage()) + using (var storage = GetApplicationsStorage(out _)) { var properties = new Dictionary { @@ -162,7 +161,7 @@ public void Should_not_update_after_dispose() CreateApplicationNode("default", "application", new Dictionary {{"key", "value1"}}); CreateReplicaNode(new ReplicaInfo("default", "application", "https://github.com/vostok")); - using (var storage = GetApplicationsStorage()) + using (var storage = GetApplicationsStorage(out _)) { var expected = ServiceTopology.Build(new List {new Uri("https://github.com/vostok")}, new Dictionary {{"key", "value1"}}); ShouldReturnImmediately(storage, "default", "application", expected); @@ -182,7 +181,7 @@ public void Should_not_update_to_invalid_application_properties() CreateApplicationNode("default", "application", new Dictionary {{"key", "value"}}); CreateReplicaNode(new ReplicaInfo("default", "application", "https://github.com/vostok")); - using (var storage = GetApplicationsStorage()) + using (var storage = GetApplicationsStorage(out _)) { var expected = ServiceTopology.Build(new List {new Uri("https://github.com/vostok")}, new Dictionary {{"key", "value"}}); ShouldReturnImmediately(storage, "default", "application", expected); @@ -201,7 +200,7 @@ public void UpdateAll_should_force_update() CreateEnvironmentNode("default"); CreateApplicationNode("default", "application"); - using (var storage = GetApplicationsStorage()) + using (var storage = GetApplicationsStorage(out _)) { var expectedReplicas = new List(); @@ -224,6 +223,104 @@ public void UpdateAll_should_force_update() } } + [Test] + public void Should_delete_application_from_cache_if_app_and_env_nodes_were_deleted_when_observation_of_deleted_apps_is_disabled() + { + const string environment = "environment1"; + const string app = "application1"; + + var expectedTopology = ServiceTopology.Build(new Uri[0], new Dictionary {{"key", "1/1"}}); + + using (var storage = GetApplicationsStorage(out var envStorage, observeNonExistentApplications: false)) + { + for (var i = 0; i < 10; i++) + { + CreateEnvironmentNode(environment); + CreateApplicationNode(environment, app, new Dictionary {{"key", "1/1"}}); + + envStorage.Get(environment).Should().BeEquivalentTo(new EnvironmentInfo(environment, null, null)); + + ShouldReturnImmediately( + storage, + environment, + app, + expectedTopology); + + DeleteApplicationNode(environment, app); + DeleteEnvironmentNode(environment); + + envStorage.UpdateAll(); + storage.UpdateAll(); + + storage.Contains(environment, app).Should().BeFalse(); + } + } + } + + [Test] + public void Should_not_delete_application_from_cache_when_env_exists_and_observation_of_deleted_apps_is_disabled() + { + const string environment = "environment1"; + const string app = "application1"; + + CreateEnvironmentNode(environment); + CreateApplicationNode(environment, app, new Dictionary {{"key", "1/1"}}); + + using (var storage = GetApplicationsStorage(out var envStorage, observeNonExistentApplications: false)) + { + var expectedTopology = ServiceTopology.Build(new Uri[0], new Dictionary {{"key", "1/1"}}); + ShouldReturnImmediately( + storage, + environment, + app, + expectedTopology); + + envStorage.Get(environment).Should().BeEquivalentTo(new EnvironmentInfo(environment, null, null)); + + DeleteApplicationNode(environment, app); + + envStorage.UpdateAll(); + envStorage.Contains(environment).Should().BeTrue(); + storage.UpdateAll(); + + storage.Contains(environment, app).Should().BeTrue(); + } + } + + [Test] + public void Should_not_delete_application_from_cache_when_observation_of_deleted_apps_is_disabled_and_client_disconnected() + { + const string environment = "environment1"; + const string app = "application1"; + CreateEnvironmentNode(environment); + CreateApplicationNode(environment, app, new Dictionary {{"key", "1/1"}}); + + using (var storage = GetApplicationsStorage(out var envStorage, observeNonExistentApplications: false)) + { + var expectedTopology = ServiceTopology.Build(new Uri[0], new Dictionary {{"key", "1/1"}}); + ShouldReturnImmediately( + storage, + environment, + app, + expectedTopology); + + envStorage.Get(environment).Should().BeEquivalentTo(new EnvironmentInfo(environment, null, null)); + + Ensemble.Stop(); + + envStorage.UpdateAll(); + envStorage.Contains(environment).Should().BeTrue(); + storage.UpdateAll(); + + storage.Contains(environment, app).Should().BeTrue(); + ShouldReturnImmediately( + storage, + environment, + app, + expectedTopology); + } + } + private static void ShouldReturn(ApplicationsStorage storage, string environment, string application, ServiceTopology topology) { Action assertion = () => { ShouldReturnImmediately(storage, environment, application, topology); }; @@ -235,9 +332,10 @@ private static void ShouldReturnImmediately(ApplicationsStorage storage, string storage.Get(environment, application).ServiceTopology.Should().BeEquivalentTo(topology); } - private ApplicationsStorage GetApplicationsStorage() + private ApplicationsStorage GetApplicationsStorage(out EnvironmentsStorage envStorage, bool observeNonExistentApplications = true) { - return new ApplicationsStorage(ZooKeeperClient, PathHelper, EventsQueue, Log); + envStorage = new EnvironmentsStorage(ZooKeeperClient, PathHelper, EventsQueue, observeNonExistentApplications, Log); + return new ApplicationsStorage(ZooKeeperClient, PathHelper, EventsQueue, observeNonExistentApplications, envStorage, Log); } } } \ No newline at end of file diff --git a/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/EnvironmentStorage_TestsBase.cs b/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/EnvironmentStorage_TestsBase.cs new file mode 100644 index 0000000..677c0b7 --- /dev/null +++ b/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/EnvironmentStorage_TestsBase.cs @@ -0,0 +1,26 @@ +using System; +using FluentAssertions; +using Vostok.Commons.Testing; +using Vostok.ServiceDiscovery.Abstractions.Models; +using Vostok.ServiceDiscovery.ServiceLocatorStorage; + +namespace Vostok.ServiceDiscovery.Tests.ServiceLocatorStorage; + +internal class EnvironmentStorage_TestsBase : TestsBase +{ + protected static void ShouldReturn(EnvironmentsStorage storage, string name, EnvironmentInfo info) + { + Action assertion = () => { ShouldReturnImmediately(storage, name, info); }; + assertion.ShouldPassIn(DefaultTimeout); + } + + protected static void ShouldReturnImmediately(EnvironmentsStorage storage, string name, EnvironmentInfo info) + { + storage.Get(name).Should().BeEquivalentTo(info); + } + + protected EnvironmentsStorage GetEnvironmentsStorage(bool observeNonExistentEnvironment = true) + { + return new EnvironmentsStorage(ZooKeeperClient, PathHelper, EventsQueue, observeNonExistentEnvironment, Log); + } +} \ No newline at end of file diff --git a/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/EnvironmentsStorageWithObserveFlag_Tests.cs b/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/EnvironmentsStorageWithObserveFlag_Tests.cs new file mode 100644 index 0000000..35fd583 --- /dev/null +++ b/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/EnvironmentsStorageWithObserveFlag_Tests.cs @@ -0,0 +1,67 @@ +using NUnit.Framework; +using FluentAssertions; +using Vostok.ServiceDiscovery.Abstractions.Models; + +namespace Vostok.ServiceDiscovery.Tests.ServiceLocatorStorage; + +[TestFixture] +internal class EnvironmentsStorageWithObserveFlag_Tests : EnvironmentStorage_TestsBase +{ + [Test] + public void Should_not_delete_environment_from_cache_if_node_was_deleted_when_observation_of_deleted_apps_is_enabled() + { + using (var storage = GetEnvironmentsStorage(observeNonExistentEnvironment: true)) + { + CreateEnvironmentNode("default", "parent"); + + var expectedInfo = new EnvironmentInfo("default", "parent", null); + storage.Get("default").Should().BeEquivalentTo(expectedInfo); + + DeleteEnvironmentNode("default"); + storage.UpdateAll(); + storage.Contains("default").Should().BeTrue(); + storage.Get("default").Should().BeNull(); + + CreateEnvironmentNode("default", "parent"); + ShouldReturn(storage, "default", expectedInfo); + } + } + + [Test] + public void Should_not_delete_environment_from_cache_when_observation_of_deleted_apps_is_disabled_and_client_disconnected() + { + using (var storage = GetEnvironmentsStorage(observeNonExistentEnvironment: false)) + { + CreateEnvironmentNode("default", "parent"); + + var expectedInfo = new EnvironmentInfo("default", "parent", null); + ShouldReturnImmediately(storage, "default", expectedInfo); + + Ensemble.Stop(); + + storage.UpdateAll(); + storage.Contains("default").Should().BeTrue(); + ShouldReturnImmediately(storage, "default", expectedInfo); + } + } + + [Test] + public void Should_delete_environment_from_cache_if_node_was_deleted_when_observation_of_deleted_apps_is_disabled() + { + var expectedInfo = new EnvironmentInfo("default", "parent", null); + + using (var storage = GetEnvironmentsStorage(observeNonExistentEnvironment: false)) + { + for (var i = 0; i < 10; i++) + { + CreateEnvironmentNode("default", "parent"); + + ShouldReturnImmediately(storage, "default", expectedInfo); + + DeleteEnvironmentNode("default"); + storage.UpdateAll(); + storage.Contains("default").Should().BeFalse(); + } + } + } +} \ No newline at end of file diff --git a/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/EnvironmentsStorage_Tests.cs b/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/EnvironmentsStorage_Tests.cs index c80d34d..5f5636f 100644 --- a/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/EnvironmentsStorage_Tests.cs +++ b/Vostok.ServiceDiscovery.Tests/ServiceLocatorStorage/EnvironmentsStorage_Tests.cs @@ -1,17 +1,12 @@ -using System; -using System.Collections.Generic; -using FluentAssertions; +using System.Collections.Generic; using NUnit.Framework; -using Vostok.Commons.Testing; using Vostok.ServiceDiscovery.Abstractions.Models; -using Vostok.ServiceDiscovery.Models; -using Vostok.ServiceDiscovery.ServiceLocatorStorage; using Vostok.ZooKeeper.Client.Abstractions; namespace Vostok.ServiceDiscovery.Tests.ServiceLocatorStorage { [TestFixture] - internal class EnvironmentsStorage_Tests : TestsBase + internal class EnvironmentsStorage_Tests : EnvironmentStorage_TestsBase { [Test] public void Should_track_environment_parent_with_properties() @@ -154,21 +149,5 @@ public void UpdateAll_should_force_update() } } } - - private static void ShouldReturn(EnvironmentsStorage storage, string name, EnvironmentInfo info) - { - Action assertion = () => { ShouldReturnImmediately(storage, name, info); }; - assertion.ShouldPassIn(DefaultTimeout); - } - - private static void ShouldReturnImmediately(EnvironmentsStorage storage, string name, EnvironmentInfo info) - { - storage.Get(name).Should().BeEquivalentTo(info); - } - - private EnvironmentsStorage GetEnvironmentsStorage() - { - return new EnvironmentsStorage(ZooKeeperClient, PathHelper, EventsQueue, Log); - } } } \ No newline at end of file diff --git a/Vostok.ServiceDiscovery/PublicAPI.Shipped.txt b/Vostok.ServiceDiscovery/PublicAPI.Shipped.txt index 16e6dd2..114bced 100644 --- a/Vostok.ServiceDiscovery/PublicAPI.Shipped.txt +++ b/Vostok.ServiceDiscovery/PublicAPI.Shipped.txt @@ -101,3 +101,5 @@ Vostok.ServiceDiscovery.ServiceLocatorSettings.ZooKeeperNodesPathEscaper.get -> Vostok.ServiceDiscovery.ServiceLocatorSettings.ZooKeeperNodesPathEscaper.set -> void Vostok.ServiceDiscovery.ServiceLocatorSettings.ZooKeeperNodesPrefix.get -> string Vostok.ServiceDiscovery.ServiceLocatorSettings.ZooKeeperNodesPrefix.set -> void +Vostok.ServiceDiscovery.ServiceLocatorSettings.ObserveNonExistentApplications.get -> bool +Vostok.ServiceDiscovery.ServiceLocatorSettings.ObserveNonExistentApplications.set -> void \ No newline at end of file diff --git a/Vostok.ServiceDiscovery/ServiceLocator.cs b/Vostok.ServiceDiscovery/ServiceLocator.cs index daf58cb..228963a 100644 --- a/Vostok.ServiceDiscovery/ServiceLocator.cs +++ b/Vostok.ServiceDiscovery/ServiceLocator.cs @@ -44,9 +44,8 @@ public ServiceLocator( pathHelper = new ServiceDiscoveryPathHelper(this.settings.ZooKeeperNodesPrefix, this.settings.ZooKeeperNodesPathEscaper); eventsQueue = new ActionsQueue(this.log); - environmentsStorage = new EnvironmentsStorage(zooKeeperClient, pathHelper, eventsQueue, log); - applicationsStorage = new ApplicationsStorage(zooKeeperClient, pathHelper, eventsQueue, log); - + environmentsStorage = new EnvironmentsStorage(zooKeeperClient, pathHelper, eventsQueue, this.settings.ObserveNonExistentApplications, log); + applicationsStorage = new ApplicationsStorage(zooKeeperClient, pathHelper, eventsQueue, this.settings.ObserveNonExistentApplications, environmentsStorage, log); } /// diff --git a/Vostok.ServiceDiscovery/ServiceLocatorSettings.cs b/Vostok.ServiceDiscovery/ServiceLocatorSettings.cs index b36936b..566ddd4 100644 --- a/Vostok.ServiceDiscovery/ServiceLocatorSettings.cs +++ b/Vostok.ServiceDiscovery/ServiceLocatorSettings.cs @@ -18,5 +18,7 @@ public class ServiceLocatorSettings public IZooKeeperPathEscaper ZooKeeperNodesPathEscaper { get; set; } = ZooKeeperPathEscaper.Instance; public TimeSpan IterationPeriod { get; set; } = 5.Seconds(); + + public bool ObserveNonExistentApplications { get; set; } = true; } } \ No newline at end of file diff --git a/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationWithReplicas.cs b/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationWithReplicas.cs index 407d03b..02d1170 100644 --- a/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationWithReplicas.cs +++ b/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationWithReplicas.cs @@ -29,6 +29,7 @@ internal class ApplicationWithReplicas : IDisposable private readonly ServiceDiscoveryPathHelper pathHelper; private readonly ActionsQueue eventsQueue; private readonly AdHocNodeWatcher nodeWatcher; + private readonly AdHocNodeWatcher existsWatcher; private readonly ILog log; private readonly AtomicBoolean isDisposed = false; @@ -39,6 +40,7 @@ public ApplicationWithReplicas( IZooKeeperClient zooKeeperClient, ServiceDiscoveryPathHelper pathHelper, ActionsQueue eventsQueue, + bool observeNonExistentApplication, ILog log) { this.environmentName = environmentName; @@ -50,25 +52,27 @@ public ApplicationWithReplicas( this.log = log; nodeWatcher = new AdHocNodeWatcher(OnNodeEvent); + existsWatcher = observeNonExistentApplication ? nodeWatcher : null; applicationContainer = new VersionedContainer(); replicasContainer = new VersionedContainer(); } - public void Update() + public void Update(out bool appExists) { + appExists = true; + if (isDisposed) return; try { - var applicationExists = zooKeeperClient.Exists(new ExistsRequest(applicationNodePath) {Watcher = nodeWatcher}); + var applicationExists = zooKeeperClient.Exists(new ExistsRequest(applicationNodePath) {Watcher = existsWatcher}); if (!applicationExists.IsSuccessful) - { return; - } if (applicationExists.Stat == null) { + appExists = false; Clear(); return; } @@ -77,7 +81,11 @@ public void Update() { var applicationData = zooKeeperClient.GetData(new GetDataRequest(applicationNodePath) {Watcher = nodeWatcher}); if (applicationData.Status == ZooKeeperStatus.NodeNotFound) + { + appExists = false; Clear(); + } + if (!applicationData.IsSuccessful) return; @@ -90,7 +98,11 @@ public void Update() { var applicationChildren = zooKeeperClient.GetChildren(new GetChildrenRequest(applicationNodePath) {Watcher = nodeWatcher}); if (applicationChildren.Status == ZooKeeperStatus.NodeNotFound) + { + appExists = false; Clear(); + } + if (!applicationChildren.IsSuccessful) return; @@ -115,7 +127,7 @@ private void OnNodeEvent(NodeChangedEventType type, string path) if (isDisposed) return; - eventsQueue.Enqueue(Update); + eventsQueue.Enqueue(() => Update(out _)); } private void Clear() diff --git a/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationsStorage.cs b/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationsStorage.cs index 633945c..aaca092 100644 --- a/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationsStorage.cs +++ b/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationsStorage.cs @@ -16,17 +16,29 @@ internal class ApplicationsStorage : IDisposable private readonly IZooKeeperClient zooKeeperClient; private readonly ServiceDiscoveryPathHelper pathHelper; private readonly ActionsQueue eventsQueue; + private readonly bool observeNonExistentApplications; + private readonly EnvironmentsStorage environmentsStorage; private readonly ILog log; private readonly AtomicBoolean isDisposed = false; - public ApplicationsStorage(IZooKeeperClient zooKeeperClient, ServiceDiscoveryPathHelper pathHelper, ActionsQueue eventsQueue, ILog log) + public ApplicationsStorage( + IZooKeeperClient zooKeeperClient, + ServiceDiscoveryPathHelper pathHelper, + ActionsQueue eventsQueue, + bool observeNonExistentApplications, + EnvironmentsStorage environmentsStorage, + ILog log) { this.zooKeeperClient = zooKeeperClient; this.pathHelper = pathHelper; this.eventsQueue = eventsQueue; + this.observeNonExistentApplications = observeNonExistentApplications; + this.environmentsStorage = environmentsStorage; this.log = log; } + public bool Contains(string environment, string application) => applications.ContainsKey((environment, application)); + public ApplicationWithReplicas Get(string environment, string application) { if (applications.TryGetValue((environment, application), out var lazy)) @@ -42,7 +54,9 @@ public void UpdateAll() if (isDisposed) return; - kvp.Value.Value.Update(); + kvp.Value.Value.Update(out var appExists); + if (!appExists && !observeNonExistentApplications && !environmentsStorage.Contains(kvp.Key.environment)) + applications.TryRemove(kvp.Key, out _); } } @@ -64,9 +78,9 @@ private ApplicationWithReplicas CreateAndGet(string environment, string applicat lazy = new Lazy( () => { - var container = new ApplicationWithReplicas(environment, application, pathHelper.BuildApplicationPath(environment, application), zooKeeperClient, pathHelper, eventsQueue, log); + var container = new ApplicationWithReplicas(environment, application, pathHelper.BuildApplicationPath(environment, application), zooKeeperClient, pathHelper, eventsQueue, observeNonExistentApplications, log); if (!isDisposed) - container.Update(); + container.Update(out _); return container; }, LazyThreadSafetyMode.ExecutionAndPublication); diff --git a/Vostok.ServiceDiscovery/ServiceLocatorStorage/EnvironmentsStorage.cs b/Vostok.ServiceDiscovery/ServiceLocatorStorage/EnvironmentsStorage.cs index 7d6c2fd..9853b45 100644 --- a/Vostok.ServiceDiscovery/ServiceLocatorStorage/EnvironmentsStorage.cs +++ b/Vostok.ServiceDiscovery/ServiceLocatorStorage/EnvironmentsStorage.cs @@ -20,19 +20,30 @@ private readonly ConcurrentDictionary environments.ContainsKey(environment); + public EnvironmentInfo Get(string name) { if (environments.TryGetValue(name, out var lazy)) @@ -77,7 +88,9 @@ private void Update(string name) { if (!environments.TryGetValue(name, out var container)) { - log.Warn("Failed to update '{Environment}' environment: it does not exist in local cache.", name); + if (observeNonExistentEnvironments) + log.Warn("Failed to update '{Environment}' environment: it does not exist in local cache.", name); + return; } @@ -92,13 +105,15 @@ private void Update(string name, VersionedContainer container) try { var environmentPath = pathHelper.BuildEnvironmentPath(name); - - var environmentExists = zooKeeperClient.Exists(new ExistsRequest(environmentPath) {Watcher = nodeWatcher}); + var environmentExists = zooKeeperClient.Exists(new ExistsRequest(environmentPath) {Watcher = existsWatcher}); if (!environmentExists.IsSuccessful) return; if (environmentExists.Stat == null) { + if (RemoveEnvironmentFromCacheIfNeeded(name)) + return; + container.Clear(); } else @@ -108,7 +123,13 @@ private void Update(string name, VersionedContainer container) var environmentData = zooKeeperClient.GetData(new GetDataRequest(environmentPath) {Watcher = nodeWatcher}); if (environmentData.Status == ZooKeeperStatus.NodeNotFound) + { + if (RemoveEnvironmentFromCacheIfNeeded(name)) + return; + container.Clear(); + } + if (!environmentData.IsSuccessful) return; @@ -122,6 +143,15 @@ private void Update(string name, VersionedContainer container) } } + private bool RemoveEnvironmentFromCacheIfNeeded(string name) + { + if (observeNonExistentEnvironments) + return false; + + environments.TryRemove(name, out _); + return true; + } + private void OnNodeEvent(NodeChangedEventType type, string path) { if (isDisposed)