Skip to content

Commit 0747dc3

Browse files
[AI Generated] Wait for apt/dpkg frontend lock holders in Debian package wait (#4556)
1 parent 368b589 commit 0747dc3

1 file changed

Lines changed: 60 additions & 3 deletions

File tree

lisa/operating_system.py

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,57 @@ def add_azure_core_repo(
976976
keys_location=keys,
977977
)
978978

979+
def _is_package_lock_held(self) -> bool:
980+
# `pidof dpkg dpkg-deb` alone misses holders such as
981+
# `unattended-upgrades` and `apt-get`, which hold the apt/dpkg
982+
# frontend lock without a running `dpkg` process. Check the lock
983+
# files directly so we wait for those holders to finish too.
984+
lock_files = " ".join(
985+
[
986+
"/var/lib/dpkg/lock-frontend",
987+
"/var/lib/dpkg/lock",
988+
"/var/lib/apt/lists/lock",
989+
"/var/cache/apt/archives/lock",
990+
]
991+
)
992+
# `fuser` exits 0 if at least one listed file is opened by a process,
993+
# and non-zero otherwise (including 127 if `fuser` is unavailable, in
994+
# which case we fall back to the `pidof` check below).
995+
fuser_result = self._node.execute(
996+
f"fuser {lock_files}",
997+
sudo=True,
998+
shell=True,
999+
no_info_log=True,
1000+
)
1001+
return fuser_result.exit_code == 0
1002+
1003+
def _disable_unattended_upgrades(self) -> None:
1004+
# Background apt jobs grab the dpkg frontend lock at boot, making the
1005+
# first package install fail with
1006+
# "Could not get lock /var/lib/dpkg/lock-frontend ... (unattended-upgr)".
1007+
# Mask these units so they cannot run now or after a reboot. `mask
1008+
# --now` is a systemd operation that also stops any in-flight run, and
1009+
# it does not need the dpkg lock, so it works even mid-upgrade. The
1010+
# `unattended-upgrade` binary stays installed, so tests that invoke it
1011+
# explicitly (e.g. the upgrade_packages transformer) are unaffected.
1012+
units = " ".join(
1013+
[
1014+
"apt-daily.timer",
1015+
"apt-daily.service",
1016+
"apt-daily-upgrade.timer",
1017+
"apt-daily-upgrade.service",
1018+
"unattended-upgrades.service",
1019+
]
1020+
)
1021+
# Best effort: never fail provisioning if a unit is missing on the image.
1022+
self._node.execute(
1023+
f"systemctl mask --now {units}",
1024+
sudo=True,
1025+
shell=True,
1026+
timeout=120,
1027+
no_error_log=True,
1028+
)
1029+
9791030
def wait_running_package_process(self) -> None:
9801031
is_first_time: bool = True
9811032
# wait for 10 minutes
@@ -985,12 +1036,15 @@ def wait_running_package_process(self) -> None:
9851036
# fix the dpkg, in case it's broken.
9861037
self._node.execute("dpkg --force-all --configure -a", sudo=True)
9871038
pidof_result = self._node.execute("pidof dpkg dpkg-deb")
988-
if pidof_result.exit_code == 1:
989-
# no dpkg process running, safe to exit and attempt repair.
1039+
dpkg_running = pidof_result.exit_code == 0
1040+
lock_held = self._is_package_lock_held()
1041+
if not dpkg_running and not lock_held:
1042+
# no dpkg process and no apt/dpkg lock holder; safe to exit
1043+
# and attempt repair.
9901044
break
9911045
if is_first_time:
9921046
is_first_time = False
993-
self._log.debug("found system dpkg process, waiting it...")
1047+
self._log.debug("found running package process, waiting it...")
9941048
time.sleep(1)
9951049

9961050
if timeout < timer.elapsed():
@@ -1122,6 +1176,9 @@ def is_end_of_life_release(self) -> bool:
11221176
skipped_exceptions=[ReleaseEndOfLifeException, RepoNotExistException],
11231177
)
11241178
def _initialize_package_installation(self) -> None:
1179+
# disable background apt jobs that grab the dpkg lock at boot, so the
1180+
# first install does not race with unattended-upgrades.
1181+
self._disable_unattended_upgrades()
11251182
# wait running system package process.
11261183
self.wait_running_package_process()
11271184
result = self._node.execute("apt-get update", sudo=True, timeout=1800)

0 commit comments

Comments
 (0)