openlang

parse() 里创建了 ImportFailure,但 Main 根本收不到(退出码是 3 而不是 2)

你好,

我目前在两份工作之间,时间多得很,正好用来被一个 100 行的程序搞糊涂。

ledger-import 读取分号分隔的 CSV,把清洗后的行写入暂存文件。除非整个文件都解析成功,否则暂存里不允许留下任何东西。我的测试文件 data/march.csv 第 4 行有个损坏的金额(-12,5x)。我预期看到 import rejected: line 4: bad amount ...,退出码 2。实际得到的是下面这个,退出码是 3。

SEVERE: staging problem: staging file build/staging.tmp closed with 2 uncommitted rows

行号和错误的值哪里都没出现。我在 parse() 的 catch 块里临时加了 System.err.println,它确实会打印,所以 ImportFailure 确实被创建了,只是一直没到达 Main。把第 4 行改对之后导入就正常了。

我最先怀疑的是 Main 里的 catch 顺序和 logging.properties 的配置。把两个 catch 块调换顺序没有任何有用的变化。相关部分如下。

// Importer.run()
StagingWriter writer = new StagingWriter(staging);
try {
    ...
    writer.add(parse(lineNo, line));
    ...
    writer.commit();
    return writer.count();
} finally {
    writer.close();
}
// StagingWriter.close()
out.close();
if (!committed && rows > 0) {
    Files.deleteIfExists(path);
    throw new IOException("staging file " + path + " closed with " + rows + " uncommitted rows");
}

Main 先捕获 ImportFailure(退出码 2),再捕获 IOException(退出码 3)。

会不会日志根本就不是问题?我的异常到底去哪了?

从英语翻译查看原文

后续讨论 1

顺着彼此的依据读下去
  1. 固定链接

    你的 ImportFailure 不是在日志里丢的,而是被 finally 块顶替掉了。

    parse() 抛出 ImportFailure,然后 finally 执行 writer.close()。这时还有未提交的行,所以 close() 抛出 IOException。在 finally 里新抛出的异常会胜出,ImportFailure 被丢弃,Main 只能看到 IOException。这就是你得到退出码 3 的原因。

    用 try-with-resources 来修。如果方法体抛出异常,close() 抛出的异常会作为 suppressed 异常附加上去,原来的异常仍然是主异常。

    try (StagingWriter writer = new StagingWriter(staging)) {
        ...
        writer.commit();
        return writer.count();
    }

    StagingWriter 必须实现 AutoCloseable。最终还应该把“uncommitted”的检查从 close() 里挪出去。只负责删除文件的 abort 路径更干净。

    从英语翻译查看原文

同一个问题的其他角度

  1. `String::from_utf8` 失败时会把字节还给你

    消耗型 API 得决定失败时那个值怎么处理。String::from_utf8 按值接收 Vec<u8>,失败时由错误持有它。 它会打印 bad byte at 2, 4 bytes back。没有拷贝,调用方在一个值里同时拿回缓冲区和出错的偏移。m…

    回复 0最近活动 rust
  2. except ValueError 在 gather(return_exceptions=True) 外面永远不执行,然后 ValueError 引发 TypeError

    我在客户回访前跑一个小脚本,根据三项指标给每个客户的网站打分,每个客户的权重从他们的分析接口获取。获取是通过 asyncio.gather 做的,并且设了 return_exceptions=True,所以一个客户出问题不会取消其他的,对吧?响应为空…

    回复 5最近活动 python
  3. use 链里每一行都挂着 `result.map_error`

    use 配 result.try 是 Gleam 里我最喜欢的部分,但我开始有点烦它了。每一步调的是不同的模块,每个模块有自己的错误类型,结果每一行末尾都得挂一个 result.map_error。 把构造器当函数传确实能让它短一点。但每行有一半都是…

    回复 1最近活动 gleam