| | | 1 | | using System.Linq; |
| | | 2 | | using System.Reflection; |
| | | 3 | | using Elsa.Common.DistributedHosting; |
| | | 4 | | using Elsa.Common.DistributedHosting.DistributedLocks; |
| | | 5 | | using Medallion.Threading; |
| | | 6 | | using Medallion.Threading.FileSystem; |
| | | 7 | | using Microsoft.Extensions.Logging; |
| | | 8 | | using Microsoft.Extensions.Options; |
| | | 9 | | |
| | | 10 | | namespace Elsa.Workflows.Runtime.Distributed; |
| | | 11 | | |
| | | 12 | | /// <summary> |
| | | 13 | | /// Logs when the distributed runtime is backed by a local-only lock provider. |
| | | 14 | | /// </summary> |
| | 14 | 15 | | public class DistributedRuntimeLockProviderValidator( |
| | 14 | 16 | | IDistributedLockProvider distributedLockProvider, |
| | 14 | 17 | | IOptions<DistributedLockingOptions> options, |
| | 14 | 18 | | ILogger<DistributedRuntimeLockProviderValidator> logger) |
| | | 19 | | { |
| | | 20 | | /// <summary> |
| | | 21 | | /// Checks the configured provider. |
| | | 22 | | /// </summary> |
| | | 23 | | public void Validate() |
| | | 24 | | { |
| | 14 | 25 | | var localProviderType = FindLocalProviderType(distributedLockProvider); |
| | 14 | 26 | | var configuredProviderTypeName = distributedLockProvider.GetType().FullName ?? distributedLockProvider.GetType() |
| | | 27 | | |
| | 14 | 28 | | if (localProviderType == null) |
| | | 29 | | { |
| | 0 | 30 | | logger.LogDebug( |
| | 0 | 31 | | "Distributed workflow runtime lock provider {DistributedLockProviderType} is configured. Ensure this pro |
| | 0 | 32 | | configuredProviderTypeName); |
| | 0 | 33 | | return; |
| | | 34 | | } |
| | | 35 | | |
| | 14 | 36 | | var localProviderTypeName = localProviderType.FullName ?? localProviderType.Name; |
| | | 37 | | |
| | 14 | 38 | | if (options.Value.AllowLocalLockProviderInDistributedRuntime) |
| | | 39 | | { |
| | 14 | 40 | | logger.LogDebug( |
| | 14 | 41 | | "Distributed workflow runtime is using acknowledged local-only lock provider {LocalDistributedLockProvid |
| | 14 | 42 | | localProviderTypeName, |
| | 14 | 43 | | configuredProviderTypeName); |
| | 14 | 44 | | return; |
| | | 45 | | } |
| | | 46 | | |
| | 0 | 47 | | logger.LogWarning( |
| | 0 | 48 | | "Distributed workflow runtime is using local-only lock provider {LocalDistributedLockProviderType} through c |
| | 0 | 49 | | localProviderTypeName, |
| | 0 | 50 | | configuredProviderTypeName); |
| | 0 | 51 | | } |
| | | 52 | | |
| | | 53 | | private Type? FindLocalProviderType(IDistributedLockProvider provider, ISet<IDistributedLockProvider>? visited = nul |
| | | 54 | | { |
| | 26 | 55 | | visited ??= new HashSet<IDistributedLockProvider>(ReferenceEqualityComparer.Instance); |
| | | 56 | | |
| | 26 | 57 | | if (!visited.Add(provider)) |
| | 0 | 58 | | return null; |
| | | 59 | | |
| | 26 | 60 | | var providerType = provider.GetType(); |
| | | 61 | | |
| | 26 | 62 | | if (provider is FileDistributedSynchronizationProvider or NoopDistributedSynchronizationProvider) |
| | 14 | 63 | | return providerType; |
| | | 64 | | |
| | 36 | 65 | | foreach (var innerProvider in GetInnerProviders(provider)) |
| | | 66 | | { |
| | 12 | 67 | | var localProviderType = FindLocalProviderType(innerProvider, visited); |
| | | 68 | | |
| | 12 | 69 | | if (localProviderType != null) |
| | 12 | 70 | | return localProviderType; |
| | | 71 | | } |
| | | 72 | | |
| | 0 | 73 | | return null; |
| | 12 | 74 | | } |
| | | 75 | | |
| | | 76 | | private IEnumerable<IDistributedLockProvider> GetInnerProviders(IDistributedLockProvider provider) |
| | | 77 | | { |
| | 36 | 78 | | foreach (var property in provider.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public) |
| | | 79 | | .Where(property => property.GetIndexParameters().Length == 0)) |
| | | 80 | | { |
| | 12 | 81 | | var returnsDistributedLockProvider = typeof(IDistributedLockProvider).IsAssignableFrom(property.PropertyType |
| | 12 | 82 | | var returnsDistributedLockProviders = |
| | 12 | 83 | | property.PropertyType == typeof(System.Collections.IEnumerable) || |
| | 12 | 84 | | TryGetDistributedLockProviderElementType(property.PropertyType, out _); |
| | | 85 | | |
| | 12 | 86 | | if (!returnsDistributedLockProvider && !returnsDistributedLockProviders) |
| | | 87 | | continue; |
| | | 88 | | |
| | | 89 | | object? value; |
| | | 90 | | try |
| | | 91 | | { |
| | 12 | 92 | | value = property.GetValue(provider); |
| | 12 | 93 | | } |
| | 0 | 94 | | catch (TargetInvocationException ex) |
| | | 95 | | { |
| | 0 | 96 | | logger.LogDebug(ex, "Skipping distributed lock provider property {PropertyName} because the getter threw |
| | 0 | 97 | | continue; |
| | | 98 | | } |
| | | 99 | | |
| | 12 | 100 | | if (value is IDistributedLockProvider innerProvider) |
| | | 101 | | { |
| | 12 | 102 | | yield return innerProvider; |
| | | 103 | | } |
| | 0 | 104 | | else if (value is System.Collections.IEnumerable innerProviders) |
| | | 105 | | { |
| | | 106 | | System.Collections.IEnumerator enumerator; |
| | | 107 | | |
| | | 108 | | try |
| | | 109 | | { |
| | 0 | 110 | | enumerator = innerProviders.GetEnumerator(); |
| | 0 | 111 | | } |
| | 0 | 112 | | catch (Exception ex) when (ex is InvalidOperationException or ObjectDisposedException or NotSupportedExc |
| | | 113 | | { |
| | 0 | 114 | | logger.LogDebug(ex, "Skipping distributed lock provider property {PropertyName} because enumeration |
| | 0 | 115 | | continue; |
| | | 116 | | } |
| | | 117 | | |
| | 0 | 118 | | using var disposableEnumerator = enumerator as IDisposable; |
| | | 119 | | |
| | 0 | 120 | | while (true) |
| | | 121 | | { |
| | | 122 | | object? providerItem; |
| | | 123 | | |
| | | 124 | | try |
| | | 125 | | { |
| | 0 | 126 | | if (!enumerator.MoveNext()) |
| | 0 | 127 | | break; |
| | | 128 | | |
| | 0 | 129 | | providerItem = enumerator.Current; |
| | 0 | 130 | | } |
| | 0 | 131 | | catch (Exception ex) when (ex is InvalidOperationException or ObjectDisposedException or NotSupporte |
| | | 132 | | { |
| | 0 | 133 | | logger.LogDebug(ex, "Skipping distributed lock provider property {PropertyName} because enumerat |
| | 0 | 134 | | break; |
| | | 135 | | } |
| | | 136 | | |
| | 0 | 137 | | if (providerItem is IDistributedLockProvider distributedLockProvider) |
| | 0 | 138 | | yield return distributedLockProvider; |
| | | 139 | | } |
| | 0 | 140 | | } |
| | 0 | 141 | | } |
| | 0 | 142 | | } |
| | | 143 | | |
| | | 144 | | private static bool TryGetDistributedLockProviderElementType(Type type, out Type? elementType) |
| | | 145 | | { |
| | 12 | 146 | | elementType = null; |
| | | 147 | | |
| | 12 | 148 | | if (type.IsArray) |
| | | 149 | | { |
| | 0 | 150 | | elementType = type.GetElementType(); |
| | 0 | 151 | | return elementType != null && typeof(IDistributedLockProvider).IsAssignableFrom(elementType); |
| | | 152 | | } |
| | | 153 | | |
| | 12 | 154 | | if (type == typeof(string)) |
| | 0 | 155 | | return false; |
| | | 156 | | |
| | 12 | 157 | | elementType = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>) |
| | 12 | 158 | | ? type.GetGenericArguments()[0] |
| | 12 | 159 | | : type |
| | 12 | 160 | | .GetInterfaces() |
| | 0 | 161 | | .Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>)) |
| | 0 | 162 | | .Select(x => x.GetGenericArguments()[0]) |
| | 12 | 163 | | .FirstOrDefault(x => typeof(IDistributedLockProvider).IsAssignableFrom(x)); |
| | | 164 | | |
| | 12 | 165 | | return elementType != null && typeof(IDistributedLockProvider).IsAssignableFrom(elementType); |
| | | 166 | | } |
| | | 167 | | } |