Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SpinWaitEx: overload to avoid allocations, annotations #482

Merged
merged 1 commit into from
May 27, 2024
Merged
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
37 changes: 34 additions & 3 deletions rd-net/Lifetimes/Annotations/CodeAnnotations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,12 +434,43 @@ public PublicAPIAttribute([NotNull] string comment)
}

/// <summary>
/// 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 <see cref="RequireAwait"/> is true, the attribute will only take effect
/// if the method invocation is located under the <c>await</c> expression.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
internal sealed class InstantHandleAttribute : Attribute { }
internal sealed class InstantHandleAttribute : Attribute
{
/// <summary>
/// Requires the method invocation to be used under the <c>await</c> expression for this attribute to take effect.
/// Can be used for delegate/enumerable parameters of <c>async</c> methods.
/// </summary>
public bool RequireAwait { get; set; }
}

/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// In C# 9.0+ code, the IDE will also suggest annotating the anonymous functions with the <c>static</c> modifier
/// to make use of the similar analysis provided by the language/compiler.
/// </remarks>
[AttributeUsage(AttributeTargets.Parameter)]
internal sealed class RequireStaticDelegateAttribute : Attribute
{
public bool IsError { get; set; }
}

/// <summary>
/// Indicates that a method does not make any observable state changes.
Expand Down
97 changes: 62 additions & 35 deletions rd-net/Lifetimes/Threading/SpinWaitEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,76 +3,99 @@
using JetBrains.Annotations;
using JetBrains.Lifetimes;


namespace JetBrains.Threading
{
{
/// <summary>
/// Extensions for <see cref="SpinWait"/> static methods.
/// </summary>
public static class SpinWaitEx
{

/// <summary>
/// Spins while <paramref name="condition"/> is false.
/// Spins while <paramref name="condition"/> is false.
/// </summary>
/// <param name="condition">Stops spinning when condition is true</param>
[PublicAPI]
public static void SpinUntil(Func<bool> condition)
public static void SpinUntil([InstantHandle] Func<bool> condition)
{
SpinUntil(Lifetime.Eternal, TimeSpan.MaxValue, condition);
}

/// <summary>
/// Spins while <paramref name="lifetime"/> is alive and <paramref name="condition"/> is false.
/// Spins while <paramref name="lifetime"/> is alive and <paramref name="condition"/> is false.
/// </summary>
/// <param name="lifetime">Stops spinning and return <c>false</c> when lifetime is no more alive</param>
/// <param name="condition">Stops spinning and return <c>false</c> when condition is true</param>
/// <returns><c>false</c> if <paramref name="lifetime"/> is not alive or canceled during spinning.
/// Otherwise <c>true</c> (when <paramref name="condition"/> returns true)</returns>
/// <returns>
/// <c>false</c> if <paramref name="lifetime"/> is not alive or canceled during spinning.
/// Otherwise, <c>true</c> (when <paramref name="condition"/> returns true)
/// </returns>
[PublicAPI]
public static bool SpinUntil(Lifetime lifetime, Func<bool> condition)
public static bool SpinUntil(Lifetime lifetime, [InstantHandle] Func<bool> condition)
{
return SpinUntil(lifetime, TimeSpan.MaxValue, condition);
}

/// <summary>
/// Spins while <paramref name="timeout"/> is not elapsed and <paramref name="condition"/> is false.
/// Spins while <paramref name="timeout"/> is not elapsed and <paramref name="condition"/> is false.
/// </summary>
/// <param name="timeout">Stops spinning and return <c>false</c> when timeout is alive</param>
/// <param name="condition">Stops spinning and return <c>false</c> when condition is true</param>
/// <returns><c>false</c> if <paramref name="timeout"/> is zero or elapsed during spinning.
/// Otherwise <c>true</c> (when <paramref name="condition"/> returns true)</returns>
/// <returns>
/// <c>false</c> if <paramref name="timeout"/> is zero or elapsed during spinning.
/// Otherwise, <c>true</c> (when <paramref name="condition"/> returns true)
/// </returns>
[PublicAPI]
public static bool SpinUntil(TimeSpan timeout, Func<bool> condition)
public static bool SpinUntil(TimeSpan timeout, [InstantHandle] Func<bool> condition)
{
return SpinUntil(Lifetime.Eternal, (long)timeout.TotalMilliseconds, condition);
}

/// <summary>
/// Spins while <paramref name="lifetime"/> is alive, <paramref name="timeout"/> is not elapsed and <paramref name="condition"/> is false.
/// Spins while <paramref name="lifetime"/> is alive, <paramref name="timeout"/> is not elapsed and <paramref name="condition"/> is false.
/// </summary>
/// <param name="lifetime">Stops spinning and return <c>false</c> when lifetime is no more alive</param>
/// <param name="timeout">Stops spinning and return <c>false</c> when timeout is alive</param>
/// <param name="condition">Stops spinning and return <c>false</c> when condition is true</param>
/// <returns><c>false</c> if <paramref name="lifetime"/> is not alive or canceled during spinning, <paramref name="timeout"/> is zero or elapsed during spinning.
/// Otherwise <c>true</c> (when <paramref name="condition"/> returns true)</returns>
/// <returns>
/// <c>false</c> if <paramref name="lifetime"/> is not alive or canceled during spinning, <paramref name="timeout"/> is zero
/// or elapsed during spinning. Otherwise, <c>true</c> (when <paramref name="condition"/> returns true)
/// </returns>
[PublicAPI]
public static bool SpinUntil(Lifetime lifetime, TimeSpan timeout, Func<bool> condition)
public static bool SpinUntil(Lifetime lifetime, TimeSpan timeout, [InstantHandle] Func<bool> condition)
{
return SpinUntil(lifetime, (long)timeout.TotalMilliseconds, condition);
}



/// <summary>
/// Spins while <paramref name="lifetime"/> is alive, <paramref name="timeoutMs"/> is not elapsed and <paramref name="condition"/> is false.
/// </summary>
/// <param name="lifetime">Stops spinning and return <c>false</c> when lifetime is no more alive</param>
/// <param name="timeoutMs">Stops spinning and return <c>false</c> when timeout is alive</param>
/// <param name="condition">Stops spinning and return <c>false</c> when condition is true</param>
/// <returns>
/// <c>false</c> if <paramref name="lifetime"/> is not alive or canceled during spinning, <paramref name="timeoutMs"/> is zero
/// or elapsed during spinning. Otherwise, <c>true</c> (when <paramref name="condition"/> returns true)
/// </returns>
[PublicAPI]
public static bool SpinUntil(Lifetime lifetime, long timeoutMs, [InstantHandle] Func<bool> condition)
{
return SpinUntil(lifetime, timeoutMs, state: condition, static condition => condition());
}

/// <summary>
/// Spins while <paramref name="lifetime"/> is alive, <paramref name="timeoutMs"/> is not elapsed and <paramref name="condition"/> is false.
/// Spins while <paramref name="lifetime"/> is alive, <paramref name="timeoutMs"/> is not elapsed and <paramref name="condition"/> is false.
/// </summary>
/// <param name="lifetime">Stops spinning and return <c>false</c> when lifetime is no more alive</param>
/// <param name="timeoutMs">Stops spinning and return <c>false</c> when timeout is alive</param>
/// <param name="condition">Stops spinning and return <c>false</c> when condition is true</param>
/// <returns><c>false</c> if <paramref name="lifetime"/> is not alive or canceled during spinning, <paramref name="timeoutMs"/> is zero or elapsed during spinning.
/// Otherwise <c>true</c> (when <paramref name="condition"/> returns true)</returns>
/// <param name="state">State to pass into delegate to avoid closures</param>
/// <returns>
/// <c>false</c> if <paramref name="lifetime"/> is not alive or canceled during spinning, <paramref name="timeoutMs"/> is zero
/// or elapsed during spinning. Otherwise, <c>true</c> (when <paramref name="condition"/> returns true)
/// </returns>
[PublicAPI]
public static bool SpinUntil(Lifetime lifetime, long timeoutMs, Func<bool> condition)
public static bool SpinUntil<TState>(
Lifetime lifetime, long timeoutMs, TState state, [InstantHandle, RequireStaticDelegate] Func<TState, bool> condition)
{
#if !NET35
var s = new SpinWait();
Expand All @@ -84,9 +107,9 @@ public static bool SpinUntil(Lifetime lifetime, long timeoutMs, Func<bool> condi
if (!lifetime.IsAlive)
return false;

if (condition())
if (condition(state))
return true;

if (Environment.TickCount - start > timeoutMs)
return false;

Expand All @@ -95,27 +118,31 @@ public static bool SpinUntil(Lifetime lifetime, long timeoutMs, Func<bool> condi
#else
Thread.Sleep(0);
#endif
}
}
}


#if !NET35
/// <summary>
/// Spins in ASYNC manner (not consuming thread or CPU resources) while <paramref name="lifetime"/> is alive, <paramref name="timeoutMs"/> is not elapsed and <paramref name="condition"/> is false.
/// Sleeps in async fashion (using <see cref="System.Threading.Tasks.Task.Delay(System.TimeSpan, CancellationToken)"/> for <paramref name="delayBetweenChecksMs"/> each time between <paramref name="condition"/> check.
/// Only <paramref name="lifetime"/> cancellation could immediately return execution from delay.
/// Spins in ASYNC manner (not consuming thread or CPU resources) while <paramref name="lifetime"/> is alive,
/// <paramref name="timeoutMs"/> is not elapsed and <paramref name="condition"/> is false.
/// Sleeps in async fashion (using <see cref="System.Threading.Tasks.Task.Delay(System.TimeSpan, CancellationToken)"/>
/// for <paramref name="delayBetweenChecksMs"/> each time between <paramref name="condition"/> check.
/// Only <paramref name="lifetime"/> cancellation could immediately return execution from delay.
/// </summary>
/// <param name="lifetime">Stops spinning and return <c>false</c> when lifetime is no more alive</param>
/// <param name="timeoutMs">Stops spinning and return <c>false</c> when timeout is alive</param>
/// <param name="delayBetweenChecksMs">Interval to delay</param>
/// <param name="condition">Stops spinning and return <c>false</c> when condition is true</param>
/// <returns><c>false</c> if <paramref name="lifetime"/> is not alive or canceled during spinning, <paramref name="timeoutMs"/> is zero or elapsed during spinning.
/// Otherwise <c>true</c> (when <paramref name="condition"/> returns true)</returns>
/// <returns>
/// <c>false</c> if <paramref name="lifetime"/> is not alive or canceled during spinning, <paramref name="timeoutMs"/> is zero
/// or elapsed during spinning. Otherwise, <c>true</c> (when <paramref name="condition"/> returns true)
/// </returns>
[PublicAPI]
public static async System.Threading.Tasks.Task<bool> SpinUntilAsync(Lifetime lifetime, long timeoutMs, int delayBetweenChecksMs, Func<bool> condition)
public static async System.Threading.Tasks.Task<bool> SpinUntilAsync(
Lifetime lifetime, long timeoutMs, int delayBetweenChecksMs, [InstantHandle(RequireAwait = true)] Func<bool> condition)
{
var start = Environment.TickCount;

while (true)
{
if (!lifetime.IsAlive || Environment.TickCount - start > timeoutMs)
Expand Down
Loading