mirror of
https://github.com/dotnet/skills.git
synced 2026-09-20 09:49:54 +08:00
042a9d2b2c
* Reorganize plugins per agreed structure (#271) Split the dotnet plugin into domain-specific plugins per discussion in #271: - dotnet: narrowed to common everyday C#/.NET coding tasks - dotnet-diag: perf investigations, debugging, incident analysis - dotnet-data: data access and Entity Framework - dotnet-aspnet, dotnet-wpf, dotnet-winforms, dotnet-maui: scaffolded empty to convey intent for future framework-specific skills Moved skills, tests, and CODEOWNERS entries accordingly. No file content was changed; all skill/test moves are pure renames. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Improve dotnet-diag and dotnet-winforms descriptions - dotnet-diag: add '.NET' qualifier to clarify scope - dotnet-winforms: use official 'Windows Forms (WinForms)' name Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add .NET qualifier to dotnet-data description Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove empty plugins; list them in CONTRIBUTING.md as future options Remove the four empty placeholder plugins (dotnet-aspnet, dotnet-wpf, dotnet-winforms, dotnet-maui) that had only plugin.json and no skills. Add a 'Plugin organization' section to CONTRIBUTING.md that describes the domain-specific plugin groupings introduced in this PR and lists the four removed plugin names as reserved candidates for future contributions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Create dotnet-upgrade plugin with migration/upgrade skills Move migrate-nullable-references, thread-abort-migration, and dotnet-aot-compat from dotnet to the new dotnet-upgrade plugin. Update CODEOWNERS paths (same owners), CONTRIBUTING.md plugin table, and fix the thread-abort-migration SKILL.md link in CONTRIBUTING.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
261 lines
8.8 KiB
C#
261 lines
8.8 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
#nullable enable
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Azure.Core.Pipeline;
|
|
|
|
namespace Azure.Core
|
|
{
|
|
/// <summary>
|
|
/// Primitive that combines async lock and value cache
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
internal sealed class AsyncLockWithValue<T>
|
|
{
|
|
private readonly object _syncObj = new();
|
|
private Queue<TaskCompletionSource<LockOrValue>>? _waiters;
|
|
private bool _isLocked;
|
|
private bool _hasValue;
|
|
private long _index;
|
|
private T? _value;
|
|
|
|
public bool HasValue
|
|
{
|
|
get
|
|
{
|
|
lock (_syncObj)
|
|
{
|
|
return _hasValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
public AsyncLockWithValue() { }
|
|
|
|
public AsyncLockWithValue(T value)
|
|
{
|
|
_hasValue = true;
|
|
_value = value;
|
|
}
|
|
|
|
public bool TryGetValue(out T? value)
|
|
{
|
|
lock (_syncObj)
|
|
{
|
|
if (_hasValue)
|
|
{
|
|
value = _value;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
value = default;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Method that either returns cached value or acquire a lock.
|
|
/// If one caller has acquired a lock, other callers will be waiting for the lock to be released.
|
|
/// If value is set, lock is released and all waiters get that value.
|
|
/// If value isn't set, the next waiter in the queue will get the lock.
|
|
/// </summary>
|
|
/// <param name="async"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async ValueTask<LockOrValue> GetLockOrValueAsync(bool async, CancellationToken cancellationToken = default)
|
|
{
|
|
TaskCompletionSource<LockOrValue> valueTcs;
|
|
lock (_syncObj)
|
|
{
|
|
// If there is a value, just return it
|
|
if (_hasValue)
|
|
{
|
|
return new LockOrValue(_value!);
|
|
}
|
|
|
|
// If lock isn't acquire yet, acquire it and return to the caller
|
|
if (!_isLocked)
|
|
{
|
|
_isLocked = true;
|
|
_index = unchecked(_index + 1);
|
|
return new LockOrValue(this, _index);
|
|
}
|
|
|
|
// Check cancellationToken before instantiating waiter
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// If lock is already taken, create a waiter and wait either until value is set or lock can be acquired by this waiter
|
|
_waiters ??= new Queue<TaskCompletionSource<LockOrValue>>();
|
|
// if async == false, valueTcs will be waited only in this thread and only synchronously, so RunContinuationsAsynchronously isn't needed.
|
|
valueTcs = new TaskCompletionSource<LockOrValue>(async ? TaskCreationOptions.RunContinuationsAsynchronously : TaskCreationOptions.None);
|
|
_waiters.Enqueue(valueTcs);
|
|
}
|
|
|
|
try
|
|
{
|
|
if (async)
|
|
{
|
|
return await valueTcs.Task.AwaitWithCancellation(cancellationToken);
|
|
}
|
|
|
|
#pragma warning disable AZC0104 // Use EnsureCompleted() directly on asynchronous method return value.
|
|
#pragma warning disable AZC0111 // DO NOT use EnsureCompleted in possibly asynchronous scope.
|
|
valueTcs.Task.Wait(cancellationToken);
|
|
return valueTcs.Task.EnsureCompleted();
|
|
#pragma warning restore AZC0111 // DO NOT use EnsureCompleted in possibly asynchronous scope.
|
|
#pragma warning restore AZC0104 // Use EnsureCompleted() directly on asynchronous method return value.
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// Throw OperationCanceledException only if another thread hasn't set a value to this waiter
|
|
// by calling either Reset or SetValue
|
|
if (valueTcs.TrySetCanceled(cancellationToken))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
return valueTcs.Task.Result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set value to the cache and to all the waiters
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <param name="lockIndex"></param>
|
|
private void SetValue(T value, in long lockIndex)
|
|
{
|
|
Queue<TaskCompletionSource<LockOrValue>> waiters;
|
|
lock (_syncObj)
|
|
{
|
|
if (lockIndex != _index)
|
|
{
|
|
throw new InvalidOperationException($"Disposed {nameof(LockOrValue)} tries to set value. Current index: {_index}, {nameof(LockOrValue)} index: {lockIndex}");
|
|
}
|
|
|
|
_value = value;
|
|
_hasValue = true;
|
|
_index = 0;
|
|
_isLocked = false;
|
|
if (_waiters == default)
|
|
{
|
|
return;
|
|
}
|
|
|
|
waiters = _waiters;
|
|
_waiters = default;
|
|
}
|
|
|
|
while (waiters.Count > 0)
|
|
{
|
|
waiters.Dequeue().TrySetResult(new LockOrValue(value));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Release the lock and allow next waiter acquire it
|
|
/// </summary>
|
|
private void Reset(in long lockIndex)
|
|
{
|
|
UnlockOrGetNextWaiter(lockIndex, out var nextWaiter);
|
|
while (nextWaiter != default && !nextWaiter.TrySetResult(new LockOrValue(this, unchecked(lockIndex + 1))))
|
|
{
|
|
UnlockOrGetNextWaiter(lockIndex, out nextWaiter);
|
|
}
|
|
}
|
|
|
|
private void UnlockOrGetNextWaiter(in long lockIndex, out TaskCompletionSource<LockOrValue>? nextWaiter)
|
|
{
|
|
lock (_syncObj)
|
|
{
|
|
nextWaiter = default;
|
|
// If lock isn't acquired, just return
|
|
if (!_isLocked || lockIndex != _index)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_index = unchecked(lockIndex + 1);
|
|
|
|
// If lock was acquired, but there are no waiters, unlock and return
|
|
if (_waiters == default)
|
|
{
|
|
_isLocked = false;
|
|
return;
|
|
}
|
|
|
|
// Find the next waiter
|
|
while (_waiters.Count > 0)
|
|
{
|
|
nextWaiter = _waiters.Dequeue();
|
|
if (!nextWaiter.Task.IsCompleted)
|
|
{
|
|
// Return the waiter only if it wasn't canceled already
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If no next waiter has been found, unlock and return
|
|
_isLocked = false;
|
|
}
|
|
}
|
|
|
|
public readonly struct LockOrValue : IDisposable
|
|
{
|
|
private readonly AsyncLockWithValue<T>? _owner;
|
|
private readonly T? _value;
|
|
private readonly long _index;
|
|
|
|
/// <summary>
|
|
/// Returns true if lock contains the cached value. Otherwise false.
|
|
/// </summary>
|
|
public bool HasValue => _owner == default;
|
|
|
|
/// <summary>
|
|
/// Returns cached value if it was set when lock has been created. Throws exception otherwise.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">Value isn't set.</exception>
|
|
public T Value => HasValue ? _value! : throw new InvalidOperationException("Value isn't set");
|
|
|
|
public LockOrValue(T value)
|
|
{
|
|
_owner = default;
|
|
_value = value;
|
|
_index = 0;
|
|
}
|
|
|
|
public LockOrValue(AsyncLockWithValue<T> owner, long index)
|
|
{
|
|
_owner = owner;
|
|
_index = index;
|
|
_value = default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set value to the cache and to all the waiters.
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <exception cref="InvalidOperationException">Value is set already.</exception>
|
|
public void SetValue(T value)
|
|
{
|
|
if (_owner != null)
|
|
{
|
|
_owner.SetValue(value, _index);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException("Value for the lock is set already");
|
|
}
|
|
}
|
|
|
|
public void Dispose() => _owner?.Reset(_index);
|
|
}
|
|
}
|
|
}
|