Skip to content

Commit

Permalink
Improved file writer avoid race condition.
Browse files Browse the repository at this point in the history
  • Loading branch information
kekyo committed Oct 21, 2023
1 parent 366c761 commit e88fad9
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 69 deletions.
79 changes: 79 additions & 0 deletions RelaxVersioner.Core/Processor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,83 @@ public async Task<Result> RunAsync(
repository?.Dispose();
}
}

public static void WriteSafeTransacted(
string path, Action<Stream> action)
{
var directoryPath = Utilities.GetDirectoryPath(path);
if (!Directory.Exists(directoryPath))
{
try
{
Directory.CreateDirectory(directoryPath);
}
catch
{
}
}

var temporaryPath = Path.Combine(
directoryPath, Path.GetTempFileName());
try
{
using (var stream = new FileStream(
temporaryPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
action(stream);
stream.Flush();
}

var originExists = false;
var originPath = Path.Combine(
directoryPath, Path.GetTempFileName());
if (File.Exists(path))
{
try
{
File.Move(path, originPath);
originExists = true;
}
catch
{
}
}

try
{
File.Move(temporaryPath, path);
}
catch
{
if (originExists)
{
try
{
File.Move(originPath, path);
originExists = false;
}
catch
{
}
}
throw;
}

if (originExists)
{
originExists = false;
try
{
File.Delete(originPath);
}
catch
{
}
}
}
catch
{
File.Delete(temporaryPath);
}
}
}
9 changes: 9 additions & 0 deletions RelaxVersioner.Core/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ internal static class Utilities
private static readonly char[] directorySeparatorChar_ =
{ Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };

public static string GetDirectoryPath(string path) =>
Path.GetDirectoryName(path) switch
{
// Not accurate in Windows, but a compromise...
null => Path.DirectorySeparatorChar.ToString(),
"" => string.Empty,
var dp => dp,
};

public static Dictionary<string, WriterBase> GetWriters()
{
return typeof(Utilities).Assembly.
Expand Down
122 changes: 62 additions & 60 deletions RelaxVersioner.Core/Writers/WriterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void Write(
{
Debug.Assert(string.IsNullOrWhiteSpace(context.OutputPath) == false);

var targetFolder = Path.GetDirectoryName(context.OutputPath);
var targetFolder = Utilities.GetDirectoryPath(context.OutputPath);
if (!string.IsNullOrWhiteSpace(targetFolder) && !Directory.Exists(targetFolder))
{
try
Expand All @@ -46,86 +46,88 @@ public void Write(
}
}

using (var ftw = File.CreateText(context.OutputPath))
{
var tw = new SourceCodeWriter(ftw, context);
Processor.WriteSafeTransacted(
context.OutputPath,
stream =>
{
var tw = new SourceCodeWriter(new StreamWriter(stream), context);
this.WriteComment(tw,
$"This is auto-generated version information attributes by RelaxVersioner [{ThisAssembly.AssemblyVersion}], Do not edit.");
this.WriteComment(tw,
$"Generated date: {generated:F}");
tw.WriteLine();
this.WriteComment(tw,
$"This is auto-generated version information attributes by RelaxVersioner [{ThisAssembly.AssemblyVersion}], Do not edit.");
this.WriteComment(tw,
$"Generated date: {generated:F}");
tw.WriteLine();
this.WriteBeforeBody(tw);
this.WriteBeforeBody(tw);
foreach (var importNamespace in importSet)
{
this.WriteImport(tw, importNamespace);
}
tw.WriteLine();

foreach (var rule in ruleSet)
{
var formattedValue = Named.Format(
CultureInfo.InvariantCulture,
rule.Format,
keyValues,
key => string.Empty);
if (!string.IsNullOrWhiteSpace(rule.Key))
foreach (var importNamespace in importSet)
{
this.WriteAttributeWithArguments(tw, rule.Name, rule.Key, formattedValue);
this.WriteImport(tw, importNamespace);
}
else
tw.WriteLine();
foreach (var rule in ruleSet)
{
this.WriteAttributeWithArguments(tw, rule.Name, formattedValue);
var formattedValue = Named.Format(
CultureInfo.InvariantCulture,
rule.Format,
keyValues,
key => string.Empty);
if (!string.IsNullOrWhiteSpace(rule.Key))
{
this.WriteAttributeWithArguments(tw, rule.Name, rule.Key, formattedValue);
}
else
{
this.WriteAttributeWithArguments(tw, rule.Name, formattedValue);
}
}
}
tw.WriteLine();

if (context.GenerateStatic)
{
this.WriteBeforeLiteralBody(tw);
tw.WriteLine();
foreach (var g in ruleSet.GroupBy(rule => rule.Name))
if (context.GenerateStatic)
{
var rules = g.ToArray();
this.WriteBeforeLiteralBody(tw);
if (rules.Length >= 2)
foreach (var g in ruleSet.GroupBy(rule => rule.Name))
{
this.WriteBeforeNestedLiteralBody(tw, rules[0].Name);
}
var rules = g.ToArray();
foreach (var rule in rules)
{
var formattedValue = Named.Format(
CultureInfo.InvariantCulture,
rule.Format,
keyValues,
key => string.Empty);
if (!string.IsNullOrWhiteSpace(rule.Key))
if (rules.Length >= 2)
{
this.WriteLiteralWithArgument(tw, rule.Key, formattedValue);
this.WriteBeforeNestedLiteralBody(tw, rules[0].Name);
}
else
foreach (var rule in rules)
{
this.WriteLiteralWithArgument(tw, rule.Name, formattedValue);
var formattedValue = Named.Format(
CultureInfo.InvariantCulture,
rule.Format,
keyValues,
key => string.Empty);
if (!string.IsNullOrWhiteSpace(rule.Key))
{
this.WriteLiteralWithArgument(tw, rule.Key, formattedValue);
}
else
{
this.WriteLiteralWithArgument(tw, rule.Name, formattedValue);
}
}
}
if (rules.Length >= 2)
{
this.WriteAfterNestedLiteralBody(tw);
if (rules.Length >= 2)
{
this.WriteAfterNestedLiteralBody(tw);
}
}
}
this.WriteAfterLiteralBody(tw);
tw.WriteLine();
}
this.WriteAfterLiteralBody(tw);
tw.WriteLine();
}
this.WriteAfterBody(tw);
this.WriteAfterBody(tw);
tw.Flush();
}
tw.Flush();
});
}

protected static bool IsRequiredSelfHostingMetadataAttribute(ProcessorContext context) =>
Expand Down
8 changes: 4 additions & 4 deletions RelaxVersioner.Tasks/DumpPropertiesTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public string? OutputPath

public override bool Execute()
{
if (string.IsNullOrWhiteSpace(OutputPath))
if (string.IsNullOrWhiteSpace(this.OutputPath))
{
this.Log.LogError("RelaxVersioner.DumpPropertiesTask: Required output path.");
return false;
Expand All @@ -42,7 +42,7 @@ public override bool Execute()

if (project != null)
{
var basePath = Path.GetDirectoryName(OutputPath);
var basePath = Utilities.GetDirectoryPath(this.OutputPath!);
if (!Directory.Exists(basePath))
{
try
Expand All @@ -54,7 +54,7 @@ public override bool Execute()
}
}

using (var fs = File.Create(OutputPath))
using (var fs = File.Create(this.OutputPath))
{
var root = new XElement("Properties",
project.Properties.
Expand All @@ -64,7 +64,7 @@ public override bool Execute()
fs.Flush();
}

this.Log.LogMessage($"RelaxVersioner.DumpPropertiesTask: Dump properties from build engine, Path={OutputPath}");
this.Log.LogMessage($"RelaxVersioner.DumpPropertiesTask: Dump properties from build engine, Path={this.OutputPath}");
return true;
}
else
Expand Down
10 changes: 10 additions & 0 deletions RelaxVersioner.Tasks/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,22 @@
//
////////////////////////////////////////////////////////////////////////////////////////

using System.IO;
using System.Reflection;

namespace RelaxVersioner;

internal static class Utilities
{
public static string GetDirectoryPath(string path) =>
Path.GetDirectoryName(path) switch
{
// Not accurate in Windows, but a compromise...
null => Path.DirectorySeparatorChar.ToString(),
"" => string.Empty,
var dp => dp,
};

public static object? GetField(this object instance, string name)
{
var type = instance.GetType();
Expand Down
6 changes: 1 addition & 5 deletions RelaxVersioner/ResultWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ public static void Write(string path, object result)
type.GetFields().
Select(field => new XElement(field.Name, ToString(field.GetValue(result)))));

using (var fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
document.Save(fs);
fs.Flush();
}
Processor.WriteSafeTransacted(path, document.Save);
}
}

0 comments on commit e88fad9

Please sign in to comment.