The Nix expression language is a pure, lazy, functional language.

最近日常使用 NixOS 的过程中,遇到了两个奇怪的问题。

1. nix build 爆内存

正如 Quote 所言,Nix 表达式语言是一种纯粹的、惰性的、函数式语言。这种特性造成了今天要讲的第一个问题 -- nix build 内存爆炸。

根据 GitHub issues NixOS/nixpkgs #139865 中的描述,我先是写了一个 uboot.nix:

{ pkgs ? import <nixpkgs> { } }:
pkgs.pkgsCross.aarch64-multiplatform.ubootRaspberryPi4_64bit.overrideAttrs (old: {
  patches = old.patches ++ [
    (pkgs.fetchpatch {
      url = "https://patchwork.ozlabs.org/series/259129/mbox/";
      sha256 = "04x55a62a5p4mv0ddpx7dnsh5pnx46vibnq8cry7hgwf4krl64gl";
    })
  ];
})

然后执行 nix-build uboot.nix 后,是正常 build 出了 u-boot-rpi4.bin,这里没有什么问题。

这之后为了 build sdImage 方便,我将其写进 flake.nix 中,做成 overlays:

{
  nixpkgs.overlays = [
    (self: super: {
      ubootRaspberryPi4_64bit = super.callPackage ./uboot.nix { };
    })
  ];
}

并加在 flake.nix 中的 modules 里,就像这样:VergeDX/PoC - flake.nix。其中,"${nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64.nix" 是用于构建 sdImage 的。可以参考一下 之前的博客

这看起来并没有什么问题。当然如果要用 overlays 的话,也可以直接在 overlays 里 overrideAttrs,没必要 callPackage。但这确是出现问题的地方。

我将相关代码另外单独放了一处 repo,有兴趣的朋友可以自己尝试一下:VergeDX/PoC。使用命令 nix build .#nixosConfigurations."recurrent".config.system.build.sdImage 即可构建。

在我发现问题前,我跑了 nix build 一段时间后,我发现我的系统整个卡住了。最后通过 Magic SysRq 组合键 才捡回一条命。我的机器配备了 32G 的内存,不存在内存不足的情况。

这之后我进行了多次实验与观察,发现跑完 nix build 后,内存一直在增长。同时 nix 进程也不吃 signal,只能通过 kill -9 来杀死。并且似乎是卡在 eval 阶段,因为无论是 -vL 还是 –dry-run 都不输出任何内容。

与 @NickCao 老师以及 @yinfeng 讨论后,爆内存的原因终于被找到了。首先需要注意的是 uboot.nix 的第一行,它引用 pkgs 或导入 nixpkgs。由于这是 flakes,而 import <nixpkgs> 是 impure 的。但该命令并未报 impure 错误,因为此处的 pkgs 是 flakes 中 nixpkgs 的 pkgs 属性,也即 nix repl 中的 pkgs.pkgs。

而 pkgs.pkgs 则是 pkgs 本身,这也意味着可以 pkgs.pkgs.pkgs… 一直写下去。这就导致了 patches 变量一直在被 ++,始终无法收敛,最后内存爆炸。这并不是 Nix 的 bug。

@NickCao 老师:不过 til (Today I Learned)

2. Chromium 与 xdg-open 的问题

下一个问题是关于 chromium 包的。问题是这样的,某天开始我发现,如果从别的 app 调用 Chromium 打开新标签页,Chromium 仅仅显示一个空白的 New Tab。比如在 Albert 中搜索东西时,或者 Telegram / [Alacritty / Kitty 终端] 中点击链接时,又或者在 VSCode 中进行三方登陆时。在这些场景下 Chromium 都只会弹一个新窗口出来,但却是空白页。

本来我以为这只是 Chromium 的 bug,因为 google-chrome 包和 firefox 包均无此问题,就没有放在心上。但它不能正常工作又有些讨厌,于是打算尝试修一修。找到了这一篇问答:

xdg-open only opens a new tab in a new Chromium window despite passing it a URL

这里说的是在 Desktop entity 里,chromium %U 中的 %U 在 xdg-open 时会被换成 url,从而打开新标签页。于是我检查了一下 chromium, google-chrome, firefox 包中的 .desktop 文件,没看出什么端倪。

然后我试了一下 chromium https://baidu.com,发现症状得以复现,于是我看了一下 chromium 的启动脚本(仅贴出最后四行):

exec "/nix/store/blqad79va9mw2mwimhi4mvya0jbrrrvs-chromium-unwrapped-94.0.4606.71/libexec/chromium/chromium"  --enable-gpu-rasterization \
--enable-zero-copy \
--enable-features=VaapiVideoDecoder
 "$@"

这里的三个 --enable-xxx flag 是我用 override 里的参数 commandLineArgs 传进去的。它接收一个字符串,并写在 chromium 的启动脚本里。由于最后那个多出来的换行,导致 “$@” 被删去,而它本来应该是 url 的占位符。又由于是 exec chromium ,所以执行完也不会报错,url 就这样给忽略掉了。

解决的方法也非常简单,多加一个反斜杠就好了:VergeDX/config-nixpkgs #a7c6a12