Skip to content

Commit 07657ae

Browse files
committed
fix(backend): lock on mutations for branches
We can enable locking for consistency on branches now that they are more granular and avoid performance penalty. Signed-off-by: Fatih Acar <[email protected]>
1 parent 818e670 commit 07657ae

File tree

6 files changed

+13
-54
lines changed

6 files changed

+13
-54
lines changed

backend/infrahub/core/node/create.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ async def create_node(
193193
preview_obj = await node_class.init(db=db, schema=schema, branch=branch)
194194
await preview_obj.new(db=db, process_pools=False, **data)
195195
schema_branch = db.schema.get_schema_branch(name=branch.name)
196-
lock_names = get_lock_names_on_object_mutation(node=preview_obj, branch=branch, schema_branch=schema_branch)
196+
lock_names = get_lock_names_on_object_mutation(node=preview_obj, schema_branch=schema_branch)
197197

198198
async def _persist(current_db: InfrahubDatabase) -> Node:
199199
node_constraint_runner = await component_registry.get_component(

backend/infrahub/core/node/save.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ async def run_constraints_and_save(
4141
locks = (
4242
list(lock_names)
4343
if lock_names is not None
44-
else get_lock_names_on_object_mutation(node=node, branch=branch, schema_branch=schema_branch)
44+
else get_lock_names_on_object_mutation(node=node, schema_branch=schema_branch)
4545
)
4646

4747
async def _persist() -> None:

backend/infrahub/graphql/mutations/ipam.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,7 @@ def __init_subclass_with_meta__(
107107
super().__init_subclass_with_meta__(_meta=_meta, **options)
108108

109109
@staticmethod
110-
def _get_lock_names(namespace_id: str, branch: Branch) -> list[str]:
111-
if not branch.is_default:
112-
# Do not lock on other branches as reconciliation will be performed at least when merging in main branch.
113-
return []
110+
def _get_lock_names(namespace_id: str) -> list[str]:
114111
return [build_object_lock_name(InfrahubKind.IPADDRESS + "_" + namespace_id)]
115112

116113
@classmethod
@@ -144,7 +141,7 @@ async def mutate_create(
144141
ip_address = ipaddress.ip_interface(data["address"]["value"])
145142
namespace_id = await validate_namespace(db=db, branch=branch, data=data)
146143

147-
lock_names = cls._get_lock_names(namespace_id, branch)
144+
lock_names = cls._get_lock_names(namespace_id)
148145
async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
149146
async with db.start_transaction() as dbt:
150147
reconciled_address = await cls._mutate_create_object_and_reconcile(
@@ -222,9 +219,9 @@ async def mutate_update(
222219
fields.remove(field_to_remove)
223220

224221
schema_branch = db.schema.get_schema_branch(name=branch.name)
225-
lock_names = get_lock_names_on_object_mutation(node=address, branch=branch, schema_branch=schema_branch)
222+
lock_names = get_lock_names_on_object_mutation(node=address, schema_branch=schema_branch)
226223

227-
namespace_lock_names = cls._get_lock_names(namespace_id, branch)
224+
namespace_lock_names = cls._get_lock_names(namespace_id)
228225
async with InfrahubMultiLock(lock_registry=lock.registry, locks=namespace_lock_names):
229226
async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
230227
async with db.start_transaction() as dbt:
@@ -399,7 +396,7 @@ async def mutate_update(
399396
fields.remove(field_to_remove)
400397

401398
schema_branch = db.schema.get_schema_branch(name=branch.name)
402-
lock_names = get_lock_names_on_object_mutation(node=prefix, branch=branch, schema_branch=schema_branch)
399+
lock_names = get_lock_names_on_object_mutation(node=prefix, schema_branch=schema_branch)
403400

404401
namespace_lock_names = cls._get_lock_names(namespace_id)
405402
async with InfrahubMultiLock(lock_registry=lock.registry, locks=namespace_lock_names):

backend/infrahub/graphql/mutations/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ async def _call_mutate_update(
230230
await preview_obj.from_graphql(db=db, data=data, process_pools=False)
231231

232232
schema_branch = db.schema.get_schema_branch(name=branch.name)
233-
lock_names = get_lock_names_on_object_mutation(node=preview_obj, branch=branch, schema_branch=schema_branch)
233+
lock_names = get_lock_names_on_object_mutation(node=preview_obj, schema_branch=schema_branch)
234234

235235
async def _mutate(current_db: InfrahubDatabase) -> tuple[Node, Self]:
236236
updated_obj = await cls.mutate_update_object(
@@ -332,7 +332,7 @@ async def mutate_update_object(
332332
locks = lock_names
333333
if locks is None:
334334
schema_branch = db.schema.get_schema_branch(name=branch.name)
335-
locks = get_lock_names_on_object_mutation(node=obj, branch=branch, schema_branch=schema_branch)
335+
locks = get_lock_names_on_object_mutation(node=obj, schema_branch=schema_branch)
336336

337337
await run_constraints_and_save(
338338
node=obj,

backend/infrahub/lock_getter.py

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import hashlib
22
from typing import TYPE_CHECKING
33

4-
from infrahub.core.branch import Branch
5-
from infrahub.core.constants import InfrahubKind
64
from infrahub.core.node import Node
75
from infrahub.core.schema import GenericSchema
86
from infrahub.core.schema.schema_branch import SchemaBranch
@@ -11,8 +9,6 @@
119
if TYPE_CHECKING:
1210
from infrahub.core.relationship import RelationshipManager
1311

14-
KINDS_CONCURRENT_MUTATIONS_NOT_ALLOWED = [InfrahubKind.GENERICGROUP]
15-
1612

1713
def _get_kinds_to_lock_on_object_mutation(kind: str, schema_branch: SchemaBranch) -> list[str]:
1814
"""
@@ -48,31 +44,13 @@ def _get_kinds_to_lock_on_object_mutation(kind: str, schema_branch: SchemaBranch
4844
return kinds
4945

5046

51-
def _should_kind_be_locked_on_any_branch(kind: str, schema_branch: SchemaBranch) -> bool:
52-
"""
53-
Check whether kind or any kind generic is in KINDS_TO_LOCK_ON_ANY_BRANCH.
54-
"""
55-
56-
if kind in KINDS_CONCURRENT_MUTATIONS_NOT_ALLOWED:
57-
return True
58-
59-
node_schema = schema_branch.get(name=kind)
60-
if isinstance(node_schema, GenericSchema):
61-
return False
62-
63-
for generic_kind in node_schema.inherit_from:
64-
if generic_kind in KINDS_CONCURRENT_MUTATIONS_NOT_ALLOWED:
65-
return True
66-
return False
67-
68-
6947
def _hash(value: str) -> str:
7048
# Do not use builtin `hash` for lock names as due to randomization results would differ between
7149
# different processes.
7250
return hashlib.sha256(value.encode()).hexdigest()
7351

7452

75-
def get_lock_names_on_object_mutation(node: Node, branch: Branch, schema_branch: SchemaBranch) -> list[str]:
53+
def get_lock_names_on_object_mutation(node: Node, schema_branch: SchemaBranch) -> list[str]:
7654
"""
7755
Return lock names for object on which we want to avoid concurrent mutation (create/update). Except for some specific kinds,
7856
concurrent mutations are only allowed on non-main branch as objects validations will be performed at least when merging in main branch.
@@ -99,9 +77,6 @@ def _add_pool_lock(pool_id: str) -> None:
9977
if rel.from_pool and "id" in rel.from_pool:
10078
_add_pool_lock(rel.from_pool["id"])
10179

102-
if not branch.is_default and not _should_kind_be_locked_on_any_branch(node.get_kind(), schema_branch):
103-
return lock_names
104-
10580
lock_kinds = _get_kinds_to_lock_on_object_mutation(node.get_kind(), schema_branch)
10681
for kind in lock_kinds:
10782
schema = schema_branch.get(name=kind)

backend/tests/unit/core/test_get_kinds_lock.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from infrahub.lock_getter import (
77
_get_kinds_to_lock_on_object_mutation,
88
_hash,
9-
_should_kind_be_locked_on_any_branch,
109
get_lock_names_on_object_mutation,
1110
)
1211
from tests.helpers.test_app import TestInfrahubApp
@@ -48,19 +47,7 @@ async def test_lock_other_branch(
4847
schema_branch = registry.schema.get_schema_branch(name=other_branch.name)
4948

5049
person = await create_and_save(db=db, schema="TestPerson", name="John", branch=other_branch)
51-
assert get_lock_names_on_object_mutation(person, branch=other_branch, schema_branch=schema_branch) == []
52-
53-
async def test_lock_groups_on_other_branches(
54-
self,
55-
db: InfrahubDatabase,
56-
default_branch,
57-
client,
58-
register_core_models_schema,
59-
):
60-
other_branch = await create_branch(branch_name="other_branch", db=db)
61-
schema_branch = registry.schema.get_schema_branch(name=other_branch.name)
62-
63-
assert _should_kind_be_locked_on_any_branch(kind="CoreGraphQLQueryGroup", schema_branch=schema_branch) is True
50+
assert get_lock_names_on_object_mutation(person, schema_branch=schema_branch) == ["global.object.TestPerson." + _hash("John")]
6451

6552
async def test_lock_names_only_attributes(
6653
self,
@@ -78,7 +65,7 @@ async def test_lock_names_only_attributes(
7865
schema_branch = registry.schema.get_schema_branch(name=default_branch.name)
7966
person = await create_and_save(db=db, schema="TestPerson", name="John")
8067
car = await create_and_save(db=db, schema="TestCar", name="mercedes", color="blue", owner=person)
81-
assert get_lock_names_on_object_mutation(car, branch=default_branch, schema_branch=schema_branch) == [
68+
assert get_lock_names_on_object_mutation(car, schema_branch=schema_branch) == [
8269
"global.object.TestCar." + _hash("mercedes") + "." + _hash("blue")
8370
]
8471

@@ -95,6 +82,6 @@ async def test_lock_names_optional_empty_attribute(
9582

9683
schema_branch = registry.schema.get_schema_branch(name=default_branch.name)
9784
person = await create_and_save(db=db, schema="TestPerson", name="John")
98-
assert get_lock_names_on_object_mutation(person, branch=default_branch, schema_branch=schema_branch) == [
85+
assert get_lock_names_on_object_mutation(person, schema_branch=schema_branch) == [
9986
"global.object.TestPerson." + _hash("") + "." + _hash("John")
10087
]

0 commit comments

Comments
 (0)