Version: 0.13.0.4 beta release-1024 (Linux / official strangeloopgames/eco-game-server image)
Summary:
Every periodic worker created via PeriodicWorkerFactory.CreateWithInterval fires roughly 100× too frequently on Linux. The same code is correct on Windows. The cause is a unit mismatch in IntervalActionWorker between TimeSpan ticks and Stopwatch ticks that only cancels out when Stopwatch.Frequency == TimeSpan.TicksPerSecond — true on Windows (10 MHz), false on .NET/Unix (1 GHz).
Replication:
- Run a dedicated server on Linux (the official Docker image).
- Start any periodic worker, e.g.
PeriodicWorkerFactory.CreateWithInterval(TimeSpan.FromHours(1), doWork).
- Observe
doWork runs every ~36 seconds instead of hourly.
- Run the identical code on Windows — it runs hourly as intended.
3600s / 36s = 100, which equals Stopwatch.Frequency (Linux 1e9) / TimeSpan.TicksPerSecond (1e7).
Root cause:
IntervalActionWorker.Interval's setter stores TimeSpan ticks:
https://github.com/StrangeLoopGames/Eco/blob/v0.13.0.4-beta/Server/Eco.Core/Utils/Threading/IntervalActionWorker.cs#L20
set => this.intervalTicks = value.Ticks;
…but intervalTicks is later consumed as Stopwatch ticks via MillisecondsFromTicks, which divides by Stopwatch.Frequency:
https://github.com/StrangeLoopGames/Eco/blob/v0.13.0.4-beta/Server/Eco.Core/Utils/Threading/IntervalActionWorker.cs#L29
var delayMs = (int)StopwatchUtils.MillisecondsFromTicks(this.intervalTicks - (Stopwatch.GetTimestamp() - start));
https://github.com/StrangeLoopGames/Eco/blob/v0.13.0.4-beta/Server/Eco.Shared/Utils/StopwatchExtensions.cs#L27
https://github.com/StrangeLoopGames/Eco/blob/v0.13.0.4-beta/Server/Eco.Shared/Utils/StopwatchExtensions.cs#L42
private static readonly long Frequency = Stopwatch.Frequency; // ticks/sec, platform-dependent
public static double MillisecondsFromTicks(long ticks) => ticks / MillisecondFrequency; // MillisecondFrequency = Frequency / 1000
So the interval is divided by Stopwatch.Frequency but was stored as TimeSpan.Ticks (= seconds * 10,000,000). These agree only when Stopwatch.Frequency == 10,000,000 (Windows). On Linux Stopwatch.Frequency == 1,000,000,000, so the effective interval is 100× too short.
Desired outcome:
The setter should convert the TimeSpan to Stopwatch ticks — the codebase already provides StopwatchUtils.TicksFromTimeSpan for exactly this:
https://github.com/StrangeLoopGames/Eco/blob/v0.13.0.4-beta/Server/Eco.Shared/Utils/StopwatchExtensions.cs#L40
// IntervalActionWorker.cs L20 — fix:
set => this.intervalTicks = StopwatchUtils.TicksFromTimeSpan(value);
The Interval getter then round-trips correctly via SecondsFromTicks, and interval timing is correct on both platforms.
@Kiro (232040014761033738) in Discord.
Kiro in SLG.
Version:
0.13.0.4 beta release-1024(Linux / officialstrangeloopgames/eco-game-serverimage)Summary:
Every periodic worker created via
PeriodicWorkerFactory.CreateWithIntervalfires roughly 100× too frequently on Linux. The same code is correct on Windows. The cause is a unit mismatch inIntervalActionWorkerbetweenTimeSpanticks andStopwatchticks that only cancels out whenStopwatch.Frequency == TimeSpan.TicksPerSecond— true on Windows (10 MHz), false on .NET/Unix (1 GHz).Replication:
PeriodicWorkerFactory.CreateWithInterval(TimeSpan.FromHours(1), doWork).doWorkruns every ~36 seconds instead of hourly.3600s / 36s = 100, which equalsStopwatch.Frequency (Linux 1e9) / TimeSpan.TicksPerSecond (1e7).Root cause:
IntervalActionWorker.Interval's setter storesTimeSpanticks:https://github.com/StrangeLoopGames/Eco/blob/v0.13.0.4-beta/Server/Eco.Core/Utils/Threading/IntervalActionWorker.cs#L20
…but
intervalTicksis later consumed asStopwatchticks viaMillisecondsFromTicks, which divides byStopwatch.Frequency:https://github.com/StrangeLoopGames/Eco/blob/v0.13.0.4-beta/Server/Eco.Core/Utils/Threading/IntervalActionWorker.cs#L29
https://github.com/StrangeLoopGames/Eco/blob/v0.13.0.4-beta/Server/Eco.Shared/Utils/StopwatchExtensions.cs#L27
https://github.com/StrangeLoopGames/Eco/blob/v0.13.0.4-beta/Server/Eco.Shared/Utils/StopwatchExtensions.cs#L42
So the interval is divided by
Stopwatch.Frequencybut was stored asTimeSpan.Ticks(= seconds * 10,000,000). These agree only whenStopwatch.Frequency == 10,000,000(Windows). On LinuxStopwatch.Frequency == 1,000,000,000, so the effective interval is 100× too short.Desired outcome:
The setter should convert the
TimeSpantoStopwatchticks — the codebase already providesStopwatchUtils.TicksFromTimeSpanfor exactly this:https://github.com/StrangeLoopGames/Eco/blob/v0.13.0.4-beta/Server/Eco.Shared/Utils/StopwatchExtensions.cs#L40
The
Intervalgetter then round-trips correctly viaSecondsFromTicks, and interval timing is correct on both platforms.@Kiro(232040014761033738) in Discord.Kiroin SLG.