Coverage for /builds/BuildGrid/buildgrid/buildgrid/server/actioncache/caches/with_cache.py: 78.26%

46 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2025-03-13 15:36 +0000

1# Copyright (C) 2020 Bloomberg LP 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# <http://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, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15 

16from contextlib import ExitStack 

17 

18from buildgrid._protos.build.bazel.remote.execution.v2.remote_execution_pb2 import ActionResult, Digest 

19from buildgrid.server.actioncache.caches.action_cache_abc import ActionCacheABC 

20from buildgrid.server.exceptions import NotFoundError 

21from buildgrid.server.logging import buildgrid_logger 

22 

23LOGGER = buildgrid_logger(__name__) 

24 

25 

26class WithCacheActionCache(ActionCacheABC): 

27 """ 

28 An ActionCache that first makes use of a storage and fallback cache 

29 

30 """ 

31 

32 def __init__( 

33 self, cache: ActionCacheABC, fallback: ActionCacheABC, allow_updates: bool, cache_failed_actions: bool 

34 ) -> None: 

35 """Initialise a new Action Cache with a fallback cache. 

36 

37 Args: 

38 cache (ActionCacheABC): Local cache backend instance to be used. 

39 fallback(ActionCacheABC): Fallback backend instance to be used 

40 allow_updates(bool): Allow updates pushed to the Action Cache. 

41 Defaults to ``True``. 

42 cache_failed_actions(bool): Whether to store failed (non-zero exit 

43 code) actions. Default to ``True``. 

44 """ 

45 super().__init__(allow_updates=allow_updates) 

46 self._stack = ExitStack() 

47 self._cache_failed_actions = cache_failed_actions 

48 self._cache = cache 

49 self._fallback = fallback 

50 

51 def start(self) -> None: 

52 self._stack.enter_context(self._cache) 

53 self._stack.enter_context(self._fallback) 

54 

55 def stop(self) -> None: 

56 self._stack.close() 

57 

58 def get_action_result(self, action_digest: Digest) -> ActionResult: 

59 """Retrieves the cached result for an Action. 

60 

61 Will first attempt to retrieve result from cache and then fallback. A 

62 NotFoundError is raised if both cache and fallback return None. 

63 

64 Args: 

65 action_digest (Digest): The digest of the Action to retrieve the 

66 cached result of. 

67 

68 """ 

69 cache_result = None 

70 fallback_result = None 

71 key = self._get_key(action_digest) 

72 try: 

73 cache_result = self._cache.get_action_result(action_digest) 

74 except NotFoundError: 

75 pass 

76 except Exception: 

77 LOGGER.exception("Unexpected error in cache get_action_result.", tags=dict(digest=action_digest)) 

78 

79 if cache_result is None: 

80 fallback_result = self._fallback.get_action_result(action_digest) 

81 else: 

82 return cache_result 

83 

84 if fallback_result is not None: 

85 self._cache.update_action_result(action_digest, fallback_result) 

86 return fallback_result 

87 

88 raise NotFoundError(f"Key not found: {key}") 

89 

90 def update_action_result(self, action_digest: Digest, action_result: ActionResult) -> None: 

91 """Stores a result for an Action in the cache. 

92 

93 Will attempt to store result in cache, and then in fallback cache. If 

94 the result has a non-zero exit code and `cache_failed_actions` is False 

95 for this cache, the result is not cached. 

96 

97 Args: 

98 action_digest (Digest): The digest of the Action whose result is 

99 being cached. 

100 action_result (ActionResult): The result to cache for the given 

101 Action digest. 

102 

103 """ 

104 if self._cache_failed_actions or action_result.exit_code == 0: 

105 if not self._allow_updates: 

106 raise NotImplementedError("Updating cache not allowed") 

107 

108 try: 

109 self._cache.update_action_result(action_digest, action_result) 

110 

111 except Exception: 

112 LOGGER.warning("Failed to cache action.", tags=dict(digest=action_digest), exc_info=True) 

113 

114 self._fallback.update_action_result(action_digest, action_result) 

115 

116 def _get_key(self, action_digest: Digest) -> tuple[str, int]: 

117 """Get a hashable cache key from a given Action digest. 

118 

119 Args: 

120 action_digest (Digest): The digest to produce a cache key for. 

121 

122 """ 

123 return (action_digest.hash, action_digest.size_bytes)