Skip to content

Commit 1bafe31

Browse files
DeepMindcopybara-github
authored andcommitted
Make close() functions more resilient to errors.
Failing one resource cleanup shouldn't prevent cleaning up other resources. PiperOrigin-RevId: 778861249
1 parent 96bbfac commit 1bafe31

11 files changed

Lines changed: 135 additions & 75 deletions

android_env/components/coordinator.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,10 @@
2121

2222
from absl import logging
2323
from android_env.components import action_fns
24-
from android_env.components import action_type as action_type_lib
2524
from android_env.components import adb_call_parser
2625
from android_env.components import config_classes
2726
from android_env.components import device_settings as device_settings_lib
2827
from android_env.components import errors
29-
from android_env.components import pixel_fns
3028
from android_env.components import specs
3129
from android_env.components import task_manager as task_manager_lib
3230
from android_env.components.simulators import base_simulator
@@ -277,6 +275,12 @@ def close(self):
277275
"""Cleans up the state of this Coordinator."""
278276

279277
if hasattr(self, '_task_manager'):
280-
self._task_manager.stop()
278+
try:
279+
self._task_manager.stop()
280+
except: # pylint: disable=bare-except
281+
logging.exception('Failed to stop task manager. Continuing.')
281282
if hasattr(self, '_simulator'):
282-
self._simulator.close()
283+
try:
284+
self._simulator.close()
285+
except: # pylint: disable=bare-except
286+
logging.exception('Failed to close simulator. Continuing.')

android_env/components/task_manager.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import json
2424
import re
2525
import threading
26+
import time
2627
from typing import Any
2728

2829
from absl import logging
@@ -101,12 +102,22 @@ def setup_task(self) -> None:
101102

102103
def stop(self) -> None:
103104
"""Suspends task processing."""
104-
self._stop_logcat_thread()
105+
n_tries = 3
106+
for i in range(n_tries):
107+
try:
108+
self._stop_logcat_thread()
109+
break
110+
except: # pylint: disable=bare-except
111+
logging.exception(
112+
'Failed to stop logcat thread [%d/%d]. Continuing.', i + 1, n_tries
113+
)
114+
time.sleep(1)
105115

106116
def start(
107117
self,
108118
adb_call_parser_factory: Callable[[], adb_call_parser_lib.AdbCallParser],
109-
log_stream: log_stream_lib.LogStream) -> None:
119+
log_stream: log_stream_lib.LogStream,
120+
) -> None:
110121
"""Starts task processing."""
111122

112123
self._start_logcat_thread(log_stream=log_stream)
@@ -150,7 +161,8 @@ def rl_reset(self, observation: dict[str, Any]) -> dm_env.TimeStep:
150161
step_type=dm_env.StepType.FIRST,
151162
reward=0.0,
152163
discount=0.0,
153-
observation=observation)
164+
observation=observation,
165+
)
154166

155167
def rl_step(self, observation: dict[str, Any]) -> dm_env.TimeStep:
156168
"""Performs one RL step."""
@@ -202,25 +214,31 @@ def _determine_transition_fn(self) -> Callable[..., dm_env.TimeStep]:
202214
if self._task.max_episode_steps > 0:
203215
if self._stats['episode_steps'] > self._task.max_episode_steps:
204216
self._stats['reset_count_max_duration_reached'] += 1
205-
logging.info('Maximum task duration (%r steps) reached. '
206-
'Truncating the episode.', self._task.max_episode_steps)
217+
logging.info(
218+
'Maximum task duration (%r steps) reached. Truncating the episode.',
219+
self._task.max_episode_steps,
220+
)
207221
return dm_env.truncation
208222

209223
if self._task.max_episode_sec > 0.0:
210224
task_duration = datetime.datetime.now() - self._task_start_time
211225
max_episode_sec = self._task.max_episode_sec
212226
if task_duration > datetime.timedelta(seconds=int(max_episode_sec)):
213227
self._stats['reset_count_max_duration_reached'] += 1
214-
logging.info('Maximum task duration (%r sec) reached. '
215-
'Truncating the episode.', max_episode_sec)
228+
logging.info(
229+
'Maximum task duration (%r sec) reached. Truncating the episode.',
230+
max_episode_sec,
231+
)
216232
return dm_env.truncation
217233

218234
return dm_env.transition
219235

220236
def _start_setup_step_interpreter(
221-
self, adb_call_parser: adb_call_parser_lib.AdbCallParser):
237+
self, adb_call_parser: adb_call_parser_lib.AdbCallParser
238+
):
222239
self._setup_step_interpreter = setup_step_interpreter.SetupStepInterpreter(
223-
adb_call_parser=adb_call_parser)
240+
adb_call_parser=adb_call_parser
241+
)
224242

225243
def _start_logcat_thread(self, log_stream: log_stream_lib.LogStream):
226244
log_stream.set_log_filters(list(self._task.log_parsing_config.filters))
@@ -229,8 +247,9 @@ def _start_logcat_thread(self, log_stream: log_stream_lib.LogStream):
229247
for event_listener in self._logcat_listeners():
230248
self._logcat_thread.add_event_listener(event_listener)
231249

232-
def _start_dumpsys_thread(self,
233-
adb_call_parser: adb_call_parser_lib.AdbCallParser):
250+
def _start_dumpsys_thread(
251+
self, adb_call_parser: adb_call_parser_lib.AdbCallParser
252+
):
234253
self._dumpsys_thread = dumpsys_thread.DumpsysThread(
235254
app_screen_checker=app_screen_checker.AppScreenChecker(
236255
adb_call_parser=adb_call_parser,

android_env/env_interface.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from android_env.proto import adb_pb2
2626
from android_env.proto import state_pb2
2727
import dm_env
28+
from dm_env import specs
2829
import numpy as np
2930

3031

@@ -34,11 +35,11 @@ class AndroidEnvInterface(dm_env.Environment, metaclass=abc.ABCMeta):
3435
# Methods required by dm_env.Environment.
3536

3637
@abc.abstractmethod
37-
def action_spec(self) -> dict[str, dm_env.specs.Array]:
38+
def action_spec(self) -> dict[str, specs.Array]:
3839
"""Returns the action specification."""
3940

4041
@abc.abstractmethod
41-
def observation_spec(self) -> dict[str, dm_env.specs.Array]:
42+
def observation_spec(self) -> dict[str, specs.Array]:
4243
"""Returns the observation specification."""
4344

4445
@abc.abstractmethod
@@ -61,11 +62,11 @@ def task_extras(self, latest_only: bool = True) -> dict[str, np.ndarray]:
6162
return {}
6263

6364
@property
64-
def raw_action(self):
65+
def raw_action(self) -> Any:
6566
"""Returns the latest action."""
6667

6768
@property
68-
def raw_observation(self):
69+
def raw_observation(self) -> Any:
6970
"""Returns the latest observation."""
7071

7172
def stats(self) -> dict[str, Any]:

android_env/wrappers/base_wrapper.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
class BaseWrapper(env_interface.AndroidEnvInterface):
3030
"""AndroidEnv wrapper."""
3131

32-
def __init__(self, env):
32+
def __init__(self, env: env_interface.AndroidEnvInterface) -> None:
3333
self._env = env
3434
logging.info('Wrapping with %s', self.__class__.__name__)
3535

@@ -96,29 +96,30 @@ def save_state(
9696
"""
9797
return self._env.save_state(request)
9898

99-
def execute_adb_call(self,
100-
adb_call: adb_pb2.AdbRequest) -> adb_pb2.AdbResponse:
99+
def execute_adb_call(
100+
self, adb_call: adb_pb2.AdbRequest
101+
) -> adb_pb2.AdbResponse:
101102
return self._env.execute_adb_call(adb_call)
102103

103104
@property
104-
def raw_action(self):
105+
def raw_action(self) -> Any:
105106
return self._env.raw_action
106107

107108
@property
108-
def raw_observation(self):
109+
def raw_observation(self) -> Any:
109110
return self._env.raw_observation
110111

111112
@property
112-
def raw_env(self):
113+
def raw_env(self) -> env_interface.AndroidEnvInterface:
113114
"""Recursively unwrap until we reach the true 'raw' env."""
114115
wrapped = self._env
115116
if hasattr(wrapped, 'raw_env'):
116117
return wrapped.raw_env
117118
return wrapped
118119

119-
def __getattr__(self, attr):
120+
def __getattr__(self, attr) -> Any:
120121
"""Delegate attribute access to underlying environment."""
121122
return getattr(self._env, attr)
122123

123-
def close(self):
124+
def close(self) -> None:
124125
self._env.close()

android_env/wrappers/base_wrapper_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def test_multiple_wrappers(self):
101101
base_env.close.assert_called_once()
102102

103103
def test_raw_env(self):
104-
base_env = 'fake_env'
104+
base_env = mock.create_autospec(env_interface.AndroidEnvInterface)
105105
wrapped_env_1 = base_wrapper.BaseWrapper(base_env)
106106
wrapped_env_2 = base_wrapper.BaseWrapper(wrapped_env_1)
107107
self.assertEqual(base_env, wrapped_env_2.raw_env)

android_env/wrappers/discrete_action_wrapper.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"""Wraps the AndroidEnv environment to provide discrete actions."""
1717

1818
from collections.abc import Sequence
19+
from typing import cast
1920

21+
from android_env import env_interface
2022
from android_env.components import action_type
2123
from android_env.wrappers import base_wrapper
2224
import dm_env
@@ -32,21 +34,24 @@ class DiscreteActionWrapper(base_wrapper.BaseWrapper):
3234

3335
def __init__(
3436
self,
35-
env: dm_env.Environment,
37+
env: env_interface.AndroidEnvInterface,
3638
action_grid: Sequence[int] = (10, 10),
3739
redundant_actions: bool = True,
3840
noise: float = 0.1,
39-
):
41+
) -> None:
4042
super().__init__(env)
4143
self._parent_action_spec = self._env.action_spec()
4244
self._assert_base_env()
4345
self._action_grid = action_grid # [height, width]
4446
self._grid_size = np.prod(self._action_grid)
45-
self._num_action_types = self._parent_action_spec['action_type'].num_values
47+
action_types = cast(
48+
specs.DiscreteArray, self._parent_action_spec['action_type']
49+
)
50+
self._num_action_types = action_types.num_values
4651
self._redundant_actions = redundant_actions
4752
self._noise = noise
4853

49-
def _assert_base_env(self):
54+
def _assert_base_env(self) -> None:
5055
"""Checks that the wrapped env has the right action spec format."""
5156

5257
assert len(self._parent_action_spec) == 2
@@ -142,8 +147,12 @@ def _get_touch_position(self, action_id: int) -> Sequence[float]:
142147

143148
# Project action space to action_spec ranges. For the default case of
144149
# minimum = [0, 0] and maximum = [1, 1], this will not do anything.
145-
x_min, y_min = self._parent_action_spec['touch_position'].minimum
146-
x_max, y_max = self._parent_action_spec['touch_position'].maximum
150+
x_min, y_min = cast(
151+
specs.BoundedArray, self._parent_action_spec['touch_position']
152+
).minimum
153+
x_max, y_max = cast(
154+
specs.BoundedArray, self._parent_action_spec['touch_position']
155+
).maximum
147156

148157
x_pos = x_min + x_pos * (x_max - x_min)
149158
y_pos = y_min + y_pos * (y_max - y_min)

android_env/wrappers/flat_interface_wrapper.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515

1616
"""Wraps the AndroidEnv environment to make its interface flat."""
1717

18-
from typing import Any
18+
from typing import Any, cast
1919

20+
from android_env import env_interface
2021
from android_env.wrappers import base_wrapper
2122
import dm_env
2223
from dm_env import specs
@@ -25,15 +26,17 @@
2526
RGB_CHANNELS = (0, 1, 2)
2627

2728

28-
def _extract_screen_pixels(obs: np.ndarray):
29+
def _extract_screen_pixels(obs: np.ndarray) -> np.ndarray:
2930
"""Get only screen pixels by removing previous action layer."""
3031
is_grayscale_image = obs.shape[-1] == 2
3132
if is_grayscale_image:
3233
return np.expand_dims(obs[..., 0], -1)
3334
return obs[..., RGB_CHANNELS]
3435

3536

36-
def _get_no_action_observation_spec(obs_spec: specs.BoundedArray):
37+
def _get_no_action_observation_spec(
38+
obs_spec: specs.BoundedArray,
39+
) -> specs.BoundedArray:
3740
"""Create an observation spec without the action layer."""
3841
shape = np.array(obs_spec.shape)
3942
shape[2] -= 1
@@ -56,25 +59,29 @@ class FlatInterfaceWrapper(base_wrapper.BaseWrapper):
5659
space.
5760
"""
5861

59-
def __init__(self,
60-
env: dm_env.Environment,
61-
flat_actions: bool = True,
62-
flat_observations: bool = True,
63-
keep_action_layer: bool = True):
62+
def __init__(
63+
self,
64+
env: env_interface.AndroidEnvInterface,
65+
flat_actions: bool = True,
66+
flat_observations: bool = True,
67+
keep_action_layer: bool = True,
68+
) -> None:
6469
super().__init__(env)
6570
self._flat_actions = flat_actions
6671
self._flat_observations = flat_observations
6772
self._keep_action_layer = keep_action_layer
6873
self._action_name = list(self._env.action_spec())[0]
6974
self._assert_base_env()
7075

71-
def _assert_base_env(self):
76+
def _assert_base_env(self) -> None:
7277
base_action_spec = self._env.action_spec()
7378
assert len(base_action_spec) == 1, self._env.action_spec()
7479
assert isinstance(base_action_spec, dict)
7580
assert isinstance(base_action_spec[self._action_name], specs.BoundedArray)
7681

77-
def _process_action(self, action: int | np.ndarray | dict[str, Any]):
82+
def _process_action(
83+
self, action: int | np.ndarray | dict[str, Any]
84+
) -> int | np.ndarray | dict[str, Any]:
7885
if self._flat_actions:
7986
return {self._action_name: action}
8087
else:
@@ -85,13 +92,15 @@ def _process_timestep(self, timestep: dm_env.TimeStep) -> dm_env.TimeStep:
8592
step_type, reward, discount, observation = timestep
8693
# Keep only the pixels.
8794
pixels = observation['pixels']
88-
pixels = pixels if self._keep_action_layer else _extract_screen_pixels(
89-
pixels)
95+
pixels = (
96+
pixels if self._keep_action_layer else _extract_screen_pixels(pixels)
97+
)
9098
return dm_env.TimeStep(
9199
step_type=step_type,
92100
reward=reward,
93101
discount=discount,
94-
observation=pixels)
102+
observation=pixels,
103+
)
95104
else:
96105
return timestep
97106

@@ -105,7 +114,9 @@ def step(self, action: int) -> dm_env.TimeStep:
105114

106115
def observation_spec(self) -> specs.Array | dict[str, specs.Array]: # pytype: disable=signature-mismatch # overriding-return-type-checks
107116
if self._flat_observations:
108-
pixels_spec = self._env.observation_spec()['pixels']
117+
pixels_spec = cast(
118+
specs.BoundedArray, self._env.observation_spec()['pixels']
119+
)
109120
if not self._keep_action_layer:
110121
return _get_no_action_observation_spec(pixels_spec)
111122
return pixels_spec
@@ -114,6 +125,6 @@ def observation_spec(self) -> specs.Array | dict[str, specs.Array]: # pytype: d
114125

115126
def action_spec(self) -> specs.BoundedArray | dict[str, specs.Array]: # pytype: disable=signature-mismatch # overriding-return-type-checks
116127
if self._flat_actions:
117-
return self._env.action_spec()[self._action_name]
128+
return self._env.action_spec()[self._action_name] # pytype: disable=bad-return-type
118129
else:
119130
return self._env.action_spec()

android_env/wrappers/float_pixels_wrapper.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
"""Converts pixel observation to from int to float32 between 0.0 and 1.0."""
1717

18+
from android_env import env_interface
1819
from android_env.components import pixel_fns
1920
from android_env.wrappers import base_wrapper
2021
import dm_env
@@ -25,7 +26,7 @@
2526
class FloatPixelsWrapper(base_wrapper.BaseWrapper):
2627
"""Wraps AndroidEnv for Panultimate agent."""
2728

28-
def __init__(self, env: dm_env.Environment):
29+
def __init__(self, env: env_interface.AndroidEnvInterface) -> None:
2930
super().__init__(env)
3031
self._input_spec = self._env.observation_spec()['pixels']
3132
self._should_convert_int_to_float = np.issubdtype(self._input_spec.dtype,

0 commit comments

Comments
 (0)