Skip to content

Commit 5f7d728

Browse files
committed
docs: explain the safety of accesses to the mutable global variable
1 parent 3fb9d85 commit 5f7d728

File tree

1 file changed

+20
-1
lines changed

1 file changed

+20
-1
lines changed

src/Program.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,20 @@ internal class Cli(string url, string dir) {
2525
public static partial class Program {
2626
private static readonly ILogger Logger = CreateLogger("Program");
2727

28-
// -1: not initialized yet, 0: initialized but unable to get value
28+
// -1: not initialized yet
29+
// 0: initialized, but unable to determine expected image count
30+
// >0: initialized with expected image count
31+
//
32+
// SAFETY:
33+
// - `_expectedImgCount` is effectively a `static mut`: global and mutable.
34+
// - Only one logical flow (`AreAllImagesLoaded`) ever accesses it.
35+
// - That flow is always sequentially awaited, so no parallel invocations occur.
36+
// - However, the async runtime may resume the flow on different threads.
37+
// Thus, ordinary reads/writes would risk stale or reordered values.
38+
// - Volatile operations are used to enforce safe publication:
39+
// - First write is `VolatileWrite`: publishes initialized value to other threads.
40+
// - First read in subsequent calls is `VolatileRead`: ensures visibility of the published value.
41+
// - Later reads are plain, since the value is immutable after initialization.
2942
private static int _expectedImgCount = -1;
3043

3144
private static async Task BlockRequest(IBrowserContext context) {
@@ -133,20 +146,26 @@ async Task<bool> HasReachedCount(int expectedCount) {
133146
return actualCount == expectedCount;
134147
}
135148

149+
// SAFETY: `VolatileRead` ensures the second and later calls observe
150+
// the published value from the first initialization, even if resumed on another thread.
136151
if (Thread.VolatileRead(ref _expectedImgCount) == -1) { // not cached
137152
// ReSharper disable once StringLiteralTypo
138153
var title = page.Locator("h1.focusbox-title");
139154
var text = await title.InnerTextAsync();
140155
var match = ImgCountPattern().Match(text);
141156
if (match.Success) {
142157
var count = int.Parse(match.Groups[1].Value); // impossible to be negative
158+
// SAFETY: `VolatileWrite` publishes the initialized value
159+
// so subsequent calls on other threads will see it.
143160
Thread.VolatileWrite(ref _expectedImgCount, count);
144161
return await HasReachedCount(count); // practically non-zero
145162
} else {
146163
Thread.VolatileWrite(ref _expectedImgCount, 0);
147164
return null;
148165
}
149166
} else { // cached
167+
// SAFETY: after a `VolatileRead` has established visibility,
168+
// ordinary reads are safe, since `_expectedImgCount` never mutates again.
150169
if (_expectedImgCount == 0) {
151170
return null;
152171
} else {

0 commit comments

Comments
 (0)