blob: c9a65648b5185b914e4b437b533c5d3bdfe890e9 [file] [log] [blame]
Rob Mohr8d13e252022-09-27 20:01:44 +00001# Copyright 2022 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7# https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Ensure the given call completes before swarming kills the build."""
15
Rob Mohr758a4722024-05-08 22:03:16 +000016from __future__ import annotations
17
Rob Mohr8d13e252022-09-27 20:01:44 +000018import contextlib
19import dataclasses
Rob Mohr86531542024-08-13 19:33:21 +000020from typing import TYPE_CHECKING
Rob Mohr8d13e252022-09-27 20:01:44 +000021
22from recipe_engine import recipe_api
Rob Mohr8d13e252022-09-27 20:01:44 +000023from RECIPE_MODULES.fuchsia.utils import nice_duration
24
Rob Mohr86531542024-08-13 19:33:21 +000025if TYPE_CHECKING: # pragma: no cover
26 from typing import Generator
27
Rob Mohr8d13e252022-09-27 20:01:44 +000028
29class TimeoutApi(recipe_api.RecipeApi):
30 """Ensure the given call completes before swarming kills the build."""
31
32 @contextlib.contextmanager
Rob Mohr0b9859a2024-03-06 23:25:38 +000033 def __call__(self, buffer_sec: float = 120) -> Generator[None, None, None]:
Rob Mohr7ff94c02023-11-20 17:01:28 +000034 current_time_sec: float = self.m.time.time()
Rob Mohr8d13e252022-09-27 20:01:44 +000035
Rob Mohr52bd0392024-03-25 20:43:10 +000036 # Amount of time elapsed in the build.
Rob Mohr7ff94c02023-11-20 17:01:28 +000037 elapsed_time_sec: float = (
Rob Mohrd6071192023-06-15 22:17:01 +000038 current_time_sec - self.m.buildbucket.build.start_time.seconds
Rob Mohr8d13e252022-09-27 20:01:44 +000039 )
40
Rob Mohr52bd0392024-03-25 20:43:10 +000041 # Amount of time before the build times out.
Rob Mohr7ff94c02023-11-20 17:01:28 +000042 time_remaining_sec: float = (
Rob Mohrd6071192023-06-15 22:17:01 +000043 self.m.buildbucket.build.execution_timeout.seconds
44 - elapsed_time_sec
Rob Mohr8d13e252022-09-27 20:01:44 +000045 )
46
47 # Give a buffer before build times out and kill this step then. This
48 # should give enough time to read any logfiles and maybe upload to
Rob Mohrd6071192023-06-15 22:17:01 +000049 # GCS before the build times out.
Rob Mohr52bd0392024-03-25 20:43:10 +000050 timeout_sec: float = max(time_remaining_sec - buffer_sec, 10.0)
Rob Mohr8d13e252022-09-27 20:01:44 +000051
Rob Mohr52bd0392024-03-25 20:43:10 +000052 # Timing often looks weird in recipe unit tests. If the amount of time
53 # remaining before the build times out is a negative number, ignore it
54 # and proceed as if everything is internally consistent.
55 if self._test_data.enabled and time_remaining_sec < 0:
56 time_remaining_sec = timeout_sec = 10.0
57
58 # Don't start steps if there's less than 10s remaining. A step timing
59 # out looks like an infra failure which we don't want.
60 if time_remaining_sec < 10.0:
61 self.m.step.empty(
62 'timed out',
63 status=self.m.step.FAILURE,
64 step_text=(
65 'The build has too little time remaining, not starting new '
66 f'steps. {time_remaining_sec}s remaining.'
67 ),
68 )
Rob Mohr8d13e252022-09-27 20:01:44 +000069
Rob Mohrd6071192023-06-15 22:17:01 +000070 with self.m.step.nest(f'timeout {nice_duration(timeout_sec)}'):
Rob Mohradee1d42023-03-28 21:30:26 +000071 pass
Rob Mohr8d13e252022-09-27 20:01:44 +000072
Rob Mohr8d13e252022-09-27 20:01:44 +000073 try:
Rob Mohr686cf712023-06-15 22:19:09 +000074 with self.m.time.timeout(timeout_sec):
Rob Mohr8d13e252022-09-27 20:01:44 +000075 yield
76
77 finally:
78 pass