Node 脚本遭遇异常时如何安全退出

一个 Node 相关的项目中,总是少不了跑脚本。跑一个脚本拉取配置、处理一些数据以及定时任务更是家常便饭。 2020-08-26 07:47:03 Node脚本镜像 你应该知道的 Nacos 接入和避坑指南 Hello 大家好,我是阿粉,今天给大家分享微服务环境下必须要使用的一个强大的组件 Nacos。自从使用了 Nacos,阿粉的服务再也没有担心过服务注册和发现以及配置管理混乱的问题了。 2020-08-26 07:37:25 Nacos微服务SpringBoot 选择服务网格的注意事项 服务网格是近年来火热的技术之一,并且格局在不断变化中。可选择的服务网格选项也不少。但总要根据自己的需求来进行选择,本文会提到一些要素,来帮助DevOps团队确定最适合其特定情况的服务网格。

一个 Node 相关的项目中,总是少不了跑脚本。跑一个脚本拉取配置、处理一些数据以及定时任务更是家常便饭。

一个 Node 相关的项目中,总是少不了跑脚本。跑一个脚本拉取配置、处理一些数据以及定时任务更是家常便饭。

在一些重要流程中能够看到脚本的身影:

  • CI,用以测试、质量保障及部署等
  • Docker,用以构建镜像
  • Cron,用以定时任务

如果在这些重要流程中脚本出错无法及时发现问题,将有可能引发更加隐蔽的问题。

最近观察项目镜像构建,会偶尔发现一两个镜像虽然构建成功,但容器却跑不起来的情况。「究其原因,是因为 Exit Code 的问题」。

[[339305]]

Exit Code

什么是 exit code?

exit code 代表一个进程的返回码,通过系统调用 exit_group 来触发。在 POSIX 中,0 代表正常的返回码,1-255 代表异常返回码,一般主动抛出的错误码都是 1。在 Node 应用中使用 process.exitCode = 1 来代表因不期望的异常而中断。

这里有一张关于异常码的附表 Appendix E. Exit Codes With Special Meanings[1]。

异常码在操作系统中随处可见,以下是一个关于 cat 命令的异常以及它的 exit code,并使用 strace 追踪系统调用。

  1. $cata
  2. cat:a:Nosuchfileordirectory
  3. #使用strace查看cat的系统调用
  4. #-e只显示write与exit_group的系统调用
  5. $strace-ewrite,exit_groupcata
  6. write(2,"cat:",5cat:)=5
  7. write(2,"a",1a)=1
  8. write(2,":Nosuchfileordirectory",27:Nosuchfileordirectory)=27
  9. write(2,"\n",1
  10. )=1
  11. exit_group(1)=?
  12. +++exitedwith1+++

从系统调用的最后一行可以看出,该进行的 exit code 是 1,并把错误信息输出到 stderr (标准错误的 fd 为 2) 中

如何查看 exit code

从 strace 中可以来判断进程的 exit code,但是不够方便过于冗余,特别身处 shell 编程环境中。

「有一种简单的方法,通过 echo $? 来确认返回码」

  1. $cata
  2. cat:a:Nosuchfileordirectory
  3. $echo$?
  4. 1

throw new Error与Promise.reject区别

以下是两段代码,第一段抛出一个异常,第二段 Promise.reject,两段代码都会如下打印出一段异常信息,那么两者有什么区别?

  1. functionerror(){
  2. thrownewError('hello,error')
  3. }
  4. error()
  5. //Output:
  6. ///Users/shanyue/Documents/note/demo.js:2
  7. //thrownewError('hello,world')
  8. //^
  9. //
  10. //Error:hello,world
  11. //aterror(/Users/shanyue/Documents/note/demo.js:2:9)
  12. //atObject.<anonymous>(/Users/shanyue/Documents/note/demo.js:5:1)
  13. //atModule._compile(internal/modules/cjs/loader.js:701:30)
  1. functionerror(){
  2. thrownewError('hello,error')
  3. }
  4. error()
  5. //Output:
  6. ///Users/shanyue/Documents/note/demo.js:2
  7. //thrownewError('hello,world')
  8. //^
  9. //
  10. //Error:hello,world
  11. //aterror(/Users/shanyue/Documents/note/demo.js:2:9)
  12. //atObject.<anonymous>(/Users/shanyue/Documents/note/demo.js:5:1)
  13. //atModule._compile(internal/modules/cjs/loader.js:701:30)

在对上述两个测试用例使用 echo $? 查看 exit code,我们会发现 throw new Error() 的 exit code 为 1,而 Promise.reject() 的为 0。

「从操作系统的角度来讲,exit code 为 0 代表进程成功运行并退出,此时即使有 Promise.reject,操作系统也会视为它执行成功。」

这在 Dockerfile 与 CI 中将留有安全隐患。

Dockerfile 在 node 中的注意点

当使用 Dockerfile 构建镜像时,如果 RUN 的进程返回非 0 的返回码,构建就会失败。

「而在 Node 中的错误处理中,我们倾向于所有的异常都交由 async/await 来处理,而当发生异常时,由于此时 exit code 为 0 并不会导致镜像构建失败。」

这是一个浅显易懂的含 Promise.reject() 问题的镜像。

  1. FROMnode:12-alpine
  2. RUNnode-e"Promise.reject('hello,world')"

构建镜像过程如下:「即使在构建过程打印出了 unhandledPromiseRejection 信息,但是镜像仍然构建成功。」

  1. $dockerbuild-tdemo.
  2. SendingbuildcontexttoDockerdaemon33.28kB
  3. Step1/2:FROMnode:12-alpine
  4. --->18f4bc975732
  5. Step2/2:RUNnode-e"Promise.reject('hello,world')"
  6. --->Runningin79a6d53c5aa6
  7. (node:1)UnhandledPromiseRejectionWarning:hello,world
  8. (node:1)UnhandledPromiseRejectionWarning:Unhandledpromiserejection.Thiserrororiginatedeitherbythrowinginsideofanasyncfunctionwithoutacatchblock,orbyrejectingapromisewhichwasnothandledwith.catch().Toterminatethenodeprocessonunhandledpromiserejection,usetheCLIflag`--unhandled-rejections=strict`(seehttps://nodejs.org/api/cli.html#cli_unhandled_rejections_mode).(rejectionid:1)
  9. (node:1)[DEP0018]DeprecationWarning:Unhandledpromiserejectionsaredeprecated.Inthefuture,promiserejectionsthatarenothandledwillterminatetheNode.jsprocesswithanon-zeroexitcode.
  10. Removingintermediatecontainer79a6d53c5aa6
  11. --->09f07eb993fe
  12. Successfullybuilt09f07eb993fe
  13. Successfullytaggeddemo:latest

Promise.reject 脚本解决方案

能在编译时能发现的问题,绝不要放在运行时。所以,构建镜像或 CI 中需要执行 node 脚本时,对异常处理需要手动指定 process.exitCode = 1 来提前暴露问题

  1. runScript().catch(()=>{
  2. process.exitCode=1
  3. })

在构建镜像时,也有关于异常解决方案的建议:

(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)

根据提示,--unhandled-rejections=strict 将会把 Promise.reject 的退出码设置为 1,并在将来的 node 版本中修正 Promise 异常退出码。

  1. $node--unhandled-rejections=stricterror.js

--unhandled-rejections=strict 的配置对 node 有版本要求:

Added in: v12.0.0, v10.17.0

By default all unhandled rejections trigger a warning plus a deprecation warning for the very first unhandled rejection in case no unhandledRejection hook is used.

总结

  • 当进程结束的 exit code 为非 0 时,系统会认为该进程执行失败
  • 通过 echo $? 可查看终端上一进程的 exit code
  • Node 中 Promise.reject 时 exit code 为 0
  • Node 中可以通过 process.exitCode = 1 显式设置 exit code
  • 在 Node12+ 中可以通过 node --unhandled-rejections=strict error.js执行脚本,视 Promise.reject 的 exit code 为 1

©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经

(0)
打赏 微信扫码打赏 微信扫码打赏 支付宝扫码打赏 支付宝扫码打赏
清一色的头像清一色管理团队
上一篇 2023年5月5日 12:55
下一篇 2023年5月5日 12:55

相关推荐

发表评论

登录后才能评论

联系我们

在线咨询:1643011589-QQbutton

手机:13798586780

QQ/微信:1074760229

QQ群:551893940

工作时间:工作日9:00-18:00,节假日休息

关注微信