Skip to content

Fix sensor#1102

Merged
fly602 merged 8 commits into
linuxdeepin:masterfrom
fly602:fix-sensor
May 14, 2026
Merged

Fix sensor#1102
fly602 merged 8 commits into
linuxdeepin:masterfrom
fly602:fix-sensor

Conversation

@fly602
Copy link
Copy Markdown
Contributor

@fly602 fly602 commented May 13, 2026

No description provided.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @fly602, your pull request is larger than the review limit of 150000 diff characters

Copy link
Copy Markdown
Contributor

@robertkill robertkill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — 自动亮度功能

整体架构设计不错,卡尔曼滤波和亮度曲线实现质量较高。但有3个Critical问题需要修。

🔴 Critical(merge前必须修)

C1 display1/auto_brightness.go L1109/1205/1213 — 不安全类型断言会 PANIC

config.BrightnessChangeThreshold = val.Value().(float64)  // D-Bus int64时crash
config.KalmanProcessNoise = val.Value().(float64)          // 同上
config.KalmanMeasurementNoise = val.Value().(float64)      // 同上

同文件其他字段都用了 type switch(如L1078-1086),这三处漏了,DSettings返回int64时直接panic。建议统一用type switch处理。

C2 kalman_filter.go — 并发数据竞争
补偿定时器(150ms)调 GetEstimate() 只持有 RLock,传感器回调调 Update() 修改 xEst/PEst。两者并发 = data race。建议给 KalmanFilter1D 加独立 mutex,或确保 GetEstimate 也用写锁。

C3 auto_brightness.go Stop/Start 竞态
Stop()stopCompensationTimer() 会 Unlock→Wait→Relock,窗口期内 Start() 可创建新 ticker。Stop() 之后设 running=false 会覆盖新状态,新 ticker 变孤儿泄漏。建议加 atomic stopping 标志或重构锁逻辑。

⚠️ Warnings

W1 brightness_transition.go L299 — task.ctx.(chan struct{}) 不安全断言,建议存为强类型或改用 context.Context

W2 runTransition goroutine 无 WaitGroup 追踪,Stop() 后可能仍在运行

W3 sessionActive 字段跨 goroutine 读写无同步保护

W4 getConfig 中类型断言风格不一致(部分 type switch,部分直接断言)

💡 Suggestions

  • 多处 "faild" 拼写错误 → "failed"
  • 补偿 ticker Info 日志 150ms/次太频繁,建议改 Debug
  • 1024.0 硬编码 lux 上限,室外场景会超,建议可配置或对数映射
  • claimLightWithRetry 在 auto_brightness.go 和 sensor_proxy.go 重复实现
  • TransitionExecutor 建议用 context.Context 替代 chan struct{}
  • saveConfig 非原子写入,crash会半写

✅ 亮点

  • RWMutex 读路径优化合理
  • 手动覆盖机制防止和用户操作冲突
  • 卡尔曼滤波数学实现正确
  • 亮度曲线插值边界处理完善
  • 初始化逐步降级策略(config失败→默认,sensor失败→不支持)

@robertkill
Copy link
Copy Markdown
Contributor

Critical 问题具体位置

C1 display1/auto_brightness.go:1109 display1/auto_brightness.go:1205 display1/auto_brightness.go:1213

// 这三处会 panic,DSettings integer 返回 int64:
config.BrightnessChangeThreshold = val.Value().(float64)   // L1109
config.KalmanProcessNoise = val.Value().(float64)          // L1205
config.KalmanMeasurementNoise = val.Value().(float64)      // L1213
// 应改用同文件 L1078 的 type switch 写法

C2 display1/brightness/kalman_filter.go — GetEstimate() 无锁保护

  • L728 abm.kalmanFilter.GetEstimate() 在补偿定时器中被调用(只持有 RLock)
  • L665 abm.kalmanFilter.Update() 在传感器回调中修改 xEst/PEst(持有写锁)
  • RLock 和 Lock 并发 = data race,需给 KalmanFilter1D 加独立 mutex

C3 display1/auto_brightness.go:831 — stopCompensationTimer() Unlock 窗口

  • Stop() → stopCompensationTimer() → Unlock→Wait→Relock(L831-833)
  • 窗口期内 Start() 可创建新 ticker,Stop() 设 running=false 覆盖新状态

Comment thread display1/auto_brightness.go
Comment thread display1/auto_brightness.go
Comment thread display1/auto_brightness.go
Comment thread display1/auto_brightness.go
@fly602 fly602 force-pushed the fix-sensor branch 5 times, most recently from b605094 to a486319 Compare May 13, 2026 07:35
@robertkill
Copy link
Copy Markdown
Contributor

Code Review Summary

Verdict: Changes Requested (4 Critical, 7 Warnings, 7 Suggestions)


🔴 Critical

C1. saveConfig() 读取 abm.config 未持锁 — 数据竞争
display1/auto_brightness.gosaveConfig() 直接读取 abm.config.Enabledabm.config.Sensitivity 等字段,没有任何锁保护。与 OnConfigChanged()(持锁写 config)和 OnManualBrightnessChange()(持锁修改 config.Enabled)存在竞态。
建议: 在方法开头持锁快照 config,释放锁后再序列化。

C2. stopCompensationTimer() 的 Unlock→Wait→Lock 模式极其脆弱
display1/auto_brightness.go — 该方法要求调用者已持 abm.mutex,然后内部 Unlock→Wait→Lock。如果未来有人在 defer abm.mutex.Unlock() 的上下文中调用,会 double-unlock 导致 panic。
建议: 使用独立的 compensationMu,或重构使补偿 goroutine 不需要 abm.mutex

C3. Kalman Filter 双锁潜在死锁风险
display1/brightness/kalman_filter.goAdaptiveKalmanFilter.Update() 持有 akf.mu,内部调用 akf.SetMeasurementNoise() 又去获取 kf.mu。当前调用路径不会死锁,但锁顺序是隐式的,未来维护者很容易引入死锁。
建议: 添加不加锁的内部方法 setMeasurementNoiseLocked(),在已持 akf.mu 时调用,并明确文档化锁顺序。

C4. startServiceWatching() 中 SignalLoop 泄漏
display1/sensor_proxy.gosigLoop 作为局部变量创建并 Start(),但从未存储也从未 Stop()。每次 Connect() 调用都会泄漏一个永久运行的 goroutine。
建议:sigLoop 存到结构体上,在 Disconnect() 中调用 sigLoop.Stop()


🟡 Warnings

W1. getBacklightCurvePercent() 读取曲线状态未持锁
display1/brightness/curve.go — 读取 cm.curveTypecm.flmCurveFunc 等字段无锁保护,与写入端存在数据竞争。

W2. getCurveValue() 同样未持锁读取
display1/brightness/curve.go — 与 W1 同类问题。

W3. go callback() 在 sensor_proxy.go 中无生命周期同步
display1/sensor_proxy.go:369,388 — 回调在新 goroutine 中执行,如果 Cleanup() 后回调仍触发,可能访问已释放资源。

W4. calculateTargetBrightness() 死代码
display1/auto_brightness.goif br >= 0 { if br < 0.0 { ... } }br < 0.0 分支永远不可达。

W5. ensureSensorClaimed() TOCTOU 竞态
display1/auto_brightness.go — 先检查 IsClaimed()ClaimLight(),两步之间状态可能变化。

W6. transitionTask.ctx 类型为 interface{} 缺乏类型安全
display1/brightness/brightness_transition.go:78 — 实际用作 chan struct{},多处需类型断言,应直接定义为具体类型。

W7. 硬编码最小亮度 10%
display1/auto_brightness.go:870,8920.1 应提取为命名常量或纳入配置。


💡 Suggestions

  1. 使用 context.Context 替代手动 compensationStopCh 做取消,更符合 Go 惯例
  2. ExponentialMovingAverageMovingAverage 没有并发保护且当前未使用,建议删除
  3. claimLightWithRetry() 最多阻塞 6 秒(3次×2秒),考虑用 context + timeout
  4. fallback 线性映射 adjustedLevel / 1024.0 中 1024 是 magic number,应可配置或从传感器读取
  5. 暴露 D-Bus 方法触发立即亮度调整,方便调试
  6. setBrightness() 访问 abm.manager 未持锁,应至少做 nil 检查
  7. OnManualBrightnessChange() 异步 go saveConfig() 可能读到被其他 goroutine 修改的 config

✅ Looks Good

  • 补偿定时器的注释清晰解释了 Unlock/Wait/Lock 的原因
  • Stop/Start 窗口期的 atomic flag 设计正确
  • 模块分层清晰:SensorProxyClient → AutoBrightnessManager → brightness 包
  • Kalman 滤波数学公式正确(标准 predict-update cycle)
  • 渐变效果的 cancelTask 模式正确处理了新目标中断旧任务
  • 配置校验合理,异常时安全回退到默认值
  • 曲线插值正确处理了边界情况

Reviewed by Hermes Agent

@fly602 fly602 force-pushed the fix-sensor branch 2 times, most recently from f1f945e to ac0e8f3 Compare May 14, 2026 07:45
mhduiy
mhduiy previously approved these changes May 14, 2026
@fly602 fly602 force-pushed the fix-sensor branch 2 times, most recently from 013a888 to 73d3418 Compare May 14, 2026 09:19
@mhduiy mhduiy requested a review from robertkill May 14, 2026 09:47
fly602 added 7 commits May 14, 2026 19:51
新增AutoBrightnessManager自动亮度管理器,基于环境光传感器数据自动调节屏幕亮度

新增SensorProxyClient传感器客户端,通过net.hadess.SensorProxy D-Bus接口获取环境光数据

新增BrightnessTransition亮度渐变管理器,支持可配置的渐变时长和步进间隔

在Manager中集成自动亮度和渐变管理器,支持启停、暂停恢复、配置变更等状态管理

新增DSettings配置项,支持敏感度、变化阈值、轮询间隔、手动调节策略等参数

新增导出方法和D-Bus接口,支持外部控制自动亮度启停和属性查询

Log: 新增光感亮度调节功能
提供内置屏默认亮度曲线配置功能,重构CurveManager管理亮度曲线配置和计算。
包含对AI代码与设计的重构。

Change-Id: Icc3d68def8d6366f422c829703ac4ae1803a4d83
1. 添加自动亮度曲线配置支持,允许自定义环境光到亮度的映射关系
2. 引入卡尔曼滤波器处理环境光传感器数据,减少噪声干扰
3. 新增亮度变化阈值配置,避免微小亮度波动触发调节
4. 优化传感器数据处理流程,支持推送模式和缓存机制
5. 重构代码结构,分离滤波逻辑和亮度计算逻辑

Log: 自动亮度功能新增曲线映射和智能滤波,调节更平滑精准

Change-Id: I344f167fdb553e7cda137a78951bb6c764312aad
1. 调整卡尔曼滤波器参数:过程噪声0.01→0.8,测量噪声0.1→0.05,窗口大小10→3
2. 新增传感器数据补偿机制,在数据超时1秒时主动补偿卡尔曼滤波器
3. 添加补偿定时器管理逻辑,在自动亮度启停、暂停恢复等状态变更时同步管理
4. 新增重置历史状态方法,确保手动调节后能立即响应
5. 更新DSettings配置文件中的默认参数值

Log: 优化自动亮度调节的响应性和准确性

Change-Id: I19c1ece035dff6d91348b626466bffcec3c28733
1. 更新自动亮度设计文档,增加卡尔曼滤波器、亮度曲线管理和补偿机制等新功能
2. 更新自动亮度使用指南,补充新增配置参数和高级功能说明
3. 在 .gitignore 中添加 AGENTS.md 文件忽略规则

Change-Id: I946a1335a6b8d500e290581a950f483f999e446f
1. 移除了基于定时器的轮询机制,改为完全依赖传感器事件驱动
2. 删除了 pollInterval、stopChan、ticker、pollingWg 等轮询相关字段
3. 移除了 startPolling()、stopPolling()、pollLightLevel() 等轮询方法
4. 重构了补偿定时器逻辑,使其更简洁高效
5. 简化了状态管理,移除了 polling 状态字段
6. 优化了 adjustBrightnessOnce() 方法,直接从传感器获取当前光照值
7. manager.go 新增 isPowerSaving()、scheduleSystemAdjustingClear() 方法

Change-Id: I883ad02422f7aca35cd95374807350dae4635daa
1. 将补偿间隔从300毫秒缩短到150毫秒,提高亮度调节响应速度
2. 在resetHistoryState中增加卡尔曼滤波器重置和传感器数据时间重置
3. 移除resume方法中延迟1秒后执行adjustBrightnessOnce的逻辑
4. 将needCompensationWithClient中的GetCachedLightLevel改为GetLightLevel
5. 在RefreshBrightness方法中添加自动亮度启用时的跳过逻辑
6. 在SensorProxyClient中添加GetLightLevel方法,支持直接读取实时环境光强度

Change-Id: I00296ee3c0f0d776159566305909f35a23114b0f
1. Refactor SensorProxyClient to use go-dbus-factory generated proxy
instead of raw D-Bus calls
2. Move BrightnessTransition to brightness package as unified
TransitionManager with TransitionExecutor
3. Separate setBrightness and setBrightnessWithTransition methods for
clearer API
4. Add setColorTemperature method for independent color temperature
control
5. Fix potential deadlocks in
AutoBrightnessManager.OnManualBrightnessChange by releasing mutex before
async operations
6. Fix setColorTempOneShot to read brightness from system config instead
of monitor cache
7. Add power mode change listener to properly handle auto brightness
during power saving
8. Add backlight controller caching and curve inverse function for
percentage calculation
9. Remove unused functions: gracefulDegradeService, recoverService,
loadConfig
10. Update transition config from time-based to step-percent based
approach
11. Change default values: auto brightness and transition both disabled
by default

Influence:
1. Test auto brightness enable/disable functionality
2. Verify light sensor data acquisition works correctly with new proxy
3. Test brightness transition effects with different step-percent
configurations
4. Verify manual brightness adjustment properly pauses auto brightness
5. Test color temperature changes without affecting brightness
6. Verify no deadlocks occur during rapid brightness changes
7. Test power saving mode transition with auto brightness enabled
8. Verify system wake from sleep properly restores auto brightness state
9. Test monitor hot-plug scenarios with transition enabled
10. Verify backlight and gamma brightness types work correctly

refactor: 优化光感接口

1. 重构 SensorProxyClient,使用 go-dbus-factory 生成的代理替代原始 D-Bus
调用
2. 将 BrightnessTransition 移至 brightness 包,重构为统一的
TransitionManager 和 TransitionExecutor
3. 分离 setBrightness 和 setBrightnessWithTransition 方法,使 API 更清晰
4. 新增 setColorTemperature 方法,实现独立的色温控制
5. 修复 AutoBrightnessManager.OnManualBrightnessChange 中的潜在死锁,在
异步操作前释放互斥锁
6. 修复 setColorTempOneShot 从系统配置读取亮度而非显示器缓存
7. 新增电源模式变化监听,正确处理节能模式下的自动亮度
8. 新增背光控制器缓存和曲线逆函数计算百分比
9. 移除未使用的函数:gracefulDegradeService、recoverService、loadConfig
10. 将过渡配置从基于时间改为基于步进百分比的方式
11. 更改默认值:自动亮度和过渡默认均关闭

Influence:
1. 测试自动亮度开启/关闭功能
2. 验证新代理下光感数据获取正常工作
3. 测试不同步进百分比配置下的亮度过渡效果
4. 验证手动调节亮度正确暂停自动亮度
5. 测试色温变化不影响亮度
6. 验证快速亮度调节时不发生死锁
7. 测试开启自动亮度时的节能模式切换
8. 验证系统从睡眠唤醒正确恢复自动亮度状态
9. 测试开启过渡时的显示器热插拔场景
10. 验证背光和 Gamma 亮度类型正常工作

PMS: BUG-358023

Change-Id: I610f8dc65f0558b4727275c7124a92ee3ae6f631
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

本次代码审查针对自动亮度调节、亮度渐变以及背光曲线等功能的提交进行了详细分析。整体来看,代码架构清晰,模块划分合理,状态机和锁机制设计较为完善。但在并发控制、代码质量、性能优化和安全性方面仍有一些需要改进的地方。

以下是详细的审查意见:

1. 语法与逻辑

1.1 潜在的死锁风险

  • 文件: display1/auto_brightness.go
  • 问题: stopCompensationTimer() 方法的注释标明“调用者必须持有 abm.mutex 锁”,而在该方法内部调用了 abm.compensationWg.Wait()。然而,compensationTicker 的 goroutine 内部调用了 abm.compensationTick(),而 compensationTick() 的第一行就是 abm.mutex.Lock()
    当持有 abm.mutex 的 goroutine 调用 stopCompensationTimer() 并执行 Wait() 等待时,goroutine 可能正阻塞在 abm.mutex.Lock() 上,从而导致死锁
  • 建议: 不要在持有 abm.mutex 的情况下调用 compensationWg.Wait()。可以先将 ticker 和 channel 停止并置空,释放锁后再执行 Wait(),或者重构锁的粒度。

1.2 竞态条件

  • 文件: display1/auto_brightness.go
  • 问题: OnManualBrightnessChange() 中,在释放锁后启动了异步 goroutine:go func() { ... abm.saveConfig() ... abm.Stop() ... }()Stop() 方法内部会获取锁并修改状态。如果在此期间有其他并发请求(如 SetEnabled),可能导致状态混乱。另外,abm.config.Enabled = false 是在锁内修改的,但 saveConfig() 是在锁外异步执行的,这期间如果配置被再次修改,可能会保存错误的中间状态。
  • 建议: 将 saveConfig 和状态更新放在同一个受保护的逻辑流中,或者通过 channel 串行化状态变更事件。异步保存配置时,应在加锁期间拷贝所需的配置快照。

1.3 逻辑缺陷

  • 文件: display1/brightness/curve.go
  • 问题: generateCustomCurveFunc 返回的闭包中,使用了 cm.mu.RLock()。因为 cm 是全局变量 _curveManager 的指针,闭包被长期持有并调用,这会导致外部在调用 getCurveValue(也持有锁)时,如果触发到 limitedCurveFunc,由于同协程重复加读锁(RLock 重入),虽然 RWMutex 允许多个读锁,但如果此时有写锁介入,可能会造成饥饿或死锁。
  • 建议: 闭包内部不需要再动态读取 cm.maxBrightnessUnlimited,可以在创建闭包时(即 generateCustomCurveFunc 被调用时)将此值捕获为局部变量,或者将 unlimited 状态作为参数传入。

2. 代码质量

2.1 大量重复代码

  • 文件: display1/auto_brightness.go 中的 getConfig 方法
  • 问题: 读取 DSettings 配置的代码极度冗余,每个配置项都有近乎相同的 switch v := val.Value().(type) 和错误处理逻辑,导致方法长达 200 多行。
  • 建议: 抽取通用的泛型配置读取函数或辅助方法,例如:
    func getFloat64Config(mgr configManager.Manager, key string, defaultVal float64) float64 {
        if val, err := mgr.Value(0, key); err == nil {
            switch v := val.Value().(type) {
            case float64: return v
            case int64: return float64(v)
            }
        }
        return defaultVal
    }

2.2 未使用的变量与导入

  • 文件: display1/brightness/brightness.go
  • 问题: 移除了 multierr 包的引用,但在 _setBacklight 中仍然使用 fmt.Printf 输出调试信息,且 SetBacklight 函数忽略了部分控制器的错误(只记录日志继续循环),这在库级别代码中不够严谨。
  • 建议: 将 fmt.Printf 替换为 logger.Debugf。对于 SetBacklight,如果任何一个控制器设置失败,应考虑返回组合错误或明确说明为何忽略错误。

2.3 魔法数字

  • 文件: display1/auto_brightness.go
  • 问题: calculateTargetBrightnessmaxSensorLux = 1024.0,但在计算时使用 adjustedLevel / maxSensorLux,如果传感器返回的值超过 1024,计算结果会被截断到 1.0,这虽然安全,但缺乏解释。
  • 建议: 增加注释说明为何最大勒克斯值设定为 1024,或者将其提取为可配置项。

3. 代码性能

3.1 高频定时器与锁争用

  • 文件: display1/auto_brightness.go
  • 问题: compensationTicker 的间隔为 150ms,每次触发都会尝试获取 mutex 锁。这在系统负载高时可能引起频繁的锁争用,影响其他 UI 线程的 D-Bus 调用。
  • 建议: 考虑将补偿定时器的间隔适当延长(如 500ms 或 1s),或者采用无锁队列/Channel 异步处理传感器数据,减少锁的持有时间。

3.2 字符串替换的性能开销

  • 文件: display1/brightness/curve.go
  • 问题: parseDefaultBrightnessCurveparseCustomBrightnessCurves 中使用了 strings.ReplaceAll(jsonStr, "\\", "") 来移除反斜杠。这种做法不仅破坏了 JSON 中合法的转义字符(如字符串内部包含引号的情况),而且在 JSON 较大时产生不必要的内存分配。
  • 建议: 直接使用标准库的 json.Unmarshal,它能完美处理转义。如果是为了修复格式错误的 JSON,应在数据源头修复,而不是在运行时暴力替换。

4. 代码安全

4.1 资源泄漏

  • 文件: display1/sensor_proxy.go
  • 问题: startServiceWatching 中,每次调用都会新建一个 dbusutil.NewSignalLoop 并启动:sigLoop.Start()。但在 Disconnect 中虽然调用了 sigLoop.Stop(),如果 Connect 被多次调用(例如服务重启触发重连),可能导致旧的 serviceSigLoop 未被正确停止和回收,造成 Goroutine 泄漏。
  • 建议: 确保 serviceSigLoop 是单例或复用的,在创建新的之前,必须确保旧的已经完全停止并回收。

4.2 D-Bus 方法权限与输入校验

  • 文件: display1/manager_ifc.go
  • 问题: SetAutoBrightnessEnabled 方法直接暴露在 Session D-Bus 上,任何同会话的应用都可以调用。虽然这是预期行为,但配置的保存(saveConfig)涉及全局 DConfig 写入,如果缺乏速率限制,恶意应用可以高频切换状态导致系统抖动。
  • 建议: 在 SetAutoBrightnessEnabled 入口处增加防抖逻辑或频率限制。

4.3 JSON 解析安全性

  • 文件: display1/brightness/curve.go
  • 问题: SetCustomBrightnessCurvesSetDefaultBrightnessCurve 接受外部传入的 JSON 字符串并解析。虽然目前是从 DConfig 读取,但如果 DConfig 被篡改包含畸形超长 JSON,可能导致解析时内存暴涨。
  • 建议: 对传入的 JSON 字符串长度进行上限检查,或在解析时使用 json.Decoder 并设置 DisallowUnknownFields 以防止意外注入。

总结建议

  1. 优先修复死锁隐患stopCompensationTimer 中持锁调用 WaitGroup.Wait() 是极其危险的,必须重构。
  2. 重构冗余代码getConfig 中的配置读取逻辑必须抽象化,否则后续维护成本极高。
  3. 优化锁粒度:减少高频定时器对主锁的依赖,降低 D-Bus 通信延迟。
  4. 清理调试代码:移除 fmt.Printf,统一使用日志库。

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: fly602, mhduiy, robertkill

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@fly602 fly602 merged commit bef94d7 into linuxdeepin:master May 14, 2026
16 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants