Skip to content

Commit a6668b9

Browse files
Roveyclaude
andcommitted
fix: second review pass on the tray app and install
- a failed optimize start no longer switches the watchdog off; after a run, watch only a result that was saved - tray and window apply/revert refuse while gao --optimize runs - install checks both sources first and moves aside only a copy in use - a clean exit or logoff within the 2-minute grace clears the strike - emergency resets use the process-lifetime GpuControl, not the worker's - gao --reset notifies the tray before it writes, closing a race with a watchdog tick Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent f059e9f commit a6668b9

7 files changed

Lines changed: 50 additions & 21 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ That builds two programs in `.\build\Release\`: `GpuAutoOptimizer.exe`, the wind
4444

4545
Start `GpuAutoOptimizer.exe`. The window shows the GPU, live telemetry, the saved tune and what is applied right now; pick a preset and press **Optimize** (it offers to restart as administrator first, because tuning writes clocks and power limits). The run screen shows temperature, power and every probe as it happens; **Abort** restores stock. The results screen compares before and after and can turn on **apply at every logon**.
4646

47-
With apply-at-logon on, Windows starts the app in the tray at every logon. It applies the saved tune and then keeps it applied: every 30 seconds it reads back what the driver reports. If the tune was reset (a driver reset or TDR), it applies it again; if that happens four times within an hour it stops and tells you the tune is probably not stable; if another program such as MSI Afterburner changed the settings, it leaves them alone and tells you; after a driver update it does not apply the old tune and tells you to optimize again. While it keeps a tune applied (or a run is in progress), closing the window keeps it in the tray and **Exit** in the tray menu quits; otherwise closing the window quits. **Revert to stock** in the tray menu, or `gao --reset` from a shell, puts the card at stock and the watchdog leaves it there until you apply the tune again. Only one optimize runs at a time, from the window or the command line.
47+
With apply-at-logon on, Windows starts the app in the tray at every logon. It applies the saved tune and then keeps it applied: every 30 seconds it reads back what the driver reports. If the tune was reset (a driver reset or TDR), it applies it again; if that happens four times within an hour it stops and tells you the tune is probably not stable; if another program such as MSI Afterburner changed the settings, it leaves them alone and tells you; after a driver update it does not apply the old tune and tells you to optimize again. While it keeps a tune applied (or a run is in progress), closing the window keeps it in the tray and **Exit** in the tray menu quits; otherwise closing the window quits. **Revert to stock** in the tray menu, or `gao --reset` from a shell, puts the card at stock and the watchdog leaves it there until you apply the tune again from the window or the tray menu (`gao --apply` applies it but does not switch the watchdog back on). Only one optimize runs at a time, from the window or the command line.
4848

4949
### The command line
5050

‎src/app/common.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ bool enable_boot(std::string* message) {
223223
const DWORD n = GetModuleFileNameW(nullptr, self, MAX_PATH);
224224
if (n == 0 || n >= MAX_PATH) return say("could not find this program's own path", false);
225225
if (!install_app(std::filesystem::path(self).parent_path(), &why))
226-
return say(why + " (if the tray app is running from Program Files, exit it and retry)", false);
226+
return say(why, false);
227227
// The logon task starts the tray app, which applies the tune and stays
228228
// resident to keep it applied: no time limit.
229229
const auto exe = installed_tray_path();

‎src/app/gui/main_gui.cpp‎

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ struct App {
7676
gao::Watchdog watchdog;
7777
bool watch = false; // keep the saved tune applied (off after a revert or a reset by choice)
7878
bool worker_was_running = false;
79+
bool strike_pending = false; // a logon apply whose 2-minute grace has not passed yet
7980

8081
// Crash dumps are written by a thread created up front (a crashing thread
8182
// may have no stack or heap left to do it itself).
@@ -113,8 +114,9 @@ LONG WINAPI on_crash(EXCEPTION_POINTERS* ep) {
113114
g.crash_thread = GetCurrentThreadId();
114115
SetEvent(g.dump_request);
115116
WaitForSingleObject(g.dump_done, 15000);
116-
if (g.worker && g.worker->active_gpu() && g.worker->active_gpu()->reset_to_stock)
117-
g.worker->active_gpu()->reset_to_stock(); // never leave a candidate applied
117+
// Never leave a candidate applied. Through our own GpuControl, which lives
118+
// as long as the process; the worker's may be mid-teardown.
119+
if (g.worker && g.worker->running() && g.gpu.reset_to_stock) g.gpu.reset_to_stock();
118120
return EXCEPTION_EXECUTE_HANDLER;
119121
}
120122

@@ -220,7 +222,7 @@ void show_window() {
220222
}
221223

222224
void tray_menu(int x, int y) {
223-
const bool busy = g.worker->running();
225+
const bool busy = g.worker->running() || gao::app::tuning_in_progress();
224226
HMENU menu = CreatePopupMenu();
225227
AppendMenuW(menu, MF_STRING, kMenuOpen, L"Open");
226228
if (g.ui.elevated) {
@@ -259,8 +261,7 @@ void refresh_status(bool with_task) {
259261

260262
void act_optimize(gao::Preset preset) {
261263
g.ui.message.clear();
262-
g.watch = false; // the search owns the GPU until it is done
263-
g.worker->start(preset);
264+
g.worker->start(preset); // the watchdog skips while it runs
264265
}
265266

266267
void act_abort() { g.worker->abort(); }
@@ -285,8 +286,15 @@ void act_restart_elevated() {
285286
}
286287
}
287288

289+
bool refuse_while_tuning() {
290+
if (g.worker->running()) return true;
291+
if (!gao::app::tuning_in_progress()) return false;
292+
g.ui.message = "An optimize is running on the command line; wait for it to finish.";
293+
return true;
294+
}
295+
288296
void act_apply() {
289-
if (g.worker->running()) return;
297+
if (refuse_while_tuning()) return;
290298
std::string why;
291299
if (!gao::app::prepare_state(&why)) { g.ui.message = why; return; }
292300
gao::Config cfg = gao::app::load_config();
@@ -302,7 +310,7 @@ void act_apply() {
302310
}
303311

304312
void act_revert() {
305-
if (g.worker->running()) return;
313+
if (refuse_while_tuning()) return;
306314
const bool ok = g.gpu.reset_to_stock && g.gpu.reset_to_stock();
307315
g.watch = false; // stock by choice: the watchdog must not undo it
308316
g.ui.message = ok ? "Back at stock. The saved tune is not re-applied until you apply it again."
@@ -356,8 +364,12 @@ void on_telemetry() {
356364
const bool running = g.worker->running();
357365
if (g.worker_was_running && !running) {
358366
const auto snap = g.worker->snapshot();
359-
g.watch = snap.outcome && snap.outcome->ran && snap.outcome->result.ok;
360-
g.watchdog = gao::Watchdog();
367+
// A run that never started left the GPU alone: keep watching as before.
368+
// One that ran ends either at its saved result or at stock.
369+
if (snap.outcome && snap.outcome->ran) {
370+
g.watch = snap.outcome->result.ok && snap.outcome->saved;
371+
g.watchdog = gao::Watchdog();
372+
}
361373
refresh_status(false);
362374
}
363375
g.worker_was_running = running;
@@ -403,6 +415,15 @@ void on_watchdog() {
403415

404416
// ---------------------------------------------------------------- window
405417

418+
// The strike counts crashes during the first 2 minutes after a logon apply.
419+
// A clean exit or logoff in that time is not a crash: clear it then too.
420+
void clear_strike() {
421+
if (!g.strike_pending) return;
422+
g.strike_pending = false;
423+
KillTimer(g.hwnd, kTimerStrike);
424+
gao::app::clear_boot_strike();
425+
}
426+
406427
LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
407428
if (ImGui_ImplWin32_WndProcHandler(hwnd, msg, wp, lp)) return 1;
408429
if (msg == g.taskbar_created && g.taskbar_created) { // Explorer restarted: the icon is gone
@@ -448,7 +469,7 @@ LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
448469
case WM_TIMER:
449470
if (wp == kTimerTelemetry) on_telemetry();
450471
else if (wp == kTimerWatchdog) on_watchdog();
451-
else if (wp == kTimerStrike) { KillTimer(hwnd, kTimerStrike); gao::app::clear_boot_strike(); refresh_status(false); }
472+
else if (wp == kTimerStrike) { clear_strike(); refresh_status(false); }
452473
return 0;
453474
case WM_APP_WAKE:
454475
g.input_frames = std::max(g.input_frames, 1);
@@ -481,7 +502,8 @@ LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
481502
return TRUE;
482503
case WM_ENDSESSION:
483504
if (wp) { // the session really ends: stock now, the run cannot finish
484-
if (const gao::GpuControl* gpu = g.worker->active_gpu(); gpu && gpu->reset_to_stock) gpu->reset_to_stock();
505+
clear_strike();
506+
if (g.worker->running() && g.gpu.reset_to_stock) g.gpu.reset_to_stock();
485507
}
486508
return 0;
487509
case WM_DESTROY:
@@ -573,6 +595,7 @@ int WINAPI wWinMain(HINSTANCE inst, HINSTANCE, PWSTR cmdline, int) {
573595
const auto logon = gao::app::apply_at_logon();
574596
if (logon.applied) {
575597
g.watch = true;
598+
g.strike_pending = true;
576599
SetTimer(g.hwnd, kTimerStrike, 2 * 60 * 1000, nullptr);
577600
} else if (logon.decision != gao::BootDecision::NoProfile) {
578601
notify(logon.message);
@@ -600,6 +623,7 @@ int WINAPI wWinMain(HINSTANCE inst, HINSTANCE, PWSTR cmdline, int) {
600623
}
601624
if (quit) break;
602625
if (g.exit_requested && !g.worker->running()) {
626+
clear_strike();
603627
DestroyWindow(g.hwnd);
604628
continue;
605629
}

‎src/app/gui/worker.cpp‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,12 @@ bool OptimizeWorker::start(Preset preset) {
2626
}
2727
wake_();
2828
};
29-
hooks.active_gpu = [this](const GpuControl* gpu) { active_gpu_ = gpu; };
3029
app::OptimizeOutcome outcome;
3130
try {
3231
outcome = app::run_optimize(preset, hooks);
3332
} catch (const std::exception& e) { // a thrown exception would otherwise end the process
3433
outcome.error = std::string("unexpected error: ") + e.what();
3534
}
36-
active_gpu_ = nullptr;
3735
{
3836
std::lock_guard lock(mu_);
3937
outcome_ = std::move(outcome);

‎src/app/gui/worker.hpp‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ class OptimizeWorker {
2828
void abort() { abort_ = true; } // the run restores stock before it ends
2929
bool running() const { return running_; }
3030
Snapshot snapshot() const;
31-
// The GPU being tuned right now (nullptr otherwise), for an emergency reset.
32-
const GpuControl* active_gpu() const { return active_gpu_; }
3331

3432
private:
3533
std::function<void()> wake_;
@@ -38,7 +36,6 @@ class OptimizeWorker {
3836
std::optional<app::OptimizeOutcome> outcome_;
3937
std::atomic<bool> running_{false};
4038
std::atomic<bool> abort_{false};
41-
std::atomic<const GpuControl*> active_gpu_{nullptr};
4239
std::jthread thread_;
4340
};
4441

‎src/app/main.cpp‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ static int set_offset(const char* label, int mhz, bool core) {
9595
}
9696

9797
static int reset() {
98+
// Stock by choice: tell a running tray app first, or a watchdog tick
99+
// between the writes and the notice would take it for a TDR and re-apply.
100+
gao::app::tell_tray_stock_by_choice();
98101
gao::Nvapi nvapi;
99102
if (!nvapi.Init()) { std::printf("NVAPI init failed: %s\n", nvapi.Error().c_str()); return 1; }
100103
bool ok = nvapi.ResetOffsets(kGpu);
@@ -113,8 +116,6 @@ static int reset() {
113116
std::printf("power limit: default %s\n", power_ok ? "restored" : nvml.Error().c_str());
114117
ok = ok && power_ok;
115118
}
116-
// A stock card by choice: a running tray app must not re-apply the tune.
117-
if (ok) gao::app::tell_tray_stock_by_choice();
118119
std::printf("%s\n", ok ? "OK" : "MISMATCH");
119120
return ok ? 0 : 1;
120121
}

‎src/hw/boot_task.cpp‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,20 @@ bool install_app(const std::filesystem::path& from_dir, std::string* why) {
5858
if (dst.empty()) { if (why) *why = "the Program Files folder could not be resolved"; return false; }
5959
std::error_code ec;
6060
if (std::filesystem::equivalent(from_dir, dst, ec)) return true; // running the installed copy already
61+
for (const wchar_t* name : kAppExes) { // all or nothing: never a half-installed pair
62+
if (!std::filesystem::is_regular_file(from_dir / name, ec)) {
63+
if (why) *why = (from_dir / name).string() + " is missing; both executables must be in the same folder";
64+
return false;
65+
}
66+
}
6167
std::filesystem::create_directories(dst, ec);
6268
for (const wchar_t* name : kAppExes) {
6369
const auto src = from_dir / name;
6470
const auto to = dst / name;
65-
if (!CopyFileW(src.c_str(), to.c_str(), FALSE) && !(move_aside(to) && CopyFileW(src.c_str(), to.c_str(), FALSE))) {
71+
bool copied = CopyFileW(src.c_str(), to.c_str(), FALSE);
72+
if (!copied && (GetLastError() == ERROR_SHARING_VIOLATION || GetLastError() == ERROR_ACCESS_DENIED))
73+
copied = move_aside(to) && CopyFileW(src.c_str(), to.c_str(), FALSE); // a running copy
74+
if (!copied) {
6675
if (why) *why = "could not copy " + src.string() + " to " + dst.string() + " (error " + std::to_string(GetLastError()) + ")";
6776
return false;
6877
}

0 commit comments

Comments
 (0)