ecc521 / ecc521/CoolMyMac

Architecture: centralize fan-control lifecycle and hardware state transitions

Open
#1 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Swift
Stars
4
Forks
1
PR merge metrics
No merged PRs in 30d

Description

## Context

While investigating MAX mode on an M1 Max, the immediate backend issue was that M1–M4 fan control needs the global `Ftst` gate before per-fan mode/target writes are effective.

Reviewing the surrounding lifecycle exposed a broader concern that I intentionally did **not** include in the focused backend PR: desired profile state, applied SMC state, power transitions, and daemon auto-suspend are currently coordinated by separate components.

## Current risk areas

- `ProfileStore` can persist System mode independently of a confirmed hardware release.
- Profile selection/deletion, polling, sleep/wake, startup/shutdown, and auto-suspend do not share one transition owner.
- `SMCController.resetAllFans()` is fail-fast, so one failed reset can prevent later fans from being attempted.
- Apple Silicon `Ftst` is global, while the public control API is per-fan.
- Safety paths sometimes treat profile state as proof of applied hardware state.
- Sleep is a multi-message protocol (`CanSystemSleep`, `WillSleep`, `WillNotSleep`, wake), not a single callback.

These can produce partial/manual/unknown fan states even when the persisted profile says System.

## Suggested direction

Introduce a daemon-level `FanControlCoordinator` (an actor or one serial executor) as the only mutation gateway for:

- profile activation and active-profile deletion;
- periodic target application;
- startup and shutdown recovery;
- sleep cancellation, sleep entry, and wake;
- daemon auto-suspend eligibility.

The coordinator should track applied state explicitly, for example:

- `automatic`
- `manual(profileID)`
- `releasing(reason)`
- `sleepPending`
- `sleeping`
- `faulted/unknown`

### Transaction ordering

For a transition to System:

1. quiesce new fan writes;
2. record a pending release;
3. release hardware control and attempt every fan where control is per-fan;
4. only after success persist/report System and permit auto-suspend;
5. on failure remain active in a visible fault/retry state rather than exiting.

For Apple Silicon, the backend should expose whole-operation semantics such as `applyFanTargets` and `releaseAllControl`, so global `Ftst` ownership is not composed accidentally through repeated per-fan calls.

## Acceptance criteria

- System is reported and eligible for auto-suspend only after hardware release succeeds.
- A failed release cannot later trigger daemon exit merely because the persisted profile is System.
- Deleting the active profile follows the same System transition.
- Reset attempts continue across independently controlled fans and aggregate failures.
- No target writes occur while releasing, sleeping, or in unknown state.
- `CanSystemSleep -> WillNotSleep` resumes the prior desired state.
- Restart recovers an interrupted release before applying another manual profile.
- Fault-injection tests cover each fan failing, partial apply/reset, profile persistence failure, sleep cancellation, and auto-suspend during a failed release.

This is deliberately proposed as a separate architecture change rather than expanding the focused M1–M4 `Ftst` fix.

Contributor guide

Open the contributing guide

Research direction

Start by tracing lifecycle mutations through ProfileStore and SMCController.resetAllFans(), then map profile changes, sleep/wake handling, startup/shutdown, and auto-suspend. Use the proposed FanControlCoordinator and listed acceptance criteria as the boundary for the architecture. Done means transitions are serialized, failed releases remain visible and retryable, and fault-injection tests cover the listed partial and sleep scenarios.

Written by the indexing model from the issue text.

Assessment

Tech stack
macos, swift
Domain
desktop, operating-systems
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.