Unix Process State

问题

有时候会在晚上睡觉之前开始在mac mini上下几个大文件或者跑brew update,等命令跑到一半发现不想再继续等下去了,因为等待的时间比预期的远远要长。我又不想因为一句命令让机器一整晚上都运行着,还是想着应该让这个命令结束之后再立刻休眠(pmset sleepnow)。

于是问题来了,我要么找到结束当前正在一直运行的这个命令再重新编辑(追加&& pmset sleepnow)达到效果,要么想办法找到那个进程执行完毕的那个时间点。其实对于brew update这类命令的话,结束执行再跑一点儿关系都没有,主要还是针对curl和封装了curl的任务,毕竟杀掉了重新来过一般只能重新开始。

思路

在这之前我是知道jobs, fgbg这几个常用命令的:一个在运行的程序,如果我发送Ctrl+Z的组合时间给它,就相当于挂起(suspend)了这个进程,这个时候开始它只能等待恢复(或者直接被杀掉)。jobs能直接查看到当前shell的进程列表中所有的任务列表,通过fg可以把最顶端挂起的任务恢复并分发到前台运行模式,相当于把目标任务提到了当前shell的正在执行的命令模式,相当于接替上一个状态继续执行。bg的作用基本一致,区别在于目标任务被分到了后台运行模式,当前shell仍然处于带输入模式接受下一个命令的执行。如果不考虑stdout或者目标任务的stdout被重定向到了其他文件,bg会表现得目标任务不存在一样,直到执行完毕会从后台运行模式下输出一个进程执行完毕的提示。

sleep举例:

➜  ~ sleep 30 && echo "I'm done"
^Z
[1]  + 18342 suspended  sleep 30
➜  ~ jobs
[1]  + suspended  sleep 30
➜  ~ fg
[1]  + 18342 continued  sleep 30
^Z
[1]  + 18342 suspended  sleep 30
➜  ~ bg
[1]  + 18342 continued  sleep 30
➜  ~
[1]  + 18342 done       sleep 30
➜  ~

DND 的触发逻辑就是这样的:允许目标进程启动,但是又立刻挂起,等待用户授权完成之后再恢复它。

解决方法

  1. fg && echo "Going to sleep..." && pmset sleep

    直接把fg当做目标任务的一个handler,fg在执行的时候相当于是把目标进程同步地dispatch到当前的主shell进程上来运行,直到目标进程执行完毕fg的任务才算完毕。然后继续下一个shell命令…

  2. wait, 上面说到的是同步地dispatch到主shell进程上来,那就应该有异步执行的操作然后在主线程等待(join)的信号处理方法。wait就是做这件事情的,但它也有自己的使用规则。

    wait接收一个进程id(pid)并等待目标进程的完成状态,但是这个pid必须是当前shell的子进程。wait不改变目标进程的运行模式和状态,它只是单纯地observe一个进程终止状态并作为返回值返回。所以我的问题也可以这么解决:

    ➜  ~ sleep 30 && echo "I'm done"
    ^Z
    [1]  + 55241 suspended  sleep 30
    ➜  ~ bg
    [1]  + 55241 continued  sleep 30
    ➜  ~ wait 55241 && echo "Going to sleep..." && pmset sleep
    [1]  + 55241 done       sleep 30
    gogo
    ➜  ~
    
  3. Composer component in iTerm2’s status bar

    这个功能是从iTerm2的3.3.0版本开始加入的,和上面的方式不同,它是以延迟式键盘事件的方式发送到当前shell的命令行然后回车执行的,这就相当于不用等待当前shell正在运行的进程终止,提前准备好接下来的输入然后发送。

    想到发送输入的情形就得把read的情形考虑进来,也就是说如果当前正在运行的命令包含一个交互式的等待用户输入以继续的逻辑,那么iTerm会如何处理呢?试一试:

    ➜  ~ ping www.baidu.com -t 5; read -n a; echo "Your input: $a"
    PING www.a.shifen.com (39.156.66.14): 56 data bytes
    64 bytes from 39.156.66.14: icmp_seq=0 ttl=53 time=8.710 ms
    64 bytes from 39.156.66.14: icmp_seq=1 ttl=53 time=9.165 ms
    date
    date
    64 bytes from 39.156.66.14: icmp_seq=2 ttl=53 time=8.611 ms
    64 bytes from 39.156.66.14: icmp_seq=3 ttl=53 time=8.332 ms
    64 bytes from 39.156.66.14: icmp_seq=4 ttl=53 time=11.044 ms
       
    --- www.a.shifen.com ping statistics ---
    5 packets transmitted, 5 packets received, 0.0% packet loss
    round-trip min/avg/max/stddev = 8.332/9.172/11.044/0.973 ms
    Your input: date
    ➜  ~ date
    Thu Sep 26 22:47:17 CST 2019
    ➜  ~
    

    结果出来了,过程中我用ping的目的只是为了延迟read的执行时间并加入一些输出,我在Composer组件里输出了date然后立刻回车了两次。这部分内容混杂到了整个session输出里面,但是第一个date以字符串的方式被read接收然后打印了出来,第二个date以命令的方式发送到了执行完毕之后的交互模式开始了一个新的shell任务。

    这个行为可以这么理解,从macOS Foundation NSRunLoop的原理上来看,iTerm当前shell的window或者tab在处理当前的自己的keyboard event loop的时候,会不断地询问当前运行模式下是否能处理输入事件,如果能的话就发送Composer队列里的第一个字符串到event loop中,从上面的例子可以看出来至少在处理read和普通模式的等待执行命令这两种情形是能处理的,然后结束Composer在本次loop的任务。如果不能的话就继续在下一个loop继续询问,直到Composer队列里面的所有文本命令被发送成功。

结语

  • jobs, fg, bg, wait 全部都是shell内置的命令,可能不同的shell在实现和行为上有些不同。以上环境是zsh的执行结果。

  • 可以从上面的几个例子看到其实通常一句shell命令在提交到iTerm的主进程launch_shell的时候可以有两种方式:一种是单命令,比如sleep 10,运行的时候只会产生一个进程。另一种是sleep 10 && echo "I’m done”(把&&替换成;是一样的)或者用了循环语句的情形,产生多个进程(其实大多是这种情形)。

    如果是简单的单命令执行,用以上任意一种方法都可以满足;如果是复杂的多进程执行,推荐使用第三种iTerm2的Composer方法,毕竟不用考虑进程状态等问题,但是还是要考虑read的处理问题。当然了,如果只会产生一个主进程的话,用方法1和2也是可以的,只是需要确认一下。pstree with watch?

  • sleep在上面的例子中测试并不是很好,据我测试Ctrl+Z对sleep并不能达到我期望的效果,貌似跟wall clock的行为有关,和我原本理解的按秒sleep有差别。

相关阅读:

Code Review Service

There is a wonderful channel called Code Review in Stack Exchange, it provides for a kind of question and answer style to review code for programmer.

Code Review Stack Exchange is a question and answer site for peer programmer code reviews.

Code Review不是一个新词,甚至是老生常谈。这次我们不讨论它的方式和规范,而是从商业的角度上看待这个问题。没有国外IT公司的开发经历,我们只基于国内的开发环境来看待这个问题。

论点

  1. 开发者大都是带着排斥心理去接手老项目的

    这不是在否定开发者的心态或者工作态度。假想现在突然有一个项目要我接手,首先对这个项目作出评价,就优缺点而已,其实我是先找缺点的。先把所有的毛病和奇怪的地方先梳理出来,先默认断言它是错误的,然后否定上一个结论,如果没有找到合理的解释,那就是一个问题/缺点了,如果找到了当然就忽略。直到最后我才会开始思考到底哪些地方值得算是优点的。

    对于一个没有上下文的项目而言就真的是这样,相反,如果是在Github上的一个开源项目,我们首先会看项目的Star, Fork, Commit和Contributor的数量等,然后决定开始以一种什么心态来阅读这个项目的源代码,这是有很大区别的。(当然也确实存在有些大项目后期被改残了的。)

  2. 老板需要人来维护已有的业务逻辑

    这应该是定理了吧,毕竟在不考虑业务逻辑大变化的前提下,现有的代码毕竟是老板请人花钱写出来的。对于后来的开发者而言也不能说随便就重写或者重构的。

  3. Code Guideline vs Code Review

    就我个人的认知而言其实Guideline其实只针对多人协作的编程方式,而且Guideline的目的是在新人加入一个项目之前应该了解的编码指南,它算不上是一套规则,最多勉强算是一个language style guide。Code review更侧重于对已有代码提出的质疑,也包括对不满足guideline的问题。

    以下部分地方简称Code Review为CR。

  4. 小公司大都是没有code review这个workflow的

    毕竟老板只想着找人来完成自己公司的业务实现,只关心it works这唯一一个结果。有些老板甚至在内心里也认为开发者也完全只是为了那份工资才来协助完成这份工作的,他们根本理解不了那些真正写代码的人。

  5. Code Review到底做了什么

    这一点算凑数的,只是为了加一个链接而已。 CR的主要作用一直都是优化并维持代码的高质量。

  6. Code Review到底对谁有利

    • 老板,也就是到底省不省钱。如果没有CR或者中断了一段时间的CR,新人接下来就是要大重构甚至重写了,这没什么问题,只是要花更长的时间,也就变相花了更多老板的钱。(好吧,有些外行老板只会压榨开发者的时间和加班让他们的完成这个事情,这也是常有的事情。)这是一种隐性的利好。
    • 开发者,其实我觉得这个几乎不算是一个答案选项。没有CR,开发者只会按照自己的一贯方式来完成一个项目,这其中也包含一些坏习惯,比如随便留一些hacking/magic code,temporary fixes等等。CR也并不能给开发者提供一个最佳的学习方式,因为从CR中学习别人的编码思想并不如直接阅读优秀的开源代码有价值。正如天天刷微博学习碎片知识和阅读完整知识树。有了CR之后,只会给开发者增加额外的时间来完成项目进度。唯一的好处可能只是在有限定性的规则和约束的同时给后续增量开发制造了一个舒适的编码体检。
  7. Code Review之外还有(应该)什么

    单一的CR并不是完整的,它只是在每一个迭代的增量更新中优化一部分设计而已。一个项目的完整大框架设计不应该单单依赖于CR,所以架构设计还是非常有用的。

需求

公司/老板把Code Review这个流程外包。

对于不同项目每个公司一定会有自己独立的一套架构设计,针对服务器,前端,客户端等等。这一点对于老板而言可能还真没有省事的解决方案,只能找开发者为自己单独设计,其中也包括为了涉及到的保密问题。相比于CR,我认为保密级别的问题还是相对低不少的,这其中还可以通过技术来很大程度上来解决这个问题。

设计

客户方把项目代码托管到自己指定(类似Bitbucket Server,简称Server版)或者服务方的平台(类似Bitbucket Cloud,简称Cloud版),接收代码之后由客户选择是否混淆符号,选择哪些代码文件可见。Cloud版允许所有Reviewer查看和review所有项目代码,Server版可以是邀请制+合作制,只允许指定Reviewer来查看和/或review指定项目代码。两个版本都可以有免费和付费制,并基于贡献值建立Reviewer的声望值。

托管

从托管安全的角度上讲这个业务如果是Bitbucket来做是相对比较有信任感的,毕竟有大厂背景以及他们现有的其他优秀的托管衍生服务。要加上这个业务也是最容易也最让人能够接受的。

保密

鉴于这是一个主要toB的项目,必须要让企业对自己托管的代码放心,即使是Atlassian这样的大厂,也还是推出了上面提到的两个版本的Bitbucket托管服务。

混淆

以基本的符号替换为例,这是有必要的。不管是针对公众还是企业针对自己的其他部门,都一定会有这个需求,主要是让老板放心托管。当然,混淆的数据还是需要加密处理并一起由托管服务负责,也需要加密处理。

加密

加密的核心是为了为技术付费,不让人(轻易)破解。针对的数据包括代码文件,混淆符号表和review的内容。Server版可能会脱离中心服务器的控制,所以至少可能还是需要license授权的方式才能访问系统。

Reviewer

需要有完整的Reviewer用户系统,经验,tag,语种,声望,奖励等。从这个角度来说,Stack Overflow是非常好的借鉴。没有声望的创作内容是没有评定价值的,没有奖励的创作是不可持续的。

奖励

包括声望,现金,甚至还可以有Offer。

KPI

老板的钱会花得有理有据,不然最多可能只是一个一次性消费。要有报告,统计被采纳的解决方案top 10和总数量,团队内部开发者的评价,Reviewer对项目的评价等等。

生态

在设想建立这个供需关系的时候,我们先不和公司现有的工作流进行对比。

无论哪个老板都不想自己的项目最终被搞成一堆臭狗屎,然后一波又一波的换人来重构或重写。对开发者的规范和警醒也是有必要的,CR在一般公司内部难以维持我认为有几点原因:一是领头没有带好,这是一系列协作流程的最终结果;二是依靠团队内开发者的相互监督建立起来的规则大都是弱化的,无法长期维持;三是老板只顾眼前利益和计划,当然大部分时候也是因为老板也没得选(解决方案)。

引进外围的Reviewer并不会对现有的项目开发者产生直接冲击,只会是约束效果和让自己更自律。对老板而言只是花另一小部分钱来维护自己的项目而已。

周期

传统的CR最大的弊端应该就是会拖延项目周期,毕竟对某些老板而言有时候时间比钱更值钱。传统的团队内部CR在时间上一般会因为Reviewer的工作计划有些冲突,最终导致代码合并时间点延后,甚至可能block其他开发者的进度。

解决这个问题的核心在于Reviewer的数量级和直接的奖励制度,良性循环才可能有最直接的效率输出。

证明

None

总结

None

Symbolic link path in macOS

推荐阅读:What Are Aliases, Symbolic Links, and Hard Links in Mac OS X?

这篇对alias和symbolic link的区别解释得还是很全的,从应用层上看alias更针对普通用户的操作层,一次创建,随意移动,只有Finder能识别。对比了一下这两种文件的状态,发现主要区别还是alias只是一个普通二进制文件,Finder在创建alias的时候是完全创建了一个新的独立的文件,类型是MacOS Alias file。Finder的Get Info window中还可以对这种alias文件进行更改Original指向,也是对inode的直接修改,也难怪能够随意移动和创建alias及alias副本。只是不能在terminal里使用罢了。

尤其是针对目录的alias,这种文件在terminal几乎完全就是鸡肋。这个时候symbol link创建的目录快捷方式的就有优势多了,可以在cd之间来去自如,几乎就是一个真实存在的directory。

➜  ~ stat adir
  File: adir
  Size: 102       	Blocks: 0          IO Block: 4096   directory
Device: 1000003h/16777219d	Inode: 24269459    Links: 3
Access: (0755/drwxr-xr-x)  Uid: (  504/  will)   Gid: (   20/   staff)
➜  ~ stat adir_alias
  File: adir_alias
  Size: 868       	Blocks: 8          IO Block: 4096   regular file
Device: 1000003h/16777219d	Inode: 24269469    Links: 1
Access: (0644/-rw-r--r--)  Uid: (  504/  will)   Gid: (   20/   staff)
➜  ~ stat adir_symbol
  File: adir_symbol -> adir
  Size: 4         	Blocks: 8          IO Block: 4096   symbolic link
Device: 1000003h/16777219d	Inode: 24269465    Links: 1
Access: (0755/lrwxr-xr-x)  Uid: (  504/  will)   Gid: (   20/   staff)
➜  ~ 

symbol link directory作为快捷方式虽然方便,但是对于pwd/$PWD而言,有时候也会带来困扰。比如最近我发现cmake生成的CMakeCache.txt中就会记录cmake过程中的环境变量$PWD,这些变量就包含了很多逻辑目录路径(logical directory path)。

➜  ~ cd /path/to/adir
➜  ~ pwd
/path/to/adir
➜  ~ cd /path/to/adir_symbol
➜  ~ pwd
/path/to/adir_symbol
➜  ~ 

实际上adir和adir_symbol是指向一个目录的,但是对于pwd而言(默认)是两个不同的路径。要解决统一的物理目录路径(physical directory path)问题,可以用到readlinkpwd -P这两个命令。

➜  ~ cd /path/to/adir_symbol
➜  ~ pwd
/path/to/adir_symbol
➜  ~ pwd -P
/path/to/adir
➜  ~ readlink -f /path/to/adir_symbol
/path/to_adir
➜  ~ 

readlink在GNU和mac下的行为稍有差异, 可以安装coreutil后用greadlink解决。

 

相关阅读:

Make Terminal Follow Aliases Like Symlinks

How can I get the behavior of GNU’s readlink -f on a Mac?