fix(watcher): fix TickCount wrap and resume retry on API failure
- Use 32-bit Environment.TickCount with unchecked uint arithmetic so GetIdleTime stays correct after the ~49.7 day uint wrap boundary - Only clear _pausedTaskId after successful resume, not before the API call - Add retry path: if resume failed, retry on next poll cycle while user is still active Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ internal static partial class NativeMethods
|
|||||||
var info = new LASTINPUTINFO { cbSize = (uint)Marshal.SizeOf<LASTINPUTINFO>() };
|
var info = new LASTINPUTINFO { cbSize = (uint)Marshal.SizeOf<LASTINPUTINFO>() };
|
||||||
if (!GetLastInputInfo(ref info))
|
if (!GetLastInputInfo(ref info))
|
||||||
return TimeSpan.Zero;
|
return TimeSpan.Zero;
|
||||||
return TimeSpan.FromMilliseconds(Environment.TickCount64 - info.dwTime);
|
// Use 32-bit TickCount so both values wrap at the same boundary as dwTime (uint)
|
||||||
|
return TimeSpan.FromMilliseconds(unchecked((uint)Environment.TickCount - info.dwTime));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,12 @@ public class Worker(
|
|||||||
|
|
||||||
// Idle detection
|
// Idle detection
|
||||||
var idleTime = NativeMethods.GetIdleTime();
|
var idleTime = NativeMethods.GetIdleTime();
|
||||||
if (!_isIdle && idleTime.TotalMilliseconds >= config.IdleTimeoutMs)
|
if (!_isIdle && _pausedTaskId is not null && idleTime.TotalMilliseconds < config.IdleTimeoutMs)
|
||||||
|
{
|
||||||
|
// Retry resume from a previous failed attempt
|
||||||
|
await ResumeIdlePausedTaskAsync(ct: stoppingToken);
|
||||||
|
}
|
||||||
|
else if (!_isIdle && idleTime.TotalMilliseconds >= config.IdleTimeoutMs)
|
||||||
{
|
{
|
||||||
_isIdle = true;
|
_isIdle = true;
|
||||||
logger.LogInformation("User idle for {IdleTime}, pausing active task", idleTime);
|
logger.LogInformation("User idle for {IdleTime}, pausing active task", idleTime);
|
||||||
@@ -167,7 +172,6 @@ public class Worker(
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var taskId = _pausedTaskId.Value;
|
var taskId = _pausedTaskId.Value;
|
||||||
_pausedTaskId = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -180,6 +184,7 @@ public class Worker(
|
|||||||
if (response?.Data is null || response.Data.Status != "Paused")
|
if (response?.Data is null || response.Data.Status != "Paused")
|
||||||
{
|
{
|
||||||
logger.LogDebug("Task {TaskId} is no longer paused, skipping auto-resume", taskId);
|
logger.LogDebug("Task {TaskId} is no longer paused, skipping auto-resume", taskId);
|
||||||
|
_pausedTaskId = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,13 +194,18 @@ public class Worker(
|
|||||||
new { note = "Auto-resumed: user returned" }, ct);
|
new { note = "Auto-resumed: user returned" }, ct);
|
||||||
|
|
||||||
if (resumeResponse.IsSuccessStatusCode)
|
if (resumeResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
logger.LogInformation("Auto-resumed task {TaskId}", taskId);
|
logger.LogInformation("Auto-resumed task {TaskId}", taskId);
|
||||||
|
_pausedTaskId = null;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
logger.LogWarning("Failed to resume task {TaskId}: {Status}", taskId, resumeResponse.StatusCode);
|
{
|
||||||
|
logger.LogWarning("Failed to resume task {TaskId}: {Status}, will retry", taskId, resumeResponse.StatusCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogWarning(ex, "Failed to resume task {TaskId} after idle", taskId);
|
logger.LogWarning(ex, "Failed to resume task {TaskId} after idle, will retry", taskId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user