diff --git a/rd-net/Lifetimes/Annotations/CodeAnnotations.cs b/rd-net/Lifetimes/Annotations/CodeAnnotations.cs index 4611e1442..b4878e1e3 100644 --- a/rd-net/Lifetimes/Annotations/CodeAnnotations.cs +++ b/rd-net/Lifetimes/Annotations/CodeAnnotations.cs @@ -434,12 +434,43 @@ public PublicAPIAttribute([NotNull] string comment) } /// - /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. - /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. + /// Tells the code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that the delegate can only be invoked during method execution + /// (the delegate can be invoked zero or multiple times, but not stored to some field and invoked later, + /// when the containing method is no longer on the execution stack). /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// If is true, the attribute will only take effect + /// if the method invocation is located under the await expression. /// [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class InstantHandleAttribute : Attribute { } + internal sealed class InstantHandleAttribute : Attribute + { + /// + /// Requires the method invocation to be used under the await expression for this attribute to take effect. + /// Can be used for delegate/enumerable parameters of async methods. + /// + public bool RequireAwait { get; set; } + } + + /// + /// This annotation allows enforcing allocation-less usage patterns of delegates for performance-critical APIs. + /// When this annotation is applied to the parameter of a delegate type, + /// the IDE checks the input argument of this parameter: + /// * When a lambda expression or anonymous method is passed as an argument, the IDE verifies that the passed closure + /// has no captures of the containing local variables and the compiler is able to cache the delegate instance + /// to avoid heap allocations. Otherwise, a warning is produced. + /// * The IDE warns when the method name or local function name is passed as an argument because this always results + /// in heap allocation of the delegate instance. + /// + /// + /// In C# 9.0+ code, the IDE will also suggest annotating the anonymous functions with the static modifier + /// to make use of the similar analysis provided by the language/compiler. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class RequireStaticDelegateAttribute : Attribute + { + public bool IsError { get; set; } + } /// /// Indicates that a method does not make any observable state changes. diff --git a/rd-net/Lifetimes/Threading/SpinWaitEx.cs b/rd-net/Lifetimes/Threading/SpinWaitEx.cs index d0dacf5f8..d614aa0fd 100644 --- a/rd-net/Lifetimes/Threading/SpinWaitEx.cs +++ b/rd-net/Lifetimes/Threading/SpinWaitEx.cs @@ -3,76 +3,99 @@ using JetBrains.Annotations; using JetBrains.Lifetimes; - namespace JetBrains.Threading -{ +{ /// /// Extensions for static methods. /// public static class SpinWaitEx { - /// - /// Spins while is false. + /// Spins while is false. /// /// Stops spinning when condition is true [PublicAPI] - public static void SpinUntil(Func condition) + public static void SpinUntil([InstantHandle] Func condition) { SpinUntil(Lifetime.Eternal, TimeSpan.MaxValue, condition); } /// - /// Spins while is alive and is false. + /// Spins while is alive and is false. /// /// Stops spinning and return false when lifetime is no more alive /// Stops spinning and return false when condition is true - /// false if is not alive or canceled during spinning. - /// Otherwise true (when returns true) + /// + /// false if is not alive or canceled during spinning. + /// Otherwise, true (when returns true) + /// [PublicAPI] - public static bool SpinUntil(Lifetime lifetime, Func condition) + public static bool SpinUntil(Lifetime lifetime, [InstantHandle] Func condition) { return SpinUntil(lifetime, TimeSpan.MaxValue, condition); } /// - /// Spins while is not elapsed and is false. + /// Spins while is not elapsed and is false. /// /// Stops spinning and return false when timeout is alive /// Stops spinning and return false when condition is true - /// false if is zero or elapsed during spinning. - /// Otherwise true (when returns true) + /// + /// false if is zero or elapsed during spinning. + /// Otherwise, true (when returns true) + /// [PublicAPI] - public static bool SpinUntil(TimeSpan timeout, Func condition) + public static bool SpinUntil(TimeSpan timeout, [InstantHandle] Func condition) { return SpinUntil(Lifetime.Eternal, (long)timeout.TotalMilliseconds, condition); } - + /// - /// Spins while is alive, is not elapsed and is false. + /// Spins while is alive, is not elapsed and is false. /// /// Stops spinning and return false when lifetime is no more alive /// Stops spinning and return false when timeout is alive /// Stops spinning and return false when condition is true - /// false if is not alive or canceled during spinning, is zero or elapsed during spinning. - /// Otherwise true (when returns true) + /// + /// false if is not alive or canceled during spinning, is zero + /// or elapsed during spinning. Otherwise, true (when returns true) + /// [PublicAPI] - public static bool SpinUntil(Lifetime lifetime, TimeSpan timeout, Func condition) + public static bool SpinUntil(Lifetime lifetime, TimeSpan timeout, [InstantHandle] Func condition) { return SpinUntil(lifetime, (long)timeout.TotalMilliseconds, condition); } - - + + /// + /// Spins while is alive, is not elapsed and is false. + /// + /// Stops spinning and return false when lifetime is no more alive + /// Stops spinning and return false when timeout is alive + /// Stops spinning and return false when condition is true + /// + /// false if is not alive or canceled during spinning, is zero + /// or elapsed during spinning. Otherwise, true (when returns true) + /// + [PublicAPI] + public static bool SpinUntil(Lifetime lifetime, long timeoutMs, [InstantHandle] Func condition) + { + return SpinUntil(lifetime, timeoutMs, state: condition, static condition => condition()); + } + /// - /// Spins while is alive, is not elapsed and is false. + /// Spins while is alive, is not elapsed and is false. /// /// Stops spinning and return false when lifetime is no more alive /// Stops spinning and return false when timeout is alive /// Stops spinning and return false when condition is true - /// false if is not alive or canceled during spinning, is zero or elapsed during spinning. - /// Otherwise true (when returns true) + /// State to pass into delegate to avoid closures + /// + /// false if is not alive or canceled during spinning, is zero + /// or elapsed during spinning. Otherwise, true (when returns true) + /// [PublicAPI] - public static bool SpinUntil(Lifetime lifetime, long timeoutMs, Func condition) + public static bool SpinUntil( + Lifetime lifetime, long timeoutMs, TState state, [InstantHandle, RequireStaticDelegate] Func condition) { #if !NET35 var s = new SpinWait(); @@ -84,9 +107,9 @@ public static bool SpinUntil(Lifetime lifetime, long timeoutMs, Func condi if (!lifetime.IsAlive) return false; - if (condition()) + if (condition(state)) return true; - + if (Environment.TickCount - start > timeoutMs) return false; @@ -95,27 +118,31 @@ public static bool SpinUntil(Lifetime lifetime, long timeoutMs, Func condi #else Thread.Sleep(0); #endif - } + } } - #if !NET35 /// - /// Spins in ASYNC manner (not consuming thread or CPU resources) while is alive, is not elapsed and is false. - /// Sleeps in async fashion (using for each time between check. - /// Only cancellation could immediately return execution from delay. + /// Spins in ASYNC manner (not consuming thread or CPU resources) while is alive, + /// is not elapsed and is false. + /// Sleeps in async fashion (using + /// for each time between check. + /// Only cancellation could immediately return execution from delay. /// /// Stops spinning and return false when lifetime is no more alive /// Stops spinning and return false when timeout is alive /// Interval to delay /// Stops spinning and return false when condition is true - /// false if is not alive or canceled during spinning, is zero or elapsed during spinning. - /// Otherwise true (when returns true) + /// + /// false if is not alive or canceled during spinning, is zero + /// or elapsed during spinning. Otherwise, true (when returns true) + /// [PublicAPI] - public static async System.Threading.Tasks.Task SpinUntilAsync(Lifetime lifetime, long timeoutMs, int delayBetweenChecksMs, Func condition) + public static async System.Threading.Tasks.Task SpinUntilAsync( + Lifetime lifetime, long timeoutMs, int delayBetweenChecksMs, [InstantHandle(RequireAwait = true)] Func condition) { var start = Environment.TickCount; - + while (true) { if (!lifetime.IsAlive || Environment.TickCount - start > timeoutMs)