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)