Skip to content

fix(language_support): batch package queries per call - #205

Merged
mhduiy merged 1 commit into
linuxdeepin:masterfrom
mhduiy:fix/language-support-batch-queries
Oct 16, 2026
Merged

mhduiy merged 1 commit into
linuxdeepin:masterfrom
mhduiy:fix/language-support-batch-queries

Conversation

@mhduiy

@mhduiy mhduiy commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

问题

PMS: BUG-379697、BUG-379687、BUG-379685

ByLocale / ByPackageAndLocale 查询语言支持包很慢,调用方(dde-daemon 的 LangSelector1.GetLanguageSupportPackages)可能超过 D-Bus 调用方超时。实测某台机器上 GetLanguageSupportPackages ja_JP.UTF-8 用了约 31 秒,每次 apt-cache policy 约 2.3 秒。

原因

language_support 对每个候选包各启动一次外部命令:

  • isPkgInstalled() → dpkg-query -W -- <name>
  • isPkgInstallable() → apt-cache policy -- <name>

apt-cache 每次启动都要加载整个包缓存并计算 pin,单次 0.1 秒起,pin 中有正则时可达 2 秒以上;一次查询有几十个候选包,耗时线性累积。

修改

language_support/lang_support.go,公开 API 签名不变:

  • 新增 pkgQuery:每次公开调用一份快照,含 installed 与 candidates。快照不跨调用缓存,安装或 apt update 后下一次查询立即生效。
  • isInstalled():一次 dpkg-query -W -f '${binary:Package} ${db:Status-Status}\n' 读取整个 dpkg 数据库(不传包名,因此不会因为个别包未知而整批失败),只接受状态为 installed 的条目。
  • loadCandidates():一次 apt-cache policy -- <所有候选> 批量查询,按顶格 name: 段落头 + 缩进 Candidate: 行解析;(none)、空值、没有该段落都算不可安装。
  • 新增 candidateNames()(只收集候选名,不启动进程)与 packagesFor()(公共流水线:收集 → 去重 → 一次 apt 批量查询 → 可安装过滤 → 已安装过滤 → 原有 GNOME/fcitx、hunspell-de-{de,at,ch}、hunspell-gl 排除规则)。
  • ByLocale() 先用同一份 dpkg 快照筛选触发包,再一次性调用 packagesFor();ByPackageAndLocale() 内部同样走一次 packagesFor()。
  • 多架构:name:arch 同时标记原始包名,只装了 name:i386 的包不再被判定为缺失。
  • apt-cache 环境只传 LC_ALL=C(同时去掉继承的 LC_ALL/LANG/LANGUAGE),否则翻译过的 apt 会输出本地化字段名,解析不到任何候选版本。
  • 两个工具各自使用独立的命令函数,二进制绝对路径写成字面量,不再经过一个可变的命令名参数;调用方无法决定执行哪个程序。
  • 每次调用加 context 超时(30 秒)约束,避免工具卡住时查询被无限期阻塞。单次批量调用实测为数十毫秒,该上限不会在正常场景触发。

验证

  • go test -race ./language_support 通过。
  • 新增 6 个测试:批量调用次数与参数、includeInstalled、GNOME 下排除 fcitx、去重、查询失败行为、快照刷新。
  • 本机实测同一次日语查询:dpkg-query 由 27 次降为 1 次,apt-cache 由 17 次降为 1 次,耗时由约 0.9 秒降为约 0.04 秒。
  • 行为等价性:把改动前的实现从 git 中取出,与改动后的实现一起编译,对 /usr/share/i18n/language_info.json 中的全部 146 个语言逐个比对 ByLocale 结果(includeInstalled 取 false/true):
    • 292 组对比,其中 177 组为非空结果(非空对空的假验证),差异 0 组。
    • 跑的是真实 dpkg / apt-cache 与真实 pkg_depends。
  • 解析假设已用真实 apt-cache policy 输出核对:所有顶格行均以 : 结尾;唯一的非包名顶格段为 Pinned packages:,其条目为缩进的 name -> version,不可能匹配 Candidate:;${binary:Package} 仅在有外部架构条目时才带 :arch;config-files 条目确实存在并被正确排除。
  • pkg_depends 中不含任何通配符,因此下一条"精确包名匹配"的变化当前无实际影响。

已知行为差异

  • 一次批量 apt 查询失败时,本次调用的所有候选都算不可安装(原实现只影响失败的那一个包);未增加重试或逐包回退。
  • apt 段落按精确包名匹配,形如 libc6* 的通配写法不再被顺带视为可安装;pkg_depends 中只有字面前缀,当前无影响。

dde-daemon 侧的调用方修复见 linuxdeepin/dde-daemon#1280。

Summary by Sourcery

Batch package-state queries per language-support lookup to substantially reduce locale query latency while keeping the public API and filtering behavior unchanged.

Bug Fixes:

  • Batch language-support package availability queries so locale lookups no longer launch separate package-manager commands for every candidate.

Enhancements:

  • Refresh installed and installable package state for each public query, including multi-architecture package handling and consistent candidate parsing.
  • Add bounded command execution and preserve existing package filtering behavior while handling batched query failures safely.

Tests:

  • Add coverage for batched command invocation, deduplication, installed-package filtering, desktop-specific exclusions, query failures, and per-call state refresh.

@sourcery-ai

sourcery-ai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Reworks language-support package discovery to perform at most one dpkg-query and one apt-cache batch query per public call, using a fresh snapshot and locale-independent parsing while retaining existing result filtering; extensive mocked tests verify invocation efficiency, compatibility behavior, error handling, and refresh semantics.

Sequence diagram for batched language-support package discovery

sequenceDiagram
    participant Caller
    participant LanguageSupport
    participant pkgQuery
    participant dpkg_query
    participant apt_cache

    Caller->>LanguageSupport: ByLocale(locale, includeInstalled)
    LanguageSupport->>pkgQuery: newPkgQuery()
    pkgQuery->>dpkg_query: runCommand(dpkgQueryBin, ...)
    dpkg_query-->>pkgQuery: installed package snapshot
    LanguageSupport->>LanguageSupport: candidateNames(trigger, locale)
    LanguageSupport->>pkgQuery: loadCandidates(names)
    pkgQuery->>apt_cache: runCommand(aptCacheBin, LC_ALL=C, policy, --, names)
    apt_cache-->>pkgQuery: batched candidate versions
    pkgQuery-->>LanguageSupport: packagesFor(...)
    LanguageSupport-->>Caller: filtered package list
Loading

File-Level Changes

Change Details Files
Batch package-state discovery into fresh per-call snapshots while preserving public APIs and filtering behavior.
  • Read installed packages with one full-database dpkg-query and normalize multi-architecture names.
  • Resolve all unique candidate packages with one apt-cache policy invocation and parse locale-independent output.
  • Centralize candidate collection, deduplication, installability filtering, installed filtering, and existing desktop/dictionary exclusions.
  • Route both public lookup methods through the shared snapshot pipeline, with ByLocale reusing its snapshot for trigger detection.
language_support/lang_support.go
Add deterministic tests for batching, parsing, filtering, failures, and snapshot freshness.
  • Mock command execution and verify one dpkg query, one apt query, exact deduplicated arguments, and LC_ALL=C environment handling.
  • Cover includeInstalled behavior, GNOME fcitx exclusion, multi-architecture handling, duplicate candidates, command failures, and state refresh across calls.
language_support/lang_support_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 security issue

Security issues:

  • Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code. (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="language_support/lang_support.go" line_range="45" />
<code_context>
	cmd := exec.Command(name, args...)
</code_context>
<issue_to_address>
**security (go.lang.security.audit.dangerous-exec-command):** Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

fly602
fly602 previously approved these changes Oct 15, 2026
@mhduiy mhduiy closed this Oct 15, 2026
@mhduiy mhduiy reopened this Oct 15, 2026
@linuxdeepin linuxdeepin deleted a comment from sourcery-ai Bot Oct 15, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 security issue, and 1 other issue

Security issues:

  • Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code. (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="language_support/lang_support.go" line_range="48" />
<code_context>
+// candidate names follow a "--" separator, so neither the binary nor an
+// argument can be taken from an untrusted string.
+var runCommand = func(name string, env []string, args ...string) ([]byte, error) {
+	cmd := exec.Command(name, args...)
+	if env != nil {
+		cmd.Env = env
</code_context>
<issue_to_address>
**Package queries can outlive callers**

When `apt-cache` stalls or takes longer than the D-Bus caller's deadline, `exec.Command` and `cmd.Output()` have no deadline or cancellation path, so a stalled or unusually slow `apt-cache` keeps the public query blocked even after its D-Bus caller times out; the caller sees a timeout and the server-side work continues.

Run the subprocess with a bounded context deadline and stop it when the query is cancelled or times out.
</issue_to_address>

### Comment 2
<location path="language_support/lang_support.go" line_range="48" />
<code_context>
	cmd := exec.Command(name, args...)
</code_context>
<issue_to_address>
**security (go.lang.security.audit.dangerous-exec-command):** Detected non-static command inside Command. Audit the input to 'exec.Command'. If unverified user data can reach this call site, this is a code injection vulnerability. A malicious actor can inject a malicious script to execute arbitrary code.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread language_support/lang_support.go Outdated
Comment thread language_support/lang_support.go Outdated
@mhduiy
mhduiy force-pushed the fix/language-support-batch-queries branch 2 times, most recently from c5def68 to e4984a6 Compare October 15, 2026 13:38
1. Replace the per-package dpkg-query and apt-cache processes with one dpkg
   status snapshot and one apt-cache policy batch per public call, because
   apt-cache loads the whole package cache and applies the pins on every
   start, which took seconds per candidate package.
2. Keep the snapshot local to the call, so an installation or an apt update
   is visible to the next query instead of returning stale results.
3. Mark name:arch entries as their plain name too and accept only the
   installed status, so a package installed for one architecture is no
   longer reported as missing.
4. Force LC_ALL=C for apt-cache, otherwise a translated apt prints localized
   field names and no candidate version can be parsed.
5. Bound each command with a context timeout, so a stalled tool cannot block
   the query forever.
6. Give each tool its own command function with a literal binary path, so the
   program to run is never taken from a variable.
7. Add tests covering the batching, includeInstalled, the GNOME and
   dictionary exclusions, query failures and the snapshot refresh.

Log: One language package query now runs a single dpkg-query and apt-cache.
Influence: Language package queries no longer time out.

fix(language_support): 语言包查询改为按调用批量执行

1. 不再逐包启动 dpkg-query 和 apt-cache,改为每次公开调用各执行一次 dpkg 状态读取和一次 apt-cache policy 批量查询;apt-cache 每次启动都要加载整个包缓存并计算 pin,单个候选包就要数秒。
2. 快照只在本次调用内有效,安装或 apt 更新后下一次查询即可看到新状态,不会返回过期结果。
3. name:arch 条目同时标记其原始包名,且只接受 installed 状态,某一架构已安装的包不再被判定为缺失。
4. apt-cache 强制使用 LC_ALL=C,否则翻译过的 apt 会输出本地化字段名,解析不到候选版本。
5. 每次调用加 context 超时,避免工具卡住时查询被无限期阻塞。
6. 两个工具各自使用独立的命令函数并写死二进制绝对路径,执行哪个程序不取自变量。
7. 补充测试,覆盖批量查询、includeInstalled、GNOME 与词典排除规则、查询失败以及快照刷新。

Log: 一次语言包查询现在只执行一次 dpkg-query 和一次 apt-cache。
PMS: BUG-379697
PMS: BUG-379687
PMS: BUG-379685
Influence: 语言包查询不再超时。
@mhduiy
mhduiy force-pushed the fix/language-support-batch-queries branch from e4984a6 to 0d045cb Compare October 15, 2026 13:42
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

🤖 AI 代码审查报告

总体评分: 100 分 (通过阈值: 70分)

Pass


📊 总体评价

项目 结果
审查结论 代码审查通过
评分详情 未发现安全漏洞,代码质量优秀,性能优化显著,语法逻辑正确。批量查询优化将语言支持包查询耗时从约0.9秒降至约0.04秒,同时保持了公共API和过滤行为不变。

📋 提交信息

项目 内容
PR linuxdeepin/dde-api#205
标题 fix(language_support): batch package queries per call
作者 mhduiy
分支 fix/language-support-batch-queries → master
修改文件 language_support/lang_support.go, language_support/lang_support_test.go
代码变更 +391 / -79
分析模式 全量分析
PMS关联 BUG-379697, BUG-379687, BUG-379685(无权限访问)

🔍 详细分析

1. 语法逻辑 ✅ (25/25分)

评价: 语法正确,逻辑清晰 ✅ 通过

代码审查详情:

  1. dpkgQueryCommand 和 aptCacheCommand 函数正确使用 context.WithTimeout 配合 defer cancel(),无资源泄漏
  2. pkgQuery.isInstalled() 延迟加载模式正确,多架构处理(name:arch → 同时标记 name)逻辑合理
  3. pkgQuery.loadCandidates() 正确解析 apt-cache policy 输出格式,处理 (none) 候选、空版本、缺失段落等情况
  4. candidateNames() 保留了原始的包名收集逻辑,行为一致
  5. packagesFor() 使用 seen map 正确去重,过滤流水线逻辑清晰
  6. ByLocale() 共享单个 pkgQuery 实例用于触发器检查和包过滤,确保一致性

潜在问题:
✅ 未发现明显问题


2. 代码质量 ✅ (25/25分)

评价: 代码结构清晰,注释完整 ✅ 通过

代码审查详情:

  1. 注释完整性:注释质量优秀,解释了设计决策(如 "Only LC_ALL=C: a translated apt prints localized field names"),函数级注释说明了目的和行为
  2. 代码重复:重构通过创建共享的 packagesFor() 方法减少了重复代码,原代码在 ByPackageAndLocale 和 ByLocale 中有重复的可安装性检查循环
  3. 结构合理性:组织清晰,职责分明:
    • 命令封装层(dpkgQueryCommand, aptCacheCommand)
    • 数据结构层(pkgQuery)
    • 查询方法层(isInstalled, loadCandidates)
    • 流水线层(candidateNames, packagesFor)
    • 公共API层(ByPackageAndLocale, ByLocale)
  4. 测试覆盖:全面,新增6个测试覆盖批量调用次数、includeInstalled、GNOME排除fcitx、去重、查询失败、快照刷新
  5. 调试信息清理:无残留调试代码

潜在问题:
✅ 未发现明显问题


3. 代码性能 ✅ (20/20分)

评价: 性能良好,资源使用合理 ✅ 通过

代码审查详情:

本次 PR 的核心目标是性能优化,优化效果显著:

  1. 外部命令调用次数:dpkg-query 从 27 次降至 1 次,apt-cache 从 17 次降至 1 次
  2. 耗时:从约 0.9 秒降至约 0.04 秒(22 倍提升)
  3. 无跨调用缓存:每次公开调用创建新快照,确保安装或 apt update 后下一次查询立即生效
  4. 超时保护:30 秒 context 超时防止工具卡住时查询被无限期阻塞,单次批量调用实测为数十毫秒
  5. 去重优化:候选包名在 apt-cache 调用前去重,避免重复参数

潜在问题:
✅ 未发现明显问题


4. 代码安全 🔒 (30/30分)

评价: 存在0个安全漏洞 ✅ 通过

🔐 发现 0 个安全漏洞

安全审查详情:

  1. 命令注入防护:使用 exec.CommandContext 配合固定二进制路径(/usr/bin/dpkg-query, /usr/bin/apt-cache),参数以独立切片元素传递,不经过 shell。-- 分隔符防止包名被解析为选项
  2. 环境加固:aptCacheCommand 设置 cmd.Env = []string{"LC_ALL=C"},剥离所有继承的环境变量,减少攻击面
  3. 超时保护:30 秒 context 超时防止命令无限期阻塞
  4. 错误处理:命令失败时优雅降级(返回空结果,不崩溃),已由测试验证
  5. 输入来源:包名来自可信的 pkg_depends 文件,非用户输入
  6. 二进制路径加固:使用绝对路径字面量,防止 PATH 操纵攻击
  7. 无敏感信息:无硬编码凭据,无敏感数据日志

安全漏洞详情:
✅ 未发现安全漏洞

漏洞对比统计:新增漏洞 0 个,减少漏洞 0 个,持平 0 个


📈 评分汇总

维度 评分 满分 状态
语法逻辑 25 25 ✅ 通过
代码质量 25 25 ✅ 通过
代码性能 20 20 ✅ 通过
代码安全 30 30 ✅ 通过
总分 100 100 ✅ 通过

📝 审查说明

  • 分析模式:全量分析(GitHub PR 始终使用全量分析)
  • 代码图谱分析:本次变更为单包(language_support)修改,涉及2个文件,代码逻辑清晰,GitNexus 不可用,跳过代码图谱分析
  • OCR 审查:已启动 OCR 审查但超时未返回结果,使用 AI 审计结果作为主要依据
  • SAST 扫描:无 SAST 扫描数据
  • PMS 关联:PR 描述关联 BUG-379697、BUG-379687、BUG-379685,但 PMS 账号无权限访问

本报告由 AI 代码审查工具自动生成
扫描时间: 2026-10-15 21:52:00

@mhduiy
mhduiy requested a review from fly602 October 16, 2026 01:35
@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

@mhduiy
mhduiy merged commit ebf1c70 into linuxdeepin:master Oct 16, 2026
13 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.

3 participants