原文:

Why does running a background task over ssh fail if a pseudo-tty is allocated?

问题:

I've recently run into some slightly odd behaviour when running commands over ssh. I would be interested to hear any explanations for the behaviour below.

Running ssh localhost 'touch foobar &' creates a file called foobar as expected:

[bob@server ~]$ ssh localhost 'touch foobar &'
[bob@server ~]$ ls foobar
foobar

However running the same command but with the -t option to force pseudo-tty allocation fails to create foobar:

[bob@server ~]$ ssh -t localhost 'touch foobar &'
Connection to localhost closed.
[bob@server ~]$ echo $?
0
[bob@server ~]$ ls foobar
ls: cannot access foobar: No such file or directory

My current theory is that because the touch process is being backgrounded the pseudo-tty is allocated and unallocated before the process has a chance to run. Certainly adding one second sleep allows touch to run as expected:

[bob@pidora ~]$ ssh -t localhost 'touch foobar & sleep 1'
Connection to localhost closed.
[bob@pidora ~]$ ls foobar
foobar

回答:

This is related with how process groups work, how bash behaves when invoked as a non-interactive shell with -c, and the effect of & in input commands.

The answer assumes you're familiar with how job control works in UNIX; if you're not, here's a high level view: every process belongs to a process group (the processes in the same group are often put there as part of a command pipeline, e.g. cat file | sort | grep 'word' would place the processes running cat(1), sort(1) and grep(1) in the same process group). bash is a process like any other, and it also belongs to a process group. Process groups are part of a session (a session is composed of one or more process groups). In a session, there is at most one process group, called the foreground process group, and possibly many background process groups. The foreground process group has control of the terminal (if there is a controlling terminal attached to the session); the session leader (bash) moves processes from background to foreground and from foreground to background with tcsetpgrp(3). A signal sent to a process group is delivered to every process in that group.

If the concept of process groups and job control is completely new to you, I think you'll need to read up on that to fully understand this answer. A great resource to learn this is Chapter 9 of Advanced Programming in the UNIX Environment (3rd edition).

That being said, let's see what is happening here. We have to fit together every piece of the puzzle.

In both cases, the ssh remote side invokes bash(1) with -c. The -c flag causes bash(1) to run as a non-interactive shell. From the manpage:

An interactive shell is one started without non-option arguments and without the -c option whose standard input and error are both connected to terminals (as determined by isatty(3)), or one started with the -i option. PS1 is set and $- includes i if bash is interactive, allowing a shell script or a startup file to test this state.

Also, it is important to know that job control is disabled when bash is started in non-interactive mode. This means that bash will not create a separate process group to run the command, since job control is disabled, there will be no need to move this command between foreground and background, so it might as well just remain in the same process group as bash. This will happen whether or not you forced PTY allocation on ssh with -t.

However, the use of & has the side effect of causing the shell not to wait for command termination (even if job control is disabled). From the manpage:

If a command is terminated by the control operator &, the shell executes the command in the background in a subshell. The shell does not wait for the command to finish, and the return status is 0. Commands separated by a ; are executed sequentially; the shell waits for each command to terminate in turn. The return status is the exit status of the last command executed.

So, in both cases, bash will not wait for command execution, and touch(1) will be executed in the same process group as bash(1).

Now, consider what happens when a session leader exits. Quoting from setpgid(2) manpage:

If a session has a controlling terminal, and the CLOCAL flag for that terminal is not set, and a terminal hangup occurs, then the session leader is sent a SIGHUP. If the session leader exits, then a SIGHUP signal will also be sent to each process in the foreground process group of the controlling terminal.

When you don't use -t

When you don't use -t, there is no PTY allocation on the remote side, so bash is not a session leader, and in fact no new session is created. Because sshd is running as a daemon, the bash process that is forked + exec()'d will not have a controlling terminal. As such, even though the shell terminates very quickly (probably before touch(1)), there is no SIGHUP sent to the process group, because bash wasn't a session leader (and there is no controlling terminal). So everything works.

When you use -t

-t forces PTY allocation, which means that the ssh remote side will call setsid(2), allocate a pseudo-terminal + fork a new process with forkpty(3), connect the PTY master device input and output to the socket endpoints that lead to your machine, and finally execute bash(1). forkpty(3) opens the PTY slave side in the forked process that will become bash; since there's no controlling terminal for the current session, and a terminal device is being opened, the PTY device becomes the controlling terminal for the session and bash becomes the session leader.

Then the same thing happens again: touch(1) is executed in the same process group, etc., yadda yadda. The point is, this time, there is a session leader and a controlling terminal. So, since bash does not bother waiting because of the &, when it exits, SIGHUP is delivered to the process group and touch(1) dies prematurely.

About nohup

nohup(1) doesn't work here because there is still a race condition. If bash(1) terminates before nohup(1) has the chance to set up the necessary signal handling and file redirection, it will have no effect (which is probably what happens)

A possible fix

Forcefully re-enabling job control fixes it. In bash, you do that with set -m. This works:

ssh -t localhost 'set -m ; touch foobar &'

Or force bash to wait for touch(1) to complete:

ssh -t localhost 'touch foobar & wait `pgrep touch`'

running a background task over ssh的更多相关文章

  1. UWP -- Background Task 深入解析

    原文:UWP -- Background Task 深入解析 1. 重点 锁屏问题 从 Windows 10 开始,用户无须再将你的应用添加到锁屏界面,即可利用后台任务,通用 Windows 应用必须 ...

  2. Tasker to detect application running in background

    We used to be told that tasker is only capable of detecting foreground application, if the app gets ...

  3. 与众不同 windows phone (13) - Background Task(后台任务)之后台文件传输(上传和下载)

    原文:与众不同 windows phone (13) - Background Task(后台任务)之后台文件传输(上传和下载) [索引页][源码下载] 与众不同 windows phone (13) ...

  4. 与众不同 windows phone (12) - Background Task(后台任务)之 PeriodicTask(周期任务)和 ResourceIntensiveTask(资源密集型任务)

    原文:与众不同 windows phone (12) - Background Task(后台任务)之 PeriodicTask(周期任务)和 ResourceIntensiveTask(资源密集型任 ...

  5. 与众不同 windows phone (11) - Background Task(后台任务)之警报(Alarm)和提醒(Reminder)

    原文:与众不同 windows phone (11) - Background Task(后台任务)之警报(Alarm)和提醒(Reminder) [索引页][源码下载] 与众不同 windows p ...

  6. ios background task

    今天要实现一个需求,当用户触摸HOME键,将应用切换到后台时,启动自动备份的任务.这涉及到ios的后台任务处理,本文简单总结一下 首先,ios app有5种状态,分别是:not running, in ...

  7. how to run a continuous background task on OpenShift

    https://stackoverflow.com/questions/27152438/best-way-to-run-rails-background-jobs-with-openshift ht ...

  8. 【SQL Server学习笔记】事务、锁定、阻塞、死锁 sys.sysprocesses

    http://blog.csdn.net/sqlserverdiscovery/article/details/7712068 Column name Data type Description   ...

  9. 了解和解决SQL SERVER阻塞问题(copy)

    http://support.microsoft.com/kb/224453 Summary In this article, the term "connection" refe ...

随机推荐

  1. 阻止YII 1.0自动加载内置JQUERY库

    有些时候我们会在项目中用到很多js库, 因为Yii 1.0框架会默认自动加载一些自带核心库, 很容易引起冲突问题, 下面的代码就展示了如何在Yii 1.0框架下取消jQuery自动加载. Open C ...

  2. hdu 4601 Letter Tree

    不easy啊.. 一个小错误让我wa死了.找了一个晚上,怎么都找不到 最后是对拍代码找到的错误.发现当步数比較小的时候答案就是对的,比較大的时候就不正确了 想到一定是什么地方越界了.. . power ...

  3. js项目第一课:获取节点的方法有三个

    第一种方法: demo.html代码如下:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" &qu ...

  4. shell学习五十七天----linux任务管理,针对上一讲的总结和扩展

    linux任务管理 在linux下有两类任务管理,各自是一次性和周期性.一次性是at和batch,周期性又分为系统不论什么和用户任务. 一次性任务: 1.命令格式:at [选项] time 2.选项: ...

  5. struts2中在Action中如何获取servlet的api?

    1.通过ActionContext类(拿到的不是真正的servlet api,而是一个map) ActionContext context = ActionContext.getContext(); ...

  6. 程序包 javax.servlet 不存在 解决办法

    其原因是java编译器没有找到软件包javax.servlet. 下载servlet.jar放到lib下没有效果,后发现需要在jdk中添加,如下: 解决办法: 从tomcat lib目录下拷贝一个se ...

  7. hdu 5881 Tea (2016 acm 青岛网络赛)

    原题地址:http://acm.hdu.edu.cn/showproblem.php?pid=5881 Tea Time Limit: 3000/1000 MS (Java/Others)    Me ...

  8. 2016 acm香港网络赛 A题. A+B Problem (FFT)

    原题地址:https://open.kattis.com/problems/aplusb FFT代码参考kuangbin的博客:http://www.cnblogs.com/kuangbin/arch ...

  9. kubernetes高级之集群中使用sysctls

    系列目录 在linux系统里,sysctls 接口允许管理员在运行时修改内核参数.参数存在于/proc/sys/虚拟进程文件系统里.参数涉及到很多子模块,例如: 内核(kernel)(常见前缀kern ...

  10. 【BZOJ1038】[ZJOI2008]瞭望塔 半平面交

    [BZOJ1038][ZJOI2008]瞭望塔 Description 致力于建设全国示范和谐小村庄的H村村长dadzhi,决定在村中建立一个瞭望塔,以此加强村中的治安.我们将H村抽象为一维的轮廓.如 ...