Skip to content
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
8 changes: 5 additions & 3 deletions src/c#/GeneralUpdate.Bowl/Strategies/LinuxBowlStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,10 @@ private static string DetectDistro()

private static void EnsureDirectory(string path)
{
if (Directory.Exists(path))
Directory.Delete(path, recursive: true);
Directory.CreateDirectory(path);
// Create the fail directory if it does not yet exist.
// Do NOT delete an existing directory — that would destroy
// crash diagnostics from previous surveillance sessions.
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
}
}
14 changes: 9 additions & 5 deletions src/c#/GeneralUpdate.Bowl/Strategies/ProcessRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static async Task<ProcessExitResult> RunAsync(
var outputLines = new List<string>();

using var process = new Process { StartInfo = startInfo };
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);

process.OutputDataReceived += (_, e) =>
Expand Down Expand Up @@ -61,14 +62,17 @@ public static async Task<ProcessExitResult> RunAsync(
process.BeginOutputReadLine();
process.BeginErrorReadLine();

// Wait for exit or timeout/cancellation
var completedTask = await Task.WhenAny(
tcs.Task,
Task.Delay(timeoutMs, ct)
);
// Wait for exit or timeout/cancellation.
// Cancel the delay when the process exits first to avoid a timer leak.
var delayTask = Task.Delay(timeoutMs, timeoutCts.Token);
var completedTask = await Task.WhenAny(tcs.Task, delayTask);

// Cancel the opposing task so timers/resources are reclaimed promptly.
timeoutCts.Cancel();

if (completedTask == tcs.Task)
{
try { await delayTask; } catch (OperationCanceledException) { /* cancelled — expected */ }
var exitCode = await tcs.Task;
GeneralTracer.Info($"ProcessRunner.RunAsync: process exited, ExitCode={exitCode}");
// Snapshot output under lock to avoid race with in-flight handlers
Expand Down
8 changes: 5 additions & 3 deletions src/c#/GeneralUpdate.Bowl/Strategies/WindowsBowlStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ public Task PostProcessAsync(in BowlContext context,

private static void EnsureDirectory(string path)
{
if (Directory.Exists(path))
StorageHelper.DeleteDirectory(path);
Directory.CreateDirectory(path);
// Create the fail directory if it does not yet exist.
// Do NOT delete an existing directory — that would destroy
// crash diagnostics from previous surveillance sessions.
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
}
}
8 changes: 7 additions & 1 deletion src/c#/GeneralUpdate.Core/Event/EventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,15 @@ public void Dispatch<TEventArgs>(object sender, TEventArgs eventArgs) where TEve
var type = typeof(Action<object, TEventArgs>);
if (_dicDelegates.TryGetValue(type, out var existingDelegate))
{
// Snapshot the delegate invocation list to avoid a race with
// concurrent AddListener / RemoveListener mutating the delegate
// while we enumerate it. The ConcurrentDictionary protects the
// dictionary structure but not the Delegate value itself.
var invocationList = existingDelegate.GetInvocationList();

// Invoke each handler individually so one handler's exception
// doesn't prevent others from being called.
foreach (var handler in existingDelegate.GetInvocationList())
foreach (var handler in invocationList)
{
try
{
Expand Down
3 changes: 1 addition & 2 deletions src/c#/GeneralUpdate.Core/FileSystem/FileNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,7 @@ public FileNode SearchParent(long id)
public override bool Equals(object obj)
{
if (obj == null) return false;
var tempNode = obj as FileNode;
if (tempNode == null) throw new ArgumentException(nameof(tempNode));
if (obj is not FileNode tempNode) return false;
return string.Equals(Hash, tempNode.Hash, StringComparison.OrdinalIgnoreCase) &&
string.Equals(Name, tempNode.Name, StringComparison.OrdinalIgnoreCase);
}
Expand Down
8 changes: 4 additions & 4 deletions src/c#/GeneralUpdate.Core/FileSystem/FileTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,23 +209,23 @@ public void Compare(FileNode node, FileNode node0, ref List<FileNode> nodes)
if (node != null && node.Left != null)
{
if (!node.Equals(node0) && node0 != null) nodes.Add(node0);
Compare(node.Left, node0.Left, ref nodes);
Compare(node.Left, node0?.Left, ref nodes);
}
else if (node0 != null && node0.Left != null)
{
nodes.Add(node0);
Compare(node.Left, node0.Left, ref nodes);
Compare(node?.Left, node0.Left, ref nodes);
}

if (node != null && node.Right != null)
{
if (!node.Equals(node0) && node0 != null) nodes.Add(node0);
Compare(node.Right, node0 == null ? null : node0.Right, ref nodes);
Compare(node.Right, node0?.Right, ref nodes);
}
else if (node0 != null && node0.Right != null)
{
nodes.Add(node0);
Compare(node == null ? null : node.Right, node0.Right, ref nodes);
Compare(node?.Right, node0.Right, ref nodes);
}
else if (node0 != null)
{
Expand Down
4 changes: 2 additions & 2 deletions src/c#/GeneralUpdate.Core/Pipeline/DiffPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public async Task CleanAsync(
}

int completed = 0;
var semaphore = new SemaphoreSlim(_options.MaxDegreeOfParallelism);
using var semaphore = new SemaphoreSlim(_options.MaxDegreeOfParallelism);

var tasks = differentFiles.Select(file => Task.Run(async () =>
{
Expand Down Expand Up @@ -342,7 +342,7 @@ public async Task DirtyAsync(
}

int completed = 0;
var semaphore = new SemaphoreSlim(_options.MaxDegreeOfParallelism);
using var semaphore = new SemaphoreSlim(_options.MaxDegreeOfParallelism);

var matchedPairs = new List<(FileInfo OldFile, FileInfo PatchFile)>();
foreach (var oldFile in oldFiles)
Expand Down
5 changes: 4 additions & 1 deletion src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,10 @@ private bool CheckFail(string version)
var fail = Environments.GetEnvironmentVariable("UpgradeFail");
if (string.IsNullOrEmpty(fail) || string.IsNullOrEmpty(version))
return false;
return new Version(fail) >= new Version(version);
if (!Version.TryParse(fail, out var failVersion) ||
!Version.TryParse(version, out var versionParsed))
return false;
return failVersion >= versionParsed;
}

/// <summary>
Expand Down
19 changes: 15 additions & 4 deletions src/c#/GeneralUpdate.Core/Strategy/OssStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
Expand Down Expand Up @@ -258,15 +259,25 @@ private async Task ExecuteClientAsync()
: installPath;
var upgradeAppName = !string.IsNullOrWhiteSpace(_configInfo.UpdateAppName)
? _configInfo.UpdateAppName
: "GeneralUpdate.Upgrade.exe";
: RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "GeneralUpdate.Upgrade.exe"
: "GeneralUpdate.Upgrade";
var appPath = Path.Combine(upgradeDir, upgradeAppName);
GeneralTracer.Info($"[OssClient] Resolved upgrade path: {appPath}");

// List exe files in the directory to help diagnose missing file issues
// List executables in the directory to help diagnose missing file issues
try
{
var dirFiles = Directory.GetFiles(upgradeDir, "*.exe").Select(f => Path.GetFileName(f));
GeneralTracer.Info($"[OssClient] *.exe files in {upgradeDir}: [{string.Join(", ", dirFiles)}]");
var searchPattern = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "*.exe" : "*";
const int maxDisplay = 20;
var dirFiles = Directory.EnumerateFiles(upgradeDir, searchPattern)
.Take(maxDisplay + 1)
.Select(f => Path.GetFileName(f))
.ToList();
var summary = dirFiles.Count > maxDisplay
? $"[{string.Join(", ", dirFiles.Take(maxDisplay))}, ... and {dirFiles.Count - maxDisplay} more]"
: $"[{string.Join(", ", dirFiles)}]";
GeneralTracer.Info($"[OssClient] Files in {upgradeDir}: {summary}");
}
catch (Exception ex)
{
Expand Down
39 changes: 21 additions & 18 deletions src/c#/GeneralUpdate.Core/Tracer/GeneralTracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,30 @@ static GeneralTracer()

private static void InitializeFileListener()
{
//Ensure that log files are rotated on a daily basis
var today = DateTime.Now.ToString("yyyy-MM-dd");
if (today == _currentLogDate && _fileListener != null)
return;

if (_fileListener != null)
lock (_lockObj)
{
Trace.Listeners.Remove(_fileListener);
_fileListener.Flush();
_fileListener.Close();
_fileListener.Dispose();
}
// Ensure that log files are rotated on a daily basis
var today = DateTime.Now.ToString("yyyy-MM-dd");
if (today == _currentLogDate && _fileListener != null)
return;

var logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
Directory.CreateDirectory(logDir);
if (_fileListener != null)
{
Trace.Listeners.Remove(_fileListener);
_fileListener.Flush();
_fileListener.Close();
_fileListener.Dispose();
}

var logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
Directory.CreateDirectory(logDir);

var logFileName = Path.Combine(logDir, $"generalupdate-trace {today}.log");
_fileListener = new TextTraceListener(logFileName);

Trace.Listeners.Add(_fileListener);
_currentLogDate = today;
var logFileName = Path.Combine(logDir, $"generalupdate-trace {today}.log");
_fileListener = new TextTraceListener(logFileName);

Trace.Listeners.Add(_fileListener);
_currentLogDate = today;
}
}

public static void Debug(string message) => WriteTraceMessage(TraceLevel.Verbose, message);
Expand Down
18 changes: 13 additions & 5 deletions src/c#/GeneralUpdate.Differential/Differ/BsdiffDiffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,9 @@ await Task.Run(() =>
byte[] formatByte = ReadExactly(patchStream, 1);
byte candidate = formatByte[0];

if (candidate == BZip2FormatVersion || candidate == DeflateFormatVersion)
if (candidate == BZip2FormatVersion || candidate == DeflateFormatVersion
|| candidate == BrotliFormatVersion
)
Comment on lines +311 to +313
{
formatVersion = candidate;
actualHeaderSize = ExtendedHeaderSize;
Expand All @@ -333,6 +335,9 @@ await Task.Run(() =>
{
BZip2FormatVersion => new BZip2CompressionProvider(),
DeflateFormatVersion => new DeflateCompressionProvider(),
#if NET6_0_OR_GREATER
BrotliFormatVersion => new BrotliCompressionProvider(),
#endif
_ => throw new InvalidOperationException(
$"Unsupported patch compression format version: 0x{formatVersion:X2}")
};
Expand Down Expand Up @@ -437,6 +442,7 @@ await Task.Run(() =>

private const byte BZip2FormatVersion = 0x00;
private const byte DeflateFormatVersion = 0x01;
private const byte BrotliFormatVersion = 0x02;

private static FileStream OpenPatchStream(string patchPath)
{
Expand Down Expand Up @@ -477,17 +483,19 @@ private static int Search(int[] I, byte[] oldData, byte[] newData, int newOffset
{
if (end - start < 2)
{
int x = MatchLength(oldData, I[start], newData, newOffset);
int y = MatchLength(oldData, I[end], newData, newOffset);
// Guard against sentinel -1 values that Split() writes for singleton buckets.
// MatchLength with a negative index would throw IndexOutOfRangeException.
int x = I[start] >= 0 ? MatchLength(oldData, I[start], newData, newOffset) : 0;
int y = I[end] >= 0 ? MatchLength(oldData, I[end], newData, newOffset) : 0;

if (x > y)
{
pos = I[start];
pos = I[start] >= 0 ? I[start] : 0;
return x;
}
else
{
pos = I[end];
pos = I[end] >= 0 ? I[end] : 0;
return y;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,16 +436,20 @@ private static void ValidatePaths(string oldPath, string newPath, string patchPa
private static byte[] ReadFileWithBudget(string path, int maxBytes)
{
byte[] buffer = new byte[maxBytes];
int totalRead = 0;
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
int totalRead = 0;
while (totalRead < maxBytes)
{
int read = fs.Read(buffer, totalRead, maxBytes - totalRead);
if (read == 0) break;
totalRead += read;
}
}
// Trim to actual bytes read so callers using .Length see the real data,
// not zero-initialized tail bytes that would corrupt patch computation.
if (totalRead < maxBytes)
Array.Resize(ref buffer, totalRead);
return buffer;
}

Expand Down
Loading
Loading