diff --git a/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationWithReplicas.cs b/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationWithReplicas.cs index 407d03b..cd2ebfb 100644 --- a/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationWithReplicas.cs +++ b/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationWithReplicas.cs @@ -32,6 +32,8 @@ internal class ApplicationWithReplicas : IDisposable private readonly ILog log; private readonly AtomicBoolean isDisposed = false; + public AtomicBoolean IsDeleted { get; } + public ApplicationWithReplicas( string environmentName, string applicationName, @@ -49,6 +51,7 @@ public ApplicationWithReplicas( this.eventsQueue = eventsQueue; this.log = log; + IsDeleted = new AtomicBoolean(false); nodeWatcher = new AdHocNodeWatcher(OnNodeEvent); applicationContainer = new VersionedContainer(); replicasContainer = new VersionedContainer(); @@ -61,7 +64,8 @@ public void Update() try { - var applicationExists = zooKeeperClient.Exists(new ExistsRequest(applicationNodePath) {Watcher = nodeWatcher}); + var watcher = IsDeleted ? null : nodeWatcher; + var applicationExists = zooKeeperClient.Exists(new ExistsRequest(applicationNodePath) {Watcher = watcher}); if (!applicationExists.IsSuccessful) { return; @@ -75,7 +79,7 @@ public void Update() if (applicationContainer.NeedUpdate(applicationExists.Stat.ModifiedZxId)) { - var applicationData = zooKeeperClient.GetData(new GetDataRequest(applicationNodePath) {Watcher = nodeWatcher}); + var applicationData = zooKeeperClient.GetData(new GetDataRequest(applicationNodePath) {Watcher = watcher}); if (applicationData.Status == ZooKeeperStatus.NodeNotFound) Clear(); if (!applicationData.IsSuccessful) @@ -88,7 +92,7 @@ public void Update() if (replicasContainer.NeedUpdate(applicationExists.Stat.ModifiedChildrenZxId)) { - var applicationChildren = zooKeeperClient.GetChildren(new GetChildrenRequest(applicationNodePath) {Watcher = nodeWatcher}); + var applicationChildren = zooKeeperClient.GetChildren(new GetChildrenRequest(applicationNodePath) {Watcher = watcher}); if (applicationChildren.Status == ZooKeeperStatus.NodeNotFound) Clear(); if (!applicationChildren.IsSuccessful) @@ -105,6 +109,14 @@ public void Update() } } + private void Update(NodeChangedEventType type) + { + if (type == NodeChangedEventType.Deleted) + IsDeleted.TrySetTrue(); + + Update(); + } + public void Dispose() { isDisposed.TrySetTrue(); @@ -115,7 +127,7 @@ private void OnNodeEvent(NodeChangedEventType type, string path) if (isDisposed) return; - eventsQueue.Enqueue(Update); + eventsQueue.Enqueue(() => Update(type)); } private void Clear() diff --git a/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationsStorage.cs b/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationsStorage.cs index 633945c..879dae4 100644 --- a/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationsStorage.cs +++ b/Vostok.ServiceDiscovery/ServiceLocatorStorage/ApplicationsStorage.cs @@ -42,6 +42,12 @@ public void UpdateAll() if (isDisposed) return; + if (kvp.Value.Value.IsDeleted) + { + applications.TryRemove(kvp.Key, out _); + continue; + } + kvp.Value.Value.Update(); } } diff --git a/Vostok.ServiceDiscovery/ServiceLocatorStorage/EnvironmentContainerWrapper.cs b/Vostok.ServiceDiscovery/ServiceLocatorStorage/EnvironmentContainerWrapper.cs new file mode 100644 index 0000000..f7ef6d9 --- /dev/null +++ b/Vostok.ServiceDiscovery/ServiceLocatorStorage/EnvironmentContainerWrapper.cs @@ -0,0 +1,20 @@ +using Vostok.Commons.Threading; +using Vostok.ServiceDiscovery.Abstractions.Models; +using Vostok.ServiceDiscovery.Helpers; + +namespace Vostok.ServiceDiscovery.ServiceLocatorStorage; + +internal class EnvironmentContainerWrapper +{ + public VersionedContainer Container { get; } + + public AtomicBoolean NodeIsDeleted { get; } + + public EnvironmentContainerWrapper(VersionedContainer container) + { + Container = container; + NodeIsDeleted = new AtomicBoolean(false); + } + + public void MarkAsDeleted() => NodeIsDeleted.TrySetTrue(); +} \ No newline at end of file diff --git a/Vostok.ServiceDiscovery/ServiceLocatorStorage/EnvironmentsStorage.cs b/Vostok.ServiceDiscovery/ServiceLocatorStorage/EnvironmentsStorage.cs index 7d6c2fd..16ea00b 100644 --- a/Vostok.ServiceDiscovery/ServiceLocatorStorage/EnvironmentsStorage.cs +++ b/Vostok.ServiceDiscovery/ServiceLocatorStorage/EnvironmentsStorage.cs @@ -15,8 +15,8 @@ namespace Vostok.ServiceDiscovery.ServiceLocatorStorage { internal class EnvironmentsStorage : IDisposable { - private readonly ConcurrentDictionary>> environments - = new ConcurrentDictionary>>(); + private readonly ConcurrentDictionary> environments + = new ConcurrentDictionary>(); private readonly IZooKeeperClient zooKeeperClient; private readonly ServiceDiscoveryPathHelper pathHelper; private readonly ActionsQueue eventsHandler; @@ -36,7 +36,7 @@ public EnvironmentsStorage(IZooKeeperClient zooKeeperClient, ServiceDiscoveryPat public EnvironmentInfo Get(string name) { if (environments.TryGetValue(name, out var lazy)) - return lazy.Value.Value; + return lazy.Value.Container.Value; return CreateAndGet(name); } @@ -61,59 +61,70 @@ public void Dispose() [MethodImpl(MethodImplOptions.NoInlining)] private EnvironmentInfo CreateAndGet(string name) { - var lazy = new Lazy>( + var lazy = new Lazy( () => { var container = new VersionedContainer(); - Update(name, container); - return container; + var wrapper = new EnvironmentContainerWrapper(container); + Update(name, wrapper); + return wrapper; }, LazyThreadSafetyMode.ExecutionAndPublication); - return environments.GetOrAdd(name, _ => lazy).Value.Value; + return environments.GetOrAdd(name, _ => lazy).Value.Container.Value; } - private void Update(string name) + private void Update(string name, bool isDeleted) { if (!environments.TryGetValue(name, out var container)) { log.Warn("Failed to update '{Environment}' environment: it does not exist in local cache.", name); return; } + if(isDeleted) + container.Value.MarkAsDeleted(); Update(name, container.Value); + + if (isDeleted) + environments.TryRemove(name, out _); } - private void Update(string name, VersionedContainer container) + private void Update(string name, EnvironmentContainerWrapper container) { if (isDisposed) return; - + try { var environmentPath = pathHelper.BuildEnvironmentPath(name); - var environmentExists = zooKeeperClient.Exists(new ExistsRequest(environmentPath) {Watcher = nodeWatcher}); + var watcher = container.NodeIsDeleted ? null : nodeWatcher; + var environmentExists = zooKeeperClient.Exists(new ExistsRequest(environmentPath) {Watcher = watcher}); if (!environmentExists.IsSuccessful) return; if (environmentExists.Stat == null) { - container.Clear(); + container.Container.Clear(); } else { - if (!container.NeedUpdate(environmentExists.Stat.ModifiedZxId)) + if (!container.Container.NeedUpdate(environmentExists.Stat.ModifiedZxId)) return; - var environmentData = zooKeeperClient.GetData(new GetDataRequest(environmentPath) {Watcher = nodeWatcher}); + var environmentData = zooKeeperClient.GetData(new GetDataRequest(environmentPath) {Watcher = watcher}); if (environmentData.Status == ZooKeeperStatus.NodeNotFound) - container.Clear(); + { + container.Container.Clear(); + return; + } if (!environmentData.IsSuccessful) return; var info = EnvironmentNodeDataSerializer.Deserialize(name, environmentData.Data); - container.Update(environmentData.Stat.ModifiedZxId, info); + container.Container.Update(environmentData.Stat.ModifiedZxId, info); + } } catch (Exception error) @@ -135,7 +146,7 @@ private void OnNodeEvent(NodeChangedEventType type, string path) } // Note(kungurtsev): run in new thread, because we shouldn't block ZooKeeperClient. - eventsHandler.Enqueue(() => Update(parsedPath.Value.environment)); + eventsHandler.Enqueue(() => Update(parsedPath.Value.environment, type == NodeChangedEventType.Deleted)); } } } \ No newline at end of file