From 544e795fbb8a750f67796d7d0cad898f3f5f711c Mon Sep 17 00:00:00 2001 From: David Pursell Date: Mon, 14 Sep 2015 15:36:26 -0700 Subject: [PATCH] adb: Kill subprocess when the client exits. When the client exits (e.g. with Ctrl+C) the subprocess should be notified as well so it can cleanup if needed. Bug: http://b/23825725 Change-Id: Idb771710b293e0a9f7bebc9e2814b3a816e2c50e --- adb/shell_service.cpp | 8 ++++++++ adb/test_device.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/adb/shell_service.cpp b/adb/shell_service.cpp index 0274ae33b..f1bc36d5a 100644 --- a/adb/shell_service.cpp +++ b/adb/shell_service.cpp @@ -413,6 +413,14 @@ void Subprocess::PassDataStreams() { D("closing FD %d", dead_sfd->fd()); FD_CLR(dead_sfd->fd(), &master_read_set); FD_CLR(dead_sfd->fd(), &master_write_set); + if (dead_sfd == &protocol_sfd_) { + // Using SIGHUP is a decent general way to indicate that the + // controlling process is going away. If specific signals are + // needed (e.g. SIGINT), pass those through the shell protocol + // and only fall back on this for unexpected closures. + D("protocol FD died, sending SIGHUP to pid %d", pid_); + kill(pid_, SIGHUP); + } dead_sfd->Reset(); } } diff --git a/adb/test_device.py b/adb/test_device.py index fedd2d755..4452eed38 100644 --- a/adb/test_device.py +++ b/adb/test_device.py @@ -23,6 +23,7 @@ import posixpath import random import shlex import shutil +import signal import subprocess import tempfile import unittest @@ -196,6 +197,34 @@ class ShellTest(DeviceTest): self.assertEqual('foo' + self.device.linesep, result[1]) self.assertEqual('bar' + self.device.linesep, result[2]) + def test_non_interactive_sigint(self): + """Tests that SIGINT in a non-interactive shell kills the process. + + This requires the shell protocol in order to detect the broken + pipe; raw data transfer mode will only see the break once the + subprocess tries to read or write. + + Bug: http://b/23825725 + """ + if self.device.SHELL_PROTOCOL_FEATURE not in self.device.features: + raise unittest.SkipTest('shell protocol unsupported on this device') + + # Start a long-running process. + sleep_proc = subprocess.Popen( + self.device.adb_cmd + shlex.split('shell echo $$; sleep 60'), + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + remote_pid = sleep_proc.stdout.readline().strip() + self.assertIsNone(sleep_proc.returncode, 'subprocess terminated early') + proc_query = shlex.split('ps {0} | grep {0}'.format(remote_pid)) + + # Verify that the process is running, send signal, verify it stopped. + self.device.shell(proc_query) + os.kill(sleep_proc.pid, signal.SIGINT) + sleep_proc.communicate() + self.assertEqual(1, self.device.shell_nocheck(proc_query)[0], + 'subprocess failed to terminate') + class ArgumentEscapingTest(DeviceTest): def test_shell_escaping(self):