Add locking to prevent apt locking issues from concurrent apt ops

This commit is contained in:
Olivier 'reivilibre' 2020-11-08 20:35:28 +00:00
parent f17b17d02a
commit 55e75f3faf

View File

@ -17,7 +17,10 @@
import asyncio import asyncio
import logging import logging
from typing import List import random
from asyncio import Lock
from collections import defaultdict
from typing import List, Tuple, Dict
from scone.default.utensils.basic_utensils import SimpleExec from scone.default.utensils.basic_utensils import SimpleExec
from scone.head.head import Head from scone.head.head import Head
@ -114,6 +117,10 @@ logger = logging.getLogger(__name__)
# ) # )
# (id of Kitchen, sous name) → Lock
apt_locks: Dict[Tuple[int, str], Lock] = defaultdict(Lock)
class AptPackage(Recipe): class AptPackage(Recipe):
_NAME = "apt-install" _NAME = "apt-install"
@ -135,6 +142,8 @@ class AptPackage(Recipe):
while retries > 0: while retries > 0:
result = await kitchen.ut1areq(SimpleExec(args, "/"), SimpleExec.Result) result = await kitchen.ut1areq(SimpleExec(args, "/"), SimpleExec.Result)
logger.debug("E %r: %r", args, result.stderr)
if result.exit_code == 0 or b"/lock" not in result.stderr: if result.exit_code == 0 or b"/lock" not in result.stderr:
return result return result
@ -142,8 +151,6 @@ class AptPackage(Recipe):
"Failed apt command due to suspected locking issue. Will retry…" "Failed apt command due to suspected locking issue. Will retry…"
) )
retries -= 1
# /lock seen in stderr, probably a locking issue... # /lock seen in stderr, probably a locking issue...
lock_check = await kitchen.ut1areq( lock_check = await kitchen.ut1areq(
SimpleExec( SimpleExec(
@ -160,7 +167,8 @@ class AptPackage(Recipe):
) )
retries -= 1 retries -= 1
await asyncio.sleep(2.0) sleep = 2.0 + 3.0 * random.random()
await asyncio.sleep(sleep)
return result # noqa return result # noqa
@ -168,7 +176,13 @@ class AptPackage(Recipe):
# this is a one-off task assuming everything works # this is a one-off task assuming everything works
kitchen.get_dependency_tracker() kitchen.get_dependency_tracker()
if self.packages: lock = apt_locks[(id(kitchen), self.recipe_context.sous)]
# we only want one apt task to run at once on each sous because they tend
# to race against each other and lock each other
async with lock:
if not self.packages:
return
update = await self._apt_command(kitchen, ["apt-get", "-yq", "update"]) update = await self._apt_command(kitchen, ["apt-get", "-yq", "update"])
if update.exit_code != 0: if update.exit_code != 0:
raise RuntimeError( raise RuntimeError(