Skip to content

Commit e4cb0c0

Browse files
authored
Keep track of proposal state, used by reconciler, only in RAM. (#1324)
This PR updates the `ReconcilerState` class to use an in-memory dictionary for state storage instead of file operations. This change simplifies initialization and eliminates file system interactions, improving performance and reliability. The main effect of this change is to make the reconciler retry proposals that formerly failed to be submitted *immediately after restart*, rather than (as is right now) waiting for a configured retry duration. The configured retry duration is still respected during the execution of the reconciler.
1 parent 481bd24 commit e4cb0c0

5 files changed

Lines changed: 19 additions & 45 deletions

File tree

release-controller/README.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,16 +192,6 @@ bazel run //release-controller:release-controller \
192192
-- --dry-run --verbose
193193
```
194194

195-
Custom path for the reconciler state?
196-
197-
198-
```sh
199-
export RECONCILER_STATE_DIR=/tmp/dryrun/reconciler-state
200-
bazel run //release-controller:release-controller \
201-
--action_env=RECONCILER_STATE_DIR \
202-
-- --dry-run --verbose
203-
```
204-
205195
Typing errors preventing you from running it, because you are editing code and
206196
testing your changes? Add `--output_groups=-mypy` right after `bazel run`.
207197

release-controller/reconciler.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
SecurityReleaseNotesRequest,
3636
OrdinaryReleaseNotesRequest,
3737
)
38-
from util import version_name, release_controller_cache_directory
38+
from util import version_name
3939
from watchdog import Watchdog
4040

4141

@@ -540,9 +540,6 @@ def main() -> None:
540540
else dryrun.DRECli()
541541
)
542542
state = reconciler_state.ReconcilerState(
543-
pathlib.Path(
544-
os.environ.get("RECONCILER_STATE_DIR", release_controller_cache_directory())
545-
),
546543
None if skip_preloading_state else dre.get_election_proposals_by_version,
547544
)
548545
slack_announcer = (

release-controller/reconciler_state.py

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import datetime
22
import logging
3-
import pathlib
4-
import os
3+
import time
54
import typing
65
import dre_cli
76

@@ -73,7 +72,6 @@ class ReconcilerState:
7372

7473
def __init__(
7574
self,
76-
path: pathlib.Path,
7775
known_proposal_retriever: typing.Callable[
7876
[], dict[str, dre_cli.ElectionProposal]
7977
]
@@ -85,8 +83,12 @@ def __init__(
8583
If specified, every proposal mentioned in the known_proposals list will be
8684
recorded to the state database as existing during initialization.
8785
"""
88-
os.makedirs(path, exist_ok=True)
89-
self.path = path
86+
self.state: dict[
87+
str,
88+
tuple[typing.Literal["submitted"], float, int]
89+
| tuple[typing.Literal["malfunction"], float],
90+
] = {}
91+
9092
self._logger = logging.getLogger(self.__class__.__name__)
9193
if known_proposal_retriever:
9294
for replica_version, proposal in known_proposal_retriever().items():
@@ -99,34 +101,28 @@ def __init__(
99101
)
100102
p.record_submission(proposal["id"])
101103

102-
def _version_path(self, version: str) -> pathlib.Path:
103-
return self.path / version
104-
105104
def version_proposal(
106105
self, version: str
107106
) -> NoProposal | SubmittedProposal | DREMalfunction:
108107
"""Get the proposal ID for the given version. If the version has not been submitted, return None."""
109-
version_file = self._version_path(version)
110-
if not version_file.exists():
108+
res = self.state.get(version)
109+
if res is None:
111110
return NoProposal(version, self)
112-
with open(version_file, encoding="utf8") as vf:
113-
try:
114-
return SubmittedProposal(version, self, int(vf.read()))
115-
except ValueError:
116-
return DREMalfunction(version, self)
111+
elif isinstance(res, tuple) and res[0] == "malfunction":
112+
return DREMalfunction(version, self)
113+
else:
114+
return SubmittedProposal(version, self, res[2])
117115

118116
def _get_proposal_age(self, version: str) -> datetime.datetime:
119-
return datetime.datetime.fromtimestamp(
120-
os.path.getmtime(self._version_path(version))
121-
)
117+
state = self.state[version]
118+
return datetime.datetime.fromtimestamp(state[1])
122119

123120
def _record_malfunction(self, version: str) -> DREMalfunction:
124121
"""Mark a proposal as submitted."""
125-
self._version_path(version).touch()
122+
self.state[version] = ("malfunction", time.time())
126123
return DREMalfunction(version, self)
127124

128125
def _record_proposal_id(self, version: str, proposal_id: int) -> SubmittedProposal:
129126
"""Save the proposal ID for the given version."""
130-
with open(self._version_path(version), "w", encoding="utf8") as f:
131-
f.write(str(proposal_id))
127+
self.state[version] = ("submitted", time.time(), proposal_id)
132128
return SubmittedProposal(version, self, proposal_id)

release-controller/tests/test_reconciler.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@ class AmnesiacReconcilerState(ReconcilerState):
2424
"""Reconciler state that uses a temporary directory for storage."""
2525

2626
def __init__(self) -> None:
27-
self.tempdir = tempfile.TemporaryDirectory()
28-
super().__init__(pathlib.Path(self.tempdir.name))
29-
30-
def __del__(self) -> None:
31-
self.tempdir.cleanup()
27+
super().__init__()
3228

3329

3430
class MockActiveVersionProvider(object):

release-controller/util.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import hashlib
33
import math
44
import os
5-
import pathlib
65
import sys
76
import time
87
import typing
@@ -28,10 +27,6 @@ def resolve_binary(name: str) -> str:
2827
return name
2928

3029

31-
def release_controller_cache_directory() -> pathlib.Path:
32-
return pathlib.Path.home() / ".cache" / "release-controller"
33-
34-
3530
T = typing.TypeVar("T")
3631

3732

0 commit comments

Comments
 (0)