Skip to content

Commit 252a4ff

Browse files
committed
Allow connections to be used as context managers
Add a base class for connection objects that implements the context manager interface and keeps track of the connection's target (which gets set by Target.get_connection). When used as a context manager, the connection substitutes itself for the target's default connection for the thread within the context using Target.set_connect(). The old connection is restored upon exiting the context.
1 parent 29438c8 commit 252a4ff

File tree

7 files changed

+56
-5
lines changed

7 files changed

+56
-5
lines changed

devlib/connection.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class ConnectionBase:
2+
3+
def __init__(self):
4+
self.target = None
5+
self._old_conn = None
6+
7+
def __enter__(self):
8+
self._old_conn = self.target.set_connection(self)
9+
return self.target
10+
11+
def __exit__(self, exc_type, exc_value, traceback):
12+
self.target.set_connection(self._old_conn)
13+

devlib/host.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from getpass import getpass
2323
from pipes import quote
2424

25+
from devlib.connection import ConnectionBase
2526
from devlib.exception import TargetTransientError, TargetStableError
2627
from devlib.utils.misc import check_output
2728

@@ -37,7 +38,7 @@ def kill_children(pid, signal=signal.SIGKILL):
3738
os.kill(cpid, signal)
3839

3940

40-
class LocalConnection(object):
41+
class LocalConnection(ConnectionBase):
4142

4243
name = 'local'
4344
host = 'localhost'
@@ -56,6 +57,7 @@ def connected_as_root(self, state):
5657
# pylint: disable=unused-argument
5758
def __init__(self, platform=None, keep_password=True, unrooted=False,
5859
password=None, timeout=None):
60+
super(LocalConnection, self).__init__()
5961
self._connected_as_root = None
6062
self.logger = logging.getLogger('local_connection')
6163
self.keep_password = keep_password

devlib/target.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,9 @@ def disconnect(self):
311311
def get_connection(self, timeout=None):
312312
if self.conn_cls is None:
313313
raise ValueError('Connection class not specified on Target creation.')
314-
return self.conn_cls(timeout=timeout, **self.connection_settings) # pylint: disable=not-callable
314+
conn = self.conn_cls(timeout=timeout, **self.connection_settings) # pylint: disable=not-callable
315+
conn.target = self
316+
return conn
315317

316318
def set_connection(self, conn):
317319
tid = id(threading.current_thread())

devlib/utils/android.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
except ImportError:
3737
from pipes import quote
3838

39+
from devlib.connection import ConnectionBase
3940
from devlib.exception import TargetTransientError, TargetStableError, HostError
4041
from devlib.utils.misc import check_output, which, ABI_MAP
4142

@@ -233,7 +234,7 @@ def _run(self, command):
233234
return output
234235

235236

236-
class AdbConnection(object):
237+
class AdbConnection(ConnectionBase):
237238

238239
# maintains the count of parallel active connections to a device, so that
239240
# adb disconnect is not invoked untill all connections are closed
@@ -263,6 +264,7 @@ def connected_as_root(self, state):
263264
# pylint: disable=unused-argument
264265
def __init__(self, device=None, timeout=None, platform=None, adb_server=None,
265266
adb_as_root=False):
267+
super(AdbConnection, self).__init__()
266268
self.timeout = timeout if timeout is not None else self.default_timeout
267269
if device is None:
268270
device = adb_get_device(timeout=timeout, adb_server=adb_server)

devlib/utils/ssh.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from pexpect import EOF, TIMEOUT, spawn
4040

4141
# pylint: disable=redefined-builtin,wrong-import-position
42+
from devlib.connection import ConnectionBase
4243
from devlib.exception import (HostError, TargetStableError, TargetNotRespondingError,
4344
TimeoutError, TargetTransientError)
4445
from devlib.utils.misc import (which, strip_bash_colors, check_output,
@@ -157,7 +158,7 @@ def check_keyfile(keyfile):
157158
return keyfile
158159

159160

160-
class SshConnection(object):
161+
class SshConnection(ConnectionBase):
161162

162163
default_password_prompt = '[sudo] password'
163164
max_cancel_attempts = 5
@@ -194,6 +195,7 @@ def __init__(self,
194195
sudo_cmd="sudo -- sh -c {}",
195196
options=None
196197
):
198+
super(SshConnection, self).__init__()
197199
self._connected_as_root = None
198200
self.host = host
199201
self.username = username

doc/connection.rst

+29
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,32 @@ The only methods discussed below are those that will be overwritten by the
249249
.. method:: _wait_for_boot(self)
250250

251251
Wait for the gem5 simulated system to have booted and finished the booting animation.
252+
253+
254+
.. _multiple-connections:
255+
256+
Multipe Connections With One Target
257+
-----------------------------------
258+
259+
A ``Target`` will automatically maintain one connection per active thread and
260+
will seemlessly switch between them, so that target commands being executed from
261+
parallel threads won't block each other.
262+
263+
It is also possible to create additional connection objects within the same
264+
thread. You can then use these connections as context managers to execute
265+
target commands using them rather than the default conection for the thread:
266+
267+
```
268+
conn = target.get_connection()
269+
with conn:
270+
target.execute('ls') # uses conn rather than the default connection.
271+
```
272+
273+
If the connection object is being uses withn another function, you do not need
274+
to pass the target into that function as well, as the target will be returned on
275+
entering the connection's context:
276+
277+
```
278+
with conn as target:
279+
target.execute('ls')
280+
```

doc/target.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@ Target
182182
thread, so you don't normally need to use this explicitly in
183183
threaded code. This is generally useful if you want to perform a
184184
blocking operation (e.g. using ``background()``) while at the same
185-
time doing something else in the same host-side thread.
185+
time doing something else in the same host-side thread. See also
186+
:ref:`multple-connections`.
186187

187188
.. method:: Target.set_connection(conn)
188189

0 commit comments

Comments
 (0)