Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Orleans.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
<Project Path="test/Orleans.Placement.Tests/Orleans.Placement.Tests.csproj" />
<Project Path="test/Orleans.GrainDirectory.Tests/Orleans.GrainDirectory.Tests.csproj" />
<Project Path="test/Orleans.Streaming.Tests/Orleans.Streaming.Tests.csproj" />
<Project Path="test/Orleans.BroadcastChannel.Tests/Orleans.BroadcastChannel.Tests.csproj" />
<Project Path="test/Orleans.EventSourcing.Tests/Orleans.EventSourcing.Tests.csproj" />
<Project Path="test/Orleans.DurableJobs.Tests/Orleans.DurableJobs.Tests.csproj" />
</Folder>
Expand Down
18 changes: 5 additions & 13 deletions src/Dashboard/Orleans.Dashboard.App/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Orleans.Serialization.TypeSystem;

namespace Orleans.BroadcastChannel
Expand All @@ -7,7 +8,7 @@ namespace Orleans.BroadcastChannel
/// Default implementation of <see cref="IChannelNamespacePredicateProvider"/> for internally supported stream predicates.
/// </summary>
public class DefaultChannelNamespacePredicateProvider : IChannelNamespacePredicateProvider
{
{
/// <inheritdoc/>
public bool TryGetPredicate(string predicatePattern, out IChannelNamespacePredicate predicate)
{
Expand All @@ -30,17 +31,38 @@ public bool TryGetPredicate(string predicatePattern, out IChannelNamespacePredic
}

/// <summary>
/// Stream namespace predicate provider which supports objects which can be constructed and optionally accept a string as a constructor argument.
/// Channel namespace predicate provider which supports objects which can be constructed and optionally accept a string as a constructor argument.
/// </summary>
public class ConstructorChannelNamespacePredicateProvider : IChannelNamespacePredicateProvider
{
#if NET9_0_OR_GREATER
private readonly Lock _lock = new();
#else
private readonly object _lock = new();
#endif
private readonly Dictionary<string, bool> _allowedPredicateTypes = new(StringComparer.Ordinal);

/// <summary>
/// The prefix used to identify this predicate provider.
/// </summary>
public const string Prefix = "ctor";

/// <summary>
/// Formats a stream namespace predicate which indicates a concrete <see cref="IChannelNamespacePredicate"/> type to be constructed, along with an optional argument.
/// Registers a predicate type as allowed for construction.
/// </summary>
/// <param name="predicateType">The predicate type to register.</param>
public void RegisterPredicateType(Type predicateType)
{
ArgumentNullException.ThrowIfNull(predicateType);
var typeName = RuntimeTypeNameFormatter.Format(predicateType);
lock (_lock)
{
_allowedPredicateTypes[typeName] = true;
}
}

/// <summary>
/// Formats a channel namespace predicate which indicates a concrete <see cref="IChannelNamespacePredicate"/> type to be constructed, along with an optional argument.
/// </summary>
public static string FormatPattern(Type predicateType, string constructorArgument)
{
Expand Down Expand Up @@ -76,7 +98,24 @@ public bool TryGetPredicate(string predicatePattern, out IChannelNamespacePredic
arg = predicatePattern[(index + 1)..];
}

bool allowed;
lock (_lock)
{
allowed = _allowedPredicateTypes.ContainsKey(typeName);
}

if (!allowed)
{
throw new InvalidOperationException($"Type \"{typeName}\" is not a registered channel namespace predicate. Ensure the grain interface assembly is loaded and the predicate type is used in an [{nameof(ImplicitChannelSubscriptionAttribute)}].");
}

var type = Type.GetType(typeName, throwOnError: true);

if (!typeof(IChannelNamespacePredicate).IsAssignableFrom(type))
{
throw new InvalidOperationException($"Type \"{type}\" is not a valid channel namespace predicate because it does not implement {nameof(IChannelNamespacePredicate)}.");
}

if (string.IsNullOrEmpty(arg))
{
predicate = (IChannelNamespacePredicate)Activator.CreateInstance(type);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Orleans.BroadcastChannel;
using Orleans.Metadata;
using Orleans.Runtime;
Expand Down Expand Up @@ -70,6 +71,16 @@ public ImplicitChannelSubscriptionAttribute(IChannelNamespacePredicate predicate
/// <inheritdoc />
public IEnumerable<Dictionary<string, string>> GetBindings(IServiceProvider services, Type grainClass, GrainType grainType)
{
// Register the predicate type so the constructor provider will accept it.
foreach (var provider in services.GetServices<IChannelNamespacePredicateProvider>())
{
if (provider is ConstructorChannelNamespacePredicateProvider ctorProvider)
{
ctorProvider.RegisterPredicateType(Predicate.GetType());
break;
}
}

var binding = new Dictionary<string, string>
{
[WellKnownGrainTypeProperties.BindingTypeKey] = WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue,
Expand Down
11 changes: 11 additions & 0 deletions src/Orleans.Streaming/Predicates/StreamSubscriptionAttributes.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Orleans.Metadata;
using Orleans.Runtime;
using Orleans.Streams;
Expand Down Expand Up @@ -73,6 +74,16 @@ public ImplicitStreamSubscriptionAttribute(IStreamNamespacePredicate predicate,
/// <inheritdoc />
public IEnumerable<Dictionary<string, string>> GetBindings(IServiceProvider services, Type grainClass, GrainType grainType)
{
// Register the predicate type so the constructor provider will accept it.
foreach (var provider in services.GetServices<IStreamNamespacePredicateProvider>())
{
if (provider is ConstructorStreamNamespacePredicateProvider ctorProvider)
{
ctorProvider.RegisterPredicateType(Predicate.GetType());
break;
}
}

var binding = new Dictionary<string, string>
{
[WellKnownGrainTypeProperties.BindingTypeKey] = WellKnownGrainTypeProperties.StreamBindingTypeValue,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Orleans.Serialization.TypeSystem;

namespace Orleans.Streams
Expand All @@ -7,7 +8,7 @@ namespace Orleans.Streams
/// Default implementation of <see cref="IStreamNamespacePredicateProvider"/> for internally supported stream predicates.
/// </summary>
public class DefaultStreamNamespacePredicateProvider : IStreamNamespacePredicateProvider
{
{
/// <inheritdoc/>
public bool TryGetPredicate(string predicatePattern, out IStreamNamespacePredicate predicate)
{
Expand All @@ -34,11 +35,32 @@ public bool TryGetPredicate(string predicatePattern, out IStreamNamespacePredica
/// </summary>
public class ConstructorStreamNamespacePredicateProvider : IStreamNamespacePredicateProvider
{
#if NET9_0_OR_GREATER
private readonly Lock _lock = new();
#else
private readonly object _lock = new();
#endif
private readonly Dictionary<string, bool> _allowedPredicateTypes = new(StringComparer.Ordinal);

/// <summary>
/// The prefix used to identify this predicate provider.
/// </summary>
public const string Prefix = "ctor";

/// <summary>
/// Registers a predicate type as allowed for construction.
/// </summary>
/// <param name="predicateType">The predicate type to register.</param>
public void RegisterPredicateType(Type predicateType)
{
ArgumentNullException.ThrowIfNull(predicateType);
var typeName = RuntimeTypeNameFormatter.Format(predicateType);
lock (_lock)
{
_allowedPredicateTypes[typeName] = true;
}
}

/// <summary>
/// Formats a stream namespace predicate which indicates a concrete <see cref="IStreamNamespacePredicate"/> type to be constructed, along with an optional argument.
/// </summary>
Expand Down Expand Up @@ -76,7 +98,24 @@ public bool TryGetPredicate(string predicatePattern, out IStreamNamespacePredica
arg = predicatePattern[(index + 1)..];
}

bool allowed;
lock (_lock)
{
allowed = _allowedPredicateTypes.ContainsKey(typeName);
}

if (!allowed)
{
throw new InvalidOperationException($"Type \"{typeName}\" is not a registered stream namespace predicate. Ensure the grain interface assembly is loaded and the predicate type is used in an [{nameof(ImplicitStreamSubscriptionAttribute)}].");
}

var type = Type.GetType(typeName, throwOnError: true);

if (!typeof(IStreamNamespacePredicate).IsAssignableFrom(type))
{
throw new InvalidOperationException($"Type \"{type}\" is not a valid stream namespace predicate because it does not implement {nameof(IStreamNamespacePredicate)}.");
}

if (string.IsNullOrEmpty(arg))
{
predicate = (IStreamNamespacePredicate)Activator.CreateInstance(type);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using Orleans.BroadcastChannel;
using Orleans.Serialization.TypeSystem;
using Xunit;

namespace UnitTests;

/// <summary>
/// Tests for <see cref="ConstructorChannelNamespacePredicateProvider"/> predicate type registration and resolution.
/// </summary>
[TestCategory("BVT"), TestCategory("Predicates")]
public class ConstructorChannelNamespacePredicateProviderTests
{
[Fact]
public void RegisteredPredicateType_Succeeds()
{
var provider = new ConstructorChannelNamespacePredicateProvider();
provider.RegisterPredicateType(typeof(TestChannelPredicate));
var pattern = ConstructorChannelNamespacePredicateProvider.FormatPattern(typeof(TestChannelPredicate), constructorArgument: null);

var result = provider.TryGetPredicate(pattern, out var predicate);

Assert.True(result);
Assert.NotNull(predicate);
Assert.IsType<TestChannelPredicate>(predicate);
}

[Fact]
public void RegisteredPredicateTypeWithArg_Succeeds()
{
var provider = new ConstructorChannelNamespacePredicateProvider();
provider.RegisterPredicateType(typeof(TestChannelPredicateWithArg));
var pattern = ConstructorChannelNamespacePredicateProvider.FormatPattern(typeof(TestChannelPredicateWithArg), constructorArgument: "ch-ns");

var result = provider.TryGetPredicate(pattern, out var predicate);

Assert.True(result);
Assert.NotNull(predicate);
Assert.IsType<TestChannelPredicateWithArg>(predicate);
Assert.True(predicate.IsMatch("ch-ns"));
}

[Fact]
public void UnregisteredType_Throws()
{
var provider = new ConstructorChannelNamespacePredicateProvider();
var pattern = $"ctor:{RuntimeTypeNameFormatter.Format(typeof(System.IO.FileInfo))}:C:\\temp\\evil.txt";

Assert.Throws<InvalidOperationException>(() => provider.TryGetPredicate(pattern, out _));
}

[Fact]
public void UnregisteredArbitraryType_Throws()
{
var provider = new ConstructorChannelNamespacePredicateProvider();
var pattern = $"ctor:{RuntimeTypeNameFormatter.Format(typeof(System.Collections.ArrayList))}";

Assert.Throws<InvalidOperationException>(() => provider.TryGetPredicate(pattern, out _));
}

[Fact]
public void NonMatchingPrefix_ReturnsFalse()
{
var provider = new ConstructorChannelNamespacePredicateProvider();

var result = provider.TryGetPredicate("namespace:test", out var predicate);

Assert.False(result);
Assert.Null(predicate);
}

public class TestChannelPredicate : IChannelNamespacePredicate
{
public string PredicatePattern => ConstructorChannelNamespacePredicateProvider.FormatPattern(typeof(TestChannelPredicate), constructorArgument: null);

public bool IsMatch(string streamNamespace) => true;
}

public class TestChannelPredicateWithArg : IChannelNamespacePredicate
{
private readonly string _namespace;

public TestChannelPredicateWithArg(string ns) => _namespace = ns;

public string PredicatePattern => ConstructorChannelNamespacePredicateProvider.FormatPattern(typeof(TestChannelPredicateWithArg), _namespace);

public bool IsMatch(string streamNamespace) => string.Equals(_namespace, streamNamespace, StringComparison.Ordinal);
}
}
Loading
Loading