diff --git a/src/Orleans.Runtime/Activation/IGrainContextActivator.cs b/src/Orleans.Runtime/Activation/IGrainContextActivator.cs index bd854a9ba9..3670a305bc 100644 --- a/src/Orleans.Runtime/Activation/IGrainContextActivator.cs +++ b/src/Orleans.Runtime/Activation/IGrainContextActivator.cs @@ -121,7 +121,7 @@ public interface IGrainContextActivatorProvider /// if an appropriate activator was found, otherwise . bool TryGet(GrainType grainType, [NotNullWhen(true)] out IGrainContextActivator activator); } - + /// /// Creates a grain context for the given grain address. /// @@ -289,7 +289,7 @@ public void Configure(GrainType grainType, GrainProperties properties, GrainType shared.SetComponent(component); } - component.MayInterleavePredicates.Add(_ => true); + component.MayInterleavePredicates.Add(ReentrantPredicate.Instance); } } } @@ -305,11 +305,11 @@ public MayInterleaveConfiguratorProvider(GrainClassMap grainClassMap) public bool TryGetConfigurator(GrainType grainType, GrainProperties properties, out IConfigureGrainContext configurator) { - if (properties.Properties.TryGetValue(WellKnownGrainTypeProperties.MayInterleavePredicate, out var value) + if (properties.Properties.TryGetValue(WellKnownGrainTypeProperties.MayInterleavePredicate, out _) && _grainClassMap.TryGetGrainClass(grainType, out var grainClass)) { var predicate = GetMayInterleavePredicate(grainClass); - configurator = new MayInterleaveConfigurator(message => predicate(message.BodyObject as IInvokable)); + configurator = new MayInterleaveConfigurator(predicate); return true; } @@ -321,7 +321,7 @@ public bool TryGetConfigurator(GrainType grainType, GrainProperties properties, /// Returns interleave predicate depending on whether class is marked with or not. /// /// Grain class. - private static Func GetMayInterleavePredicate(Type grainType) + private static IMayInterleavePredicate GetMayInterleavePredicate(Type grainType) { var attribute = grainType.GetCustomAttribute(); if (attribute is null) @@ -329,12 +329,13 @@ private static Func GetMayInterleavePredicate(Type grainType) return null; } + // here var callbackMethodName = attribute.CallbackMethodName; - var method = grainType.GetMethod(callbackMethodName, BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); + var method = grainType.GetMethod(callbackMethodName, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy); if (method == null) { throw new InvalidOperationException( - $"Class {grainType.FullName} doesn't declare public static method " + + $"Class {grainType.FullName} doesn't declare public method " + $"with name {callbackMethodName} specified in MayInterleave attribute"); } @@ -345,18 +346,64 @@ private static Func GetMayInterleavePredicate(Type grainType) throw new InvalidOperationException( $"Wrong signature of callback method {callbackMethodName} " + $"specified in MayInterleave attribute for grain class {grainType.FullName}. \n" + - $"Expected: public static bool {callbackMethodName}(IInvokable req)"); + $"Expected: public bool {callbackMethodName}(IInvokable req)"); + } + + if (method.IsStatic) + { + return new MayInterleaveStaticPredicate(method.CreateDelegate>()); } - return method.CreateDelegate>(); + var predicateType = typeof(MayInterleaveInstancedPredicate<>).MakeGenericType(grainType); + return (IMayInterleavePredicate)Activator.CreateInstance(predicateType, method); } } + internal interface IMayInterleavePredicate + { + bool Invoke(object instance, IInvokable bodyObject); + } + + internal class ReentrantPredicate : IMayInterleavePredicate + { + private ReentrantPredicate() + { + } + + public static ReentrantPredicate Instance { get; } = new(); + + public bool Invoke(object _, IInvokable bodyObject) => true; + } + + internal class MayInterleaveStaticPredicate : IMayInterleavePredicate + { + private readonly Func _mayInterleavePredicate; + + public MayInterleaveStaticPredicate(Func mayInterleavePredicate) + { + _mayInterleavePredicate = mayInterleavePredicate; + } + + public bool Invoke(object _, IInvokable bodyObject) => _mayInterleavePredicate(bodyObject); + } + + internal class MayInterleaveInstancedPredicate : IMayInterleavePredicate where T : class + { + private readonly Func _mayInterleavePredicate; + + public MayInterleaveInstancedPredicate(MethodInfo mayInterleavePredicateInfo) + { + _mayInterleavePredicate = mayInterleavePredicateInfo.CreateDelegate>(); + } + + public bool Invoke(object instance, IInvokable bodyObject) => _mayInterleavePredicate(instance as T, bodyObject); + } + internal class MayInterleaveConfigurator : IConfigureGrainContext { - private readonly Func _mayInterleavePredicate; + private readonly IMayInterleavePredicate _mayInterleavePredicate; - public MayInterleaveConfigurator(Func mayInterleavePredicate) + public MayInterleaveConfigurator(IMayInterleavePredicate mayInterleavePredicate) { _mayInterleavePredicate = mayInterleavePredicate; } @@ -376,12 +423,12 @@ public void Configure(IGrainContext context) internal class GrainCanInterleave { - public List> MayInterleavePredicates { get; } = new List>(); - public bool MayInterleave(Message message) + public List MayInterleavePredicates { get; } = new List(); + public bool MayInterleave(object instance, Message message) { foreach (var predicate in this.MayInterleavePredicates) { - if (predicate(message)) return true; + if (predicate.Invoke(instance, message.BodyObject as IInvokable)) return true; } return false; diff --git a/src/Orleans.Runtime/Catalog/ActivationData.cs b/src/Orleans.Runtime/Catalog/ActivationData.cs index 86fba03556..cd45fc5886 100644 --- a/src/Orleans.Runtime/Catalog/ActivationData.cs +++ b/src/Orleans.Runtime/Catalog/ActivationData.cs @@ -1016,7 +1016,7 @@ bool MayInvokeRequest(Message incoming) { try { - return canInterleave.MayInterleave(incoming); + return canInterleave.MayInterleave(GrainInstance, incoming); } catch (Exception exception) { diff --git a/test/Grains/TestGrainInterfaces/IReentrancyGrain.cs b/test/Grains/TestGrainInterfaces/IReentrancyGrain.cs index 8a5c840f92..06a5a2dd11 100644 --- a/test/Grains/TestGrainInterfaces/IReentrancyGrain.cs +++ b/test/Grains/TestGrainInterfaces/IReentrancyGrain.cs @@ -23,7 +23,7 @@ public interface INonReentrantGrain : IGrainWithIntegerKey Task SetSelf(INonReentrantGrain self); } - public interface IMayInterleavePredicateGrain : IGrainWithIntegerKey + public interface IMayInterleaveStaticPredicateGrain : IGrainWithIntegerKey { Task One(string arg); // this interleaves only when arg == "reentrant" @@ -35,7 +35,22 @@ public interface IMayInterleavePredicateGrain : IGrainWithIntegerKey Task SubscribeToStream(); Task PushToStream(string item); - Task SetSelf(IMayInterleavePredicateGrain self); + Task SetSelf(IMayInterleaveStaticPredicateGrain self); + } + + public interface IMayInterleaveInstancedPredicateGrain : IGrainWithIntegerKey + { + Task One(string arg); // this interleaves only when arg == "reentrant" + + Task Two(); + Task TwoReentrant(); + + Task Exceptional(); + + Task SubscribeToStream(); + Task PushToStream(string item); + + Task SetSelf(IMayInterleaveInstancedPredicateGrain self); } [Unordered] diff --git a/test/Grains/TestGrains/ReentrantGrain.cs b/test/Grains/TestGrains/ReentrantGrain.cs index 77310f7ba1..9d22e1b299 100644 --- a/test/Grains/TestGrains/ReentrantGrain.cs +++ b/test/Grains/TestGrains/ReentrantGrain.cs @@ -78,11 +78,11 @@ public Task SetSelf(INonReentrantGrain self) } [MayInterleave(nameof(MayInterleave))] - public class MayInterleavePredicateGrain : Grain, IMayInterleavePredicateGrain + public class MayInterleaveStaticPredicateGrain : Grain, IMayInterleaveStaticPredicateGrain { private readonly ILogger logger; - public MayInterleavePredicateGrain(ILoggerFactory loggerFactory) + public MayInterleaveStaticPredicateGrain(ILoggerFactory loggerFactory) { this.logger = loggerFactory.CreateLogger($"{this.GetType().Name}-{this.IdentityString}"); } @@ -111,9 +111,9 @@ public static bool MayInterleave(IInvokable req) static object UnwrapImmutable(object item) => item is Immutable ? ((Immutable)item).Value : item; - private IMayInterleavePredicateGrain Self { get; set; } + private IMayInterleaveStaticPredicateGrain Self { get; set; } - // this interleaves only when arg == "reentrant" + // this interleaves only when arg == "reentrant" // and test predicate will throw when arg = "err" public Task One(string arg) { @@ -151,10 +151,94 @@ public Task PushToStream(string item) return GetStream().OnNextAsync(item); } - IAsyncStream GetStream() => + IAsyncStream GetStream() => this.GetStreamProvider("sms").GetStream("test-stream-interleave", Guid.Empty); - public Task SetSelf(IMayInterleavePredicateGrain self) + public Task SetSelf(IMayInterleaveStaticPredicateGrain self) + { + Self = self; + return Task.CompletedTask; + } + } + + [MayInterleave(nameof(MayInterleave))] + public class MayInterleaveInstancedPredicateGrain : Grain, IMayInterleaveInstancedPredicateGrain + { + private readonly ILogger logger; + + public MayInterleaveInstancedPredicateGrain(ILoggerFactory loggerFactory) + { + this.logger = loggerFactory.CreateLogger($"{this.GetType().Name}-{this.IdentityString}"); + } + + public bool MayInterleave(IInvokable req) + { + // not interested + if (req.GetArgumentCount() == 0) + return false; + + string arg = null; + + // assume single argument message + if (req.GetArgumentCount() == 1) + arg = (string)UnwrapImmutable(req.GetArgument(0)); + + // assume stream message + if (req.GetArgumentCount() == 2) + arg = (string)UnwrapImmutable(req.GetArgument(1)); + + if (arg == "err") + throw new ApplicationException("boom"); + + return arg == "reentrant"; + } + + static object UnwrapImmutable(object item) => item is Immutable ? ((Immutable)item).Value : item; + + private IMayInterleaveInstancedPredicateGrain Self { get; set; } + + // this interleaves only when arg == "reentrant" + // and test predicate will throw when arg = "err" + public Task One(string arg) + { + return Task.FromResult("one"); + } + + public async Task Two() + { + return await Self.One("") + " two"; + } + + public async Task TwoReentrant() + { + return await Self.One("reentrant") + " two"; + } + + public Task Exceptional() + { + return Self.One("err"); + } + + public async Task SubscribeToStream() + { + var stream = GetStream(); + + await stream.SubscribeAsync((item, _) => + { + logger.LogInformation("Received stream item: {Item}", item); + return Task.CompletedTask; + }); + } + + public Task PushToStream(string item) + { + return GetStream().OnNextAsync(item); + } + + IAsyncStream GetStream() => + this.GetStreamProvider("sms").GetStream("test-stream-interleave", Guid.Empty); + + public Task SetSelf(IMayInterleaveInstancedPredicateGrain self) { Self = self; return Task.CompletedTask; diff --git a/test/TesterInternal/MessageScheduling/DisabledCallChainReentrancyTestRunner.cs b/test/TesterInternal/MessageScheduling/DisabledCallChainReentrancyTestRunner.cs index 464a4c5609..1691a9cd47 100644 --- a/test/TesterInternal/MessageScheduling/DisabledCallChainReentrancyTestRunner.cs +++ b/test/TesterInternal/MessageScheduling/DisabledCallChainReentrancyTestRunner.cs @@ -44,9 +44,9 @@ public void NonReentrantGrain(bool performDeadlockDetection) this.logger.LogInformation("Reentrancy NonReentrantGrain Test finished OK."); } - public void NonReentrantGrain_WithMayInterleavePredicate_WhenPredicateReturnsFalse(bool performDeadlockDetection) + public void NonReentrantGrain_WithMayInterleaveStaticPredicate_WhenPredicateReturnsFalse(bool performDeadlockDetection) { - var grain = this.grainFactory.GetGrain(OrleansTestingBase.GetRandomGrainId()); + var grain = this.grainFactory.GetGrain(OrleansTestingBase.GetRandomGrainId()); grain.SetSelf(grain).Wait(); bool timeout = false; bool deadlock = false; @@ -69,6 +69,31 @@ public void NonReentrantGrain_WithMayInterleavePredicate_WhenPredicateReturnsFal this.logger.LogInformation("Reentrancy NonReentrantGrain_WithMayInterleavePredicate_WhenPredicateReturnsFalse Test finished OK."); } + public void NonReentrantGrain_WithMayInterleaveInstancedPredicate_WhenPredicateReturnsFalse(bool performDeadlockDetection) + { + var grain = this.grainFactory.GetGrain(OrleansTestingBase.GetRandomGrainId()); + grain.SetSelf(grain).Wait(); + bool timeout = false; + bool deadlock = false; + try + { + timeout = !grain.Two().Wait(2000); + } + catch (Exception exc) + { + Assert.True(false, string.Format("Unexpected exception {0}: {1}", exc.Message, exc.StackTrace)); + } + if (performDeadlockDetection) + { + Assert.True(deadlock, "Non-reentrant grain should deadlock when MayInterleave predicate returns false"); + } + else + { + Assert.True(timeout, "Non-reentrant grain should timeout when MayInterleave predicate returns false"); + } + this.logger.LogInformation("Reentrancy NonReentrantGrain_WithMayInterleaveInstancedPredicate_WhenPredicateReturnsFalse Test finished OK."); + } + public void UnorderedNonReentrantGrain(bool performDeadlockDetection) { IUnorderedNonReentrantGrain unonreentrant = this.grainFactory.GetGrain(OrleansTestingBase.GetRandomGrainId()); diff --git a/test/TesterInternal/MessageScheduling/DisabledCallChainReentrancyTests.cs b/test/TesterInternal/MessageScheduling/DisabledCallChainReentrancyTests.cs index dabac30679..49d489d897 100644 --- a/test/TesterInternal/MessageScheduling/DisabledCallChainReentrancyTests.cs +++ b/test/TesterInternal/MessageScheduling/DisabledCallChainReentrancyTests.cs @@ -28,9 +28,15 @@ public void NonReentrantGrain() } [Fact, TestCategory("Functional"), TestCategory("Tasks"), TestCategory("Reentrancy")] - public void NonReentrantGrain_WithMayInterleavePredicate_WhenPredicateReturnsFalse() + public void NonReentrantGrain_WithMayInterleaveStaticPredicate_WhenPredicateReturnsFalse() { - this.runner.NonReentrantGrain_WithMayInterleavePredicate_WhenPredicateReturnsFalse(false); + this.runner.NonReentrantGrain_WithMayInterleaveStaticPredicate_WhenPredicateReturnsFalse(false); + } + + [Fact, TestCategory("Functional"), TestCategory("Tasks"), TestCategory("Reentrancy")] + public void NonReentrantGrain_WithMayInterleaveInstancedPredicate_WhenPredicateReturnsFalse() + { + this.runner.NonReentrantGrain_WithMayInterleaveInstancedPredicate_WhenPredicateReturnsFalse(false); } [Fact, TestCategory("Functional"), TestCategory("Tasks"), TestCategory("Reentrancy")] diff --git a/test/TesterInternal/MessageScheduling/ReentrancyTests.cs b/test/TesterInternal/MessageScheduling/ReentrancyTests.cs index 44adb477c9..d4ee70c4ed 100644 --- a/test/TesterInternal/MessageScheduling/ReentrancyTests.cs +++ b/test/TesterInternal/MessageScheduling/ReentrancyTests.cs @@ -68,11 +68,45 @@ public void ReentrantGrain() } this.fixture.Logger.LogInformation("Reentrancy ReentrantGrain Test finished OK."); } - + + [Fact, TestCategory("Functional"), TestCategory("Tasks"), TestCategory("Reentrancy")] + public void NonReentrantGrain_WithMayInterleaveStaticPredicate_WhenPredicateReturnsTrue() + { + var grain = this.fixture.GrainFactory.GetGrain(GetRandomGrainId()); + grain.SetSelf(grain).Wait(); + try + { + Assert.True(grain.TwoReentrant().Wait(2000), "Grain should reenter when MayInterleave predicate returns true"); + } + catch (Exception ex) + { + Assert.True(false, string.Format("Unexpected exception {0}: {1}", ex.Message, ex.StackTrace)); + } + this.fixture.Logger.LogInformation("Reentrancy NonReentrantGrain_WithMayInterleaveStaticPredicate_WhenPredicateReturnsTrue Test finished OK."); + } + + [Fact, TestCategory("Functional"), TestCategory("Tasks"), TestCategory("Reentrancy")] + public async Task NonReentrantGrain_WithMayInterleaveStaticPredicate_WhenPredicateThrows() + { + var grain = this.fixture.GrainFactory.GetGrain(GetRandomGrainId()); + grain.SetSelf(grain).Wait(); + try + { + await grain.Exceptional().WithTimeout(TimeSpan.FromSeconds(2)); + } + catch (Exception ex) + { + Assert.IsType(ex); + Assert.True(ex.Message == "boom", + "Should fail with Orleans runtime exception having all of necessary details"); + } + this.fixture.Logger.LogInformation("Reentrancy NonReentrantGrain_WithMayInterleaveStaticPredicate_WhenPredicateThrows Test finished OK."); + } + [Fact, TestCategory("Functional"), TestCategory("Tasks"), TestCategory("Reentrancy")] - public void NonReentrantGrain_WithMayInterleavePredicate_WhenPredicateReturnsTrue() + public void NonReentrantGrain_WithMayInterleaveInstancedPredicate_WhenPredicateReturnsTrue() { - var grain = this.fixture.GrainFactory.GetGrain(GetRandomGrainId()); + var grain = this.fixture.GrainFactory.GetGrain(GetRandomGrainId()); grain.SetSelf(grain).Wait(); try { @@ -82,13 +116,13 @@ public void NonReentrantGrain_WithMayInterleavePredicate_WhenPredicateReturnsTru { Assert.True(false, string.Format("Unexpected exception {0}: {1}", ex.Message, ex.StackTrace)); } - this.fixture.Logger.LogInformation("Reentrancy NonReentrantGrain_WithMayInterleavePredicate_WhenPredicateReturnsTrue Test finished OK."); + this.fixture.Logger.LogInformation("Reentrancy NonReentrantGrain_WithMayInterleaveInstancedPredicate_WhenPredicateReturnsTrue Test finished OK."); } [Fact, TestCategory("Functional"), TestCategory("Tasks"), TestCategory("Reentrancy")] - public async Task NonReentrantGrain_WithMayInterleavePredicate_WhenPredicateThrows() + public async Task NonReentrantGrain_WithMayInterleaveInstancedPredicate_WhenPredicateThrows() { - var grain = this.fixture.GrainFactory.GetGrain(GetRandomGrainId()); + var grain = this.fixture.GrainFactory.GetGrain(GetRandomGrainId()); grain.SetSelf(grain).Wait(); try { @@ -100,9 +134,9 @@ public async Task NonReentrantGrain_WithMayInterleavePredicate_WhenPredicateThro Assert.True(ex.Message == "boom", "Should fail with Orleans runtime exception having all of necessary details"); } - this.fixture.Logger.LogInformation("Reentrancy NonReentrantGrain_WithMayInterleavePredicate_WhenPredicateThrows Test finished OK."); + this.fixture.Logger.LogInformation("Reentrancy NonReentrantGrain_WithMayInterleaveInstancedPredicate_WhenPredicateThrows Test finished OK."); } - + [Fact, TestCategory("Functional"), TestCategory("Tasks"), TestCategory("Reentrancy")] public void Reentrancy_Deadlock_1() {