夜天之书 #63 上游优先的故事 8 Bad Cases

前排提示,本文全文长 1.6w 字,由十个故事组成。出于微信公众号的展示特点,我将其切成若干个部分分开推送。如果想要单页读完全部内容,请点击阅读原文跳转网页,网页展示有目录,可以快速导航。

Trino

第一个印象深刻的遭受挫折的故事,来自给 Trino 提交的一个补丁。

•Add support for date, time_millis, and timestamp_millis to AvroColumnDecoder[1]

这个补丁来自于 Pulsar 实现 Pulsar Trino Plugin 的过程中发现上游核心模块里的 AvroColumnDecoder 类功能可以增强。其实在此之前,Pulsar 社群就想过把整个 Pulsar Trino Plugin 捐赠给 Trino 上游维护。

•[Connector]Add plugin for Apache Pulsar.[2]

但是 Trino 作为一个公司项目,对待他们眼里的 “external contributor” 的要求不可谓不高。如果一视同仁,我还可以理解成项目一贯的高要求。然而就像我在 PR-13070 里回复的,分明 Starburst 公司自己的员工,就可以分批交付功能,发布在不同版本上,怎么到了 “external contributor” 身上,就要我啥都调研一下,啥都尝试着做呢?

更不用说每次我有什么回复,上游没有个一二十天是不会回复的,甚至我如果不到 Slack 频道上 pop 这个合并请求,根本不会有人再看。当然,我个人其实是支持如果你的补丁被上游忽略了,应该尽可能的增大声量,哪怕上游拒绝合并你的修改,也是一个结果。但是重复做这样的事情,只会让我觉得像是一个垃圾信息制造者。

这就引出来一个要点,上游有可能出于种种原因没能和你找到一个最大公因数完成上游优先的代码反馈,这种情况并不少见,作为下游项目,此时也要有自己的解决方案。

比如,虽然我们说尽可能在上游解决,但是上游已经不维护了,那也只能绕过或者替换了。前面故事里客串出场的 Docker Maven Plugin 就是我替换已经不维护了的 Dockerfile Maven Plugin 的选择。尽管我知道可以怎么修,但是上游也不想修了。

另一个手段是分支。对于上面 Dockerfile Maven Plugin 的例子来说,我也想过分支维护的思路,但是它其实又依赖了同一个组织下另一个已经不维护了的核心库,这一下维护成本暴涨,我也就没了兴趣。但是下游分支的情况并不少见。比如 Google 的 cpplint 几乎不维护,就有人拉出一个 cpplint[3] 组织来重新维护这个项目。

回到 Pulsar Trino Plugin 的例子上来,如果上游不接受我回推到核心模块的补丁,大不了我就像现在的实现一样在 Plugin 里把我需要的修改后的类单独写出来用就是了。虽然这样有不必要的代码重复,但是至少是个方法。进一步的,如果上游不接受 Pulsar Trino Plugin 捐赠,Pulsar 社群也可以自己维护。只不过确实 Trino 的后向兼容性极差,如果不能和上游同步迭代发版的话,外部的插件很可能很难和上游 Trino 服务端保持兼容。从用户层面看,就是同时使用 Pulsar 和 Trino 很难找到一个所有需要的功能都有,而且两者还能融洽相处的版本向量。

goleak

虽然 Trino 的情况让实践上游优先策略难度陡增,但是上游优先的失败案例并不总是上游的问题。

最典型的情况是,如果你对项目的理解和项目维护者不一致,或者说和项目的定位和要解决的问题方向不同,那么上游优先的反馈就很可能被拒绝。

我在迁移 TiDB 测试的时候,部分工作是使用 goleak[4] 库来替换手写的 leaktest 工具。这主要是因为手写的工具功能有限,甚至后来发现与漏报的情况,而由于没人维护这个手写的工具,这些问题长期都无人解决。

替换成 goleak 的过程里,我发现了一个重复的模式。由于 goleak 的接口设计,在调用检查方法之后,如果失败就调用 os.Exit 直接退出进程,这导致任何 teardown 的逻辑都没有办法执行。面对这个问题,我设计了一个 goleak.TestingM 的子类型和一组接口来支持退出逻辑。

这个包装类型用的还不少,正好 goleak 也是 Uber 开源的 Golang 库,我前不久在 atomic 库有不错的合作体验,于是就提议直接在上游提供这样定制退出逻辑的功能。

•support register teardown function in VerifyTestMain[5]

虽然上游维护者对实现方式有自己的想法,但是我觉得我的实现方式更合适一些。于是我直接怼了一个补丁上去:

•testmain: support WithCallback[6]

然后就是一年杳无音讯。直到上个月一个新加入 Uber 不久的员工按照上游原来的想法实现了这个能力,并把 issue 关联关闭了。虽然我觉得他的实现其实跟我当时指出的问题一样,不能很好地在编译时就阻止错误用法,而为了追求接口的“一致性”,有可能在运行时才抛出异常,但是既然这是上游的决定,这又是个公司项目,我也没什么好说的。

当然,哪怕不是公司项目,只要你不能做约束性投票,或者说你的理念不被项目核心维护者认同,那么下游更改无法推回上游也是很常见的。Apache SkyWalking 的作者吴晟就多次拒绝方向上不符合自己理念的反馈,或者确认问题,但是按照自己的理解来实现。

•Add property mutation mechanism to simplify dynamic config watcher[7]•[Bug] Malicious / misconfigured client can cause inaccessible trace view[8]

我自己作为维护者的时候也时有拒绝 contribution 的情况。这其实也是对开源项目维护者的一个要求:你不能对 contribution 来者不拒。开源软件的制造不是纯粹的人多力量大,而是一种精英主义。项目维护者需要依赖自己对项目的设计和理解,有选择性地吸纳社群成员和接受补丁。Linus 曾经说过,他做开源软件的过程中觉得最愉快的事情,就是可以只跟自己想要合作的人合作。

当然,这就意味着总有一些出于上游优先理念提交补丁或反馈问题的成员得不到正面反馈。但是,失败并不总是坏事。批判性的看待上游的反馈,我仍然觉得 Trino 的维护者不如我了解那部分代码,甚至我都解释累了;goleak 上游的实现我也觉得不如我的视线。还有上游的实现更好,我被折服了也学到许多的例子。

尾声

上面的故事和经历包括了十个不同类型的上游优先案例,包括代码的非代码的、有需求的纯顺手的、顺利的失败的、自己提交的做 reviewer 的。相信在开源社群当中和他人协作,或者想要参与开源社群的读者都能从中找到似曾相识的画面。

希望你在开源参与的过程里锻炼自己的技艺,收获友谊和成就!

References

[1] Add support for date, time_millis, and timestamp_millis to AvroColumnDecoder: https://github.com/trinodb/trino/pull/13070
[2] [Connector]Add plugin for Apache Pulsar.: https://github.com/trinodb/trino/pull/8020
[3] cpplint: https://github.com/cpplint/cpplint
[4] goleak: https://github.com/uber-go/goleak
[5] support register teardown function in VerifyTestMain: https://github.com/uber-go/goleak/issues/63
[6] testmain: support WithCallback: https://github.com/uber-go/goleak/pull/65
[7] Add property mutation mechanism to simplify dynamic config watcher: https://github.com/apache/skywalking/pull/9681
[8] [Bug] Malicious / misconfigured client can cause inaccessible trace view: https://github.com/apache/skywalking/issues/9734

猜你喜欢

转载自blog.csdn.net/weixin_44723515/article/details/127255635
63