mirror of
https://github.com/Grace-Solutions/PSMinIO.git
synced 2026-07-26 14:58:13 +00:00
3e4a7e0810
� PERMANENT THREADING KNOWLEDGE BASE:
✅ CREATED COMPREHENSIVE DOCUMENTATION:
• docs/POWERSHELL-THREADING-RULES.md - Complete threading guide
• Enhanced ThreadSafeProgressCollector with usage examples
• Added to project structure documentation
• Saved to AI memory for future reference
� CRITICAL RULES DOCUMENTED:
• PowerShell cmdlets can ONLY call Write-* methods from main thread
• Background threads must QUEUE updates, never call Write-* directly
• ProcessQueuedUpdates() must ONLY be called from main thread
• Common mistakes and error symptoms clearly identified
� DESIGN PATTERNS PROVIDED:
• Periodic processing pattern (every 1 second)
• Completion-based processing pattern
• Manual processing points pattern
• Correct Task.WaitAll usage with timeouts
� IMPLEMENTATION CHECKLIST:
• Pre-implementation checklist for new cmdlets
• Debugging tips and error identification
• Code examples for correct and incorrect patterns
• Testing guidelines for threading compliance
� PREVENTS FUTURE REGRESSIONS:
• Clear documentation of the 'golden rule'
• Examples of common threading violations
• Patterns for all background operation types
• Reference for all future PowerShell module development
This ensures we never repeat the same threading mistakes!
171 lines
5.9 KiB
C#
171 lines
5.9 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Management.Automation;
|
|
using System.Threading;
|
|
|
|
namespace PSMinIO.Utils
|
|
{
|
|
/// <summary>
|
|
/// Thread-safe progress data collector that accumulates progress updates from background threads
|
|
/// and allows the main thread to safely report them to PowerShell
|
|
///
|
|
/// CRITICAL THREADING RULE:
|
|
/// PowerShell cmdlets can ONLY call Write-Progress, Write-Verbose, Write-Object, Write-Error
|
|
/// from the main cmdlet thread - NEVER from background threads!
|
|
///
|
|
/// USAGE PATTERN:
|
|
/// - Background threads: Call QueueProgressUpdate(), QueueVerboseMessage() (thread-safe)
|
|
/// - Main thread ONLY: Call ProcessQueuedUpdates() to display queued updates
|
|
///
|
|
/// EXAMPLE:
|
|
/// Task.Run(() => {
|
|
/// collector.QueueProgressUpdate(1, "Processing", "Status", 50); // ✅ Safe
|
|
/// // WriteProgress(...); // ❌ THREADING ERROR!
|
|
/// });
|
|
/// collector.ProcessQueuedUpdates(); // ✅ Only from main thread
|
|
/// </summary>
|
|
public class ThreadSafeProgressCollector
|
|
{
|
|
private readonly PSCmdlet _cmdlet;
|
|
private readonly ConcurrentQueue<ProgressUpdate> _progressQueue = new();
|
|
private readonly ConcurrentQueue<VerboseMessage> _verboseQueue = new();
|
|
private readonly object _lockObject = new();
|
|
private volatile bool _isCompleted = false;
|
|
|
|
public ThreadSafeProgressCollector(PSCmdlet cmdlet)
|
|
{
|
|
_cmdlet = cmdlet ?? throw new ArgumentNullException(nameof(cmdlet));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Queues a progress update from a background thread
|
|
/// </summary>
|
|
public void QueueProgressUpdate(int activityId, string activity, string statusDescription, int percentComplete, int parentActivityId = -1)
|
|
{
|
|
if (_isCompleted) return;
|
|
|
|
_progressQueue.Enqueue(new ProgressUpdate
|
|
{
|
|
ActivityId = activityId,
|
|
Activity = activity,
|
|
StatusDescription = statusDescription,
|
|
PercentComplete = percentComplete,
|
|
ParentActivityId = parentActivityId,
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Queues a progress completion from a background thread
|
|
/// </summary>
|
|
public void QueueProgressCompletion(int activityId, string activity, int parentActivityId = -1)
|
|
{
|
|
if (_isCompleted) return;
|
|
|
|
_progressQueue.Enqueue(new ProgressUpdate
|
|
{
|
|
ActivityId = activityId,
|
|
Activity = activity,
|
|
StatusDescription = "Completed",
|
|
PercentComplete = 100,
|
|
ParentActivityId = parentActivityId,
|
|
IsCompleted = true,
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Queues a verbose message from a background thread
|
|
/// </summary>
|
|
public void QueueVerboseMessage(string message, params object[] args)
|
|
{
|
|
if (_isCompleted) return;
|
|
|
|
_verboseQueue.Enqueue(new VerboseMessage
|
|
{
|
|
Message = args.Length > 0 ? string.Format(message, args) : message,
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes all queued updates from the main thread (safe to call PowerShell methods)
|
|
/// </summary>
|
|
public void ProcessQueuedUpdates()
|
|
{
|
|
// Process verbose messages first
|
|
while (_verboseQueue.TryDequeue(out var verboseMessage))
|
|
{
|
|
MinIOLogger.WriteVerbose(_cmdlet, verboseMessage.Message);
|
|
}
|
|
|
|
// Process progress updates
|
|
while (_progressQueue.TryDequeue(out var progressUpdate))
|
|
{
|
|
var progressRecord = new ProgressRecord(
|
|
progressUpdate.ActivityId,
|
|
progressUpdate.Activity,
|
|
progressUpdate.StatusDescription)
|
|
{
|
|
PercentComplete = progressUpdate.PercentComplete
|
|
};
|
|
|
|
if (progressUpdate.ParentActivityId >= 0)
|
|
{
|
|
progressRecord.ParentActivityId = progressUpdate.ParentActivityId;
|
|
}
|
|
|
|
if (progressUpdate.IsCompleted)
|
|
{
|
|
progressRecord.RecordType = ProgressRecordType.Completed;
|
|
}
|
|
|
|
_cmdlet.WriteProgress(progressRecord);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks the collector as completed (no more updates will be accepted)
|
|
/// </summary>
|
|
public void Complete()
|
|
{
|
|
_isCompleted = true;
|
|
// Process any remaining updates
|
|
ProcessQueuedUpdates();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the number of pending progress updates
|
|
/// </summary>
|
|
public int PendingProgressUpdates => _progressQueue.Count;
|
|
|
|
/// <summary>
|
|
/// Gets the number of pending verbose messages
|
|
/// </summary>
|
|
public int PendingVerboseMessages => _verboseQueue.Count;
|
|
|
|
/// <summary>
|
|
/// Progress update data structure
|
|
/// </summary>
|
|
private class ProgressUpdate
|
|
{
|
|
public int ActivityId { get; set; }
|
|
public string Activity { get; set; } = string.Empty;
|
|
public string StatusDescription { get; set; } = string.Empty;
|
|
public int PercentComplete { get; set; }
|
|
public int ParentActivityId { get; set; } = -1;
|
|
public bool IsCompleted { get; set; }
|
|
public DateTime Timestamp { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verbose message data structure
|
|
/// </summary>
|
|
private class VerboseMessage
|
|
{
|
|
public string Message { get; set; } = string.Empty;
|
|
public DateTime Timestamp { get; set; }
|
|
}
|
|
}
|
|
}
|