Skip to content

[Code Quality] Helper.WriteFileAtomic and Helper.WriteFileAtomicAsync are ~95% duplicated (DRY) #1060

@Christophe-Rogiers

Description

@Christophe-Rogiers

Severity: Info

File

src/Servy.Core/Helpers/Helper.cs

Lines

350-374 (WriteFileAtomicAsync) and 386-410 (WriteFileAtomic)

Issue

The two methods are byte-for-byte the same except the async one awaits a Func<Stream, Task> and the sync one calls an Action<Stream>. Both:

  1. compute Path.GetDirectoryName(path) and create the directory if missing,
  2. write to path + ".tmp" with FileMode.Create / FileShare.None,
  3. flush,
  4. File.Move(tmp, path, overwrite: true),
  5. finally-delete the tmp file with the same try/catch swallow.
public static async Task WriteFileAtomicAsync(string path, Func<Stream, Task> writeContent, CancellationToken ct = default) { ... }
public static void       WriteFileAtomic     (string path, Action<Stream> writeContent)                                  { ... }

Any future change (e.g. honoring a custom temp dir, switching to FileOptions.WriteThrough, adding tracing, fixing the async one's missing ct propagation to Directory.CreateDirectory) will need to be applied twice and will drift.

Suggested fix

Have the sync version delegate to the async one synchronously, or extract the prelude/cleanup into a shared private helper that takes the writer delegate as Func<Stream, ValueTask>:

public static void WriteFileAtomic(string path, Action<Stream> writeContent)
    => WriteFileAtomicCore(path, fs => { writeContent(fs); fs.Flush(); return default; }).GetAwaiter().GetResult();

public static Task WriteFileAtomicAsync(string path, Func<Stream, Task> writeContent, CancellationToken ct = default)
    => WriteFileAtomicCore(path, async fs => { await writeContent(fs); await fs.FlushAsync(ct); }, ct).AsTask();

private static async ValueTask WriteFileAtomicCore(string path, Func<Stream, ValueTask> writer, CancellationToken ct = default) { ... }

Side benefit: the async path will then also flow ct into the directory-creation step, and a single bug fix lands in both.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions