From 456ead9bc8b0f61357bae93152ad0403c4940101 Mon Sep 17 00:00:00 2001 From: vnugent Date: Tue, 13 Feb 2024 14:46:35 -0500 Subject: fix: #1 shared cluster index on linux & latested core updates --- .../src/Clustering/ClusterNodeIndex.cs | 89 ++++++++++++++-------- 1 file changed, 58 insertions(+), 31 deletions(-) (limited to 'plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering') diff --git a/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/ClusterNodeIndex.cs b/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/ClusterNodeIndex.cs index a8fb0e1..c9cd746 100644 --- a/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/ClusterNodeIndex.cs +++ b/plugins/VNLib.Data.Caching.Providers.VNCache/src/Clustering/ClusterNodeIndex.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.Providers.VNCache @@ -48,39 +48,73 @@ namespace VNLib.Data.Caching.Providers.VNCache.Clustering public static IClusterNodeIndex CreateIndex(CacheClientConfiguration config) { - //Create a named semaphore to ensure only one index is created per app domain - using Semaphore sm = new (1, 1, APP_DOMAIN_KEY, out _); + /* TEMPORARY: + * Named semaphores are only supported on Windows, which allowed synchronized communication between + * plugins, but this is not supported on Linux. This will be replaced with a more robust solution + * in the future. For now they will just need to be separate instances. + * + * Remember while plugins are in the same app-domain, they do not share an assembly + * load context which means unless the default ALC contains the desired types, types won't unify + * so we have to use "ghetto" features to avoid interprocess communication, in the same process... + */ - if (!sm.WaitOne(500)) + if (OperatingSystem.IsWindows()) { - throw new TimeoutException("Failed to access the Cluster index shared semaphore"); - } + //Create a named semaphore to ensure only one index is created per app domain + using Semaphore sm = new (1, 1, APP_DOMAIN_KEY, out _); - try - { - //Try to get an existing index from the app domain - object? remoteIndex = AppDomain.CurrentDomain.GetData(APP_DOMAIN_KEY); - if (remoteIndex == null) + if (!sm.WaitOne(500)) + { + throw new TimeoutException("Failed to access the Cluster index shared semaphore"); + } + + try { - //Create a new index and store it in the app domain - IClusterNodeIndex index = new LocalHandler(config); - AppDomain.CurrentDomain.SetData(APP_DOMAIN_KEY, index); - return index; + //Try to get an existing index from the app domain global storage pool + object? remoteIndex = AppDomain.CurrentDomain.GetData(APP_DOMAIN_KEY); + if (remoteIndex == null) + { + //Create a new index and store it in the app domain + IClusterNodeIndex index = new LocalHandler(config); + AppDomain.CurrentDomain.SetData(APP_DOMAIN_KEY, index); + return index; + } + else + { + //Use the existing index + return new RemoteHandler(remoteIndex); + } } - else + finally { - //Use the existing index - return new RemoteHandler(remoteIndex); + sm.Release(); } } - finally + else { - sm.Release(); + return new LocalHandler(config); } } + /* + * So a bit of explaination. + * + * Plugins don't share types. Each plugin will load this package into its own ALC. Which will + * cause n instances of the cluster indext manager. Which can cause unecessary http traffic + * building the cluster index multiple times. In an attemt to avoid this, I try to share a single + * cluster index instance across all plugins in the same app domain. + * + * To do this a local handler instance is loaded into whichever plugin accuires the named semaphore + * first, and then the instance is stored in the app domain global storage pool. If its found, + * then other plugins will use the remote handler to access the index. + * + * The remote handler, attempts to use reflection to get function delegates and call the local + * handler functions via reflection. + * + * Unless VNLib.Core supports a new way to safley share types across ALCs, this is my solution. + */ - record class LocalHandler(CacheClientConfiguration Config) : IClusterNodeIndex, IIntervalScheduleable + sealed class LocalHandler(CacheClientConfiguration Config) : IClusterNodeIndex, IIntervalScheduleable { private Task _currentUpdate = Task.CompletedTask; @@ -115,18 +149,11 @@ namespace VNLib.Data.Caching.Providers.VNCache.Clustering } } - class RemoteHandler : IClusterNodeIndex + sealed class RemoteHandler(object RemoteIndex) : IClusterNodeIndex { - private readonly Func _remoteSerializer; - private readonly Func _waitTask; + private readonly Func _remoteSerializer = ManagedLibrary.GetMethod>(RemoteIndex, nameof(LocalHandler.SerializeNextNode), BindingFlags.NonPublic); - public RemoteHandler(object RemoteIndex) - { - //get the serializer method - _remoteSerializer = ManagedLibrary.GetMethod>(RemoteIndex, nameof(LocalHandler.SerializeNextNode), BindingFlags.NonPublic); - //get the wait task method - _waitTask = ManagedLibrary.GetMethod>(RemoteIndex, nameof(WaitForDiscoveryAsync), BindingFlags.Public); - } + private readonly Func _waitTask = ManagedLibrary.GetMethod>(RemoteIndex, nameof(WaitForDiscoveryAsync), BindingFlags.Public); /// public CacheNodeAdvertisment? GetNextNode() -- cgit