Skip to content

Commit 65dec86

Browse files
committed
Plumb timeout through ReadAndDispatch, Shell(), use in long-running cmds
This fixes a bug in which ReadAndDispatch hits a timeout for a long-running command (eg. 'pm list packages') and assumes that the connection was broken. When the command finally finishes, we get errors when reading output from subsequent commands.
1 parent 3cf55dd commit 65dec86

File tree

4 files changed

+26
-20
lines changed

4 files changed

+26
-20
lines changed

adb/adb_protocol.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,6 @@ def __init__(self, header, data=''):
175175

176176
def Write(self, usb, timeout_ms=None):
177177
"""Send this message over USB."""
178-
timeout_ms = usb.Timeout(timeout_ms)
179178
# We can't merge these 2 writes, the device wouldn't be able to read the
180179
# packet.
181180
try:
@@ -190,7 +189,6 @@ def Read(cls, usb, timeout_ms=None):
190189
191190
Returns None if it failed to read the header with a ReadFailedError.
192191
"""
193-
timeout_ms = usb.Timeout(timeout_ms)
194192
packet = usb.BulkRead(24, timeout_ms)
195193
hdr = _AdbMessageHeader.Unpack(packet)
196194
if hdr.data_length:
@@ -228,9 +226,10 @@ def __str__(self):
228226
class _AdbConnection(object):
229227
"""One logical ADB connection to a service."""
230228
class _MessageQueue(object):
231-
def __init__(self, manager):
229+
def __init__(self, manager, timeout_ms=None):
232230
self._queue = Queue.Queue()
233231
self._manager = manager
232+
self._timeout_ms = timeout_ms
234233

235234
def __iter__(self):
236235
return self
@@ -241,7 +240,7 @@ def next(self):
241240
i = self._queue.get_nowait()
242241
except Queue.Empty:
243242
# Will reentrantly call self._Add() via parent._OnRead()
244-
if not self._manager.ReadAndDispatch():
243+
if not self._manager.ReadAndDispatch(timeout_ms=self._timeout_ms):
245244
# Failed to read from the device, the connection likely dropped.
246245
raise StopIteration()
247246
continue
@@ -255,14 +254,14 @@ def _Add(self, message):
255254
def _Close(self):
256255
self._queue.put(StopIteration())
257256

258-
def __init__(self, manager, local_id, service_name):
257+
def __init__(self, manager, local_id, service_name, timeout_ms=None):
259258
# ID as given by the remote device.
260259
self.remote_id = 0
261260
# Service requested on the remote device.
262261
self.service_name = service_name
263262
# Self assigned local ID.
264263
self._local_id = local_id
265-
self._yielder = self._MessageQueue(manager)
264+
self._yielder = self._MessageQueue(manager, timeout_ms=timeout_ms)
266265
self._manager = manager
267266

268267
@property
@@ -423,7 +422,7 @@ def Open(self, destination, timeout_ms=None):
423422
next_id = self._next_local_id
424423
self._next_local_id += 1
425424

426-
conn = _AdbConnection(self, next_id, destination)
425+
conn = _AdbConnection(self, next_id, destination, timeout_ms=timeout_ms)
427426
conn._Write('OPEN', destination + '\0')
428427
with self._lock:
429428
self._connections[conn.local_id] = conn

adb/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def __init__(self, serial=None, timeout_ms=None):
6363
self._timeout_ms = timeout_ms or DEFAULT_TIMEOUT_MS
6464

6565
def Timeout(self, timeout_ms):
66-
return timeout_ms if timeout_ms is not None else self._timeout_ms
66+
return max(timeout_ms, self._timeout_ms)
6767

6868
# TODO(bpastene) Remove all dependencies on a non UsbHandle needing port_path
6969
@property

adb/contrib/adb_commands_safe.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ def Remount(self):
382382
break
383383
return False
384384

385-
def Shell(self, cmd):
385+
def Shell(self, cmd, timeout_ms=None):
386386
"""Runs a command on an Android device while swallowing exceptions.
387387
388388
Traps all kinds of USB errors so callers do not have to handle this.
@@ -395,7 +395,7 @@ def Shell(self, cmd):
395395
if self._adb_cmd:
396396
for _ in self._Loop():
397397
try:
398-
return self.ShellRaw(cmd)
398+
return self.ShellRaw(cmd, timeout_ms=timeout_ms)
399399
except self._ERRORS as e:
400400
if not self._Reset('(%s): %s', cmd, e):
401401
break
@@ -413,7 +413,7 @@ def IsShellOk(self, cmd):
413413
# Has to keep one byte for trailing nul byte.
414414
return cmd_size < pkt_size
415415

416-
def ShellRaw(self, cmd):
416+
def ShellRaw(self, cmd, timeout_ms=None):
417417
"""Runs a command on an Android device.
418418
419419
It is expected that the user quote cmd properly.
@@ -433,8 +433,10 @@ def ShellRaw(self, cmd):
433433
# The adb protocol doesn't return the exit code, so embed it inside the
434434
# command.
435435
assert self.IsShellOk(cmd), 'Command is too long: %r' % cmd
436-
out = self._adb_cmd.Shell(cmd + self._SHELL_SUFFIX).decode(
437-
'utf-8', 'replace')
436+
timeout_ms = max(timeout_ms, self._default_timeout_ms)
437+
out = self._adb_cmd.Shell(
438+
cmd + self._SHELL_SUFFIX,
439+
timeout_ms=timeout_ms).decode('utf-8', 'replace')
438440
# Protect against & or other bash conditional execution that wouldn't make
439441
# the 'echo $?' command to run.
440442
if not out:

adb/contrib/high.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -444,12 +444,12 @@ def Remount(self):
444444
def Root(self):
445445
return self._device.Root()
446446

447-
def Shell(self, cmd):
447+
def Shell(self, cmd, timeout_ms=None):
448448
"""Automatically uses WrappedShell() when necessary."""
449449
if self._device.IsShellOk(cmd):
450-
return self._device.Shell(cmd)
450+
return self._device.Shell(cmd, timeout_ms=timeout_ms)
451451
else:
452-
return self.WrappedShell([cmd])
452+
return self.WrappedShell([cmd], timeout_ms=timeout_ms)
453453

454454
def ShellRaw(self, cmd):
455455
return self._device.ShellRaw(cmd)
@@ -729,7 +729,9 @@ def GetLastUID(self):
729729

730730
def GetPackages(self):
731731
"""Returns the list of packages installed."""
732-
out, _ = self.Shell('pm list packages')
732+
# pm can be very slow at times. Use a longer timeout to prevent
733+
# confusing a long-running command with an interrupted connection.
734+
out, _ = self.Shell('pm list packages', timeout_ms=30000)
733735
if not out:
734736
return None
735737
return [l.split(':', 1)[1] for l in out.strip().splitlines() if ':' in l]
@@ -818,7 +820,9 @@ def WaitUntilFullyBooted(self, timeout=300):
818820
'time',
819821
self.port_path)
820822
return False
821-
out, _ = self.Shell('pm path')
823+
# pm can be very slow at times. Use a longer timeout to prevent
824+
# confusing a long-running command with an interrupted connection.
825+
out, _ = self.Shell('pm path', timeout_ms=30000)
822826
if out == 'Error: no package specified\n':
823827
# It's up!
824828
break
@@ -880,7 +884,7 @@ def Mkstemp(self, content, prefix='python-adb', suffix=''):
880884
if self.PushContent(content, name):
881885
return name
882886

883-
def WrappedShell(self, commands):
887+
def WrappedShell(self, commands, timeout_ms=None):
884888
"""Creates a temporary shell script, runs it then return the data.
885889
886890
This is needed when:
@@ -899,7 +903,8 @@ def WrappedShell(self, commands):
899903
if not outfile:
900904
return False
901905
try:
902-
_, exit_code = self.Shell('sh %s &> %s' % (script, outfile))
906+
_, exit_code = self.Shell('sh %s &> %s' % (script, outfile),
907+
timeout_ms=timeout_ms)
903908
out = self.PullContent(outfile)
904909
return out, exit_code
905910
finally:

0 commit comments

Comments
 (0)