2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

[Bug] Fix for create_child_builds (#8399)

* Fix for create_child_builds

- Account for concurrency between multiple worker processes
- Ensure db commits are atomic
- Add random delays between build creation

* Check for existing build order

* Initially force  task off to background worker

* Revert force_async change
This commit is contained in:
Oliver 2024-10-31 13:59:53 +11:00 committed by GitHub
parent feefa60a8f
commit 913a05cf45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 47 additions and 20 deletions

View File

@ -180,6 +180,7 @@ class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTre
return reference return reference
@transaction.atomic
def create(self, validated_data): def create(self, validated_data):
"""Save the Build object.""" """Save the Build object."""
@ -192,7 +193,7 @@ class BuildSerializer(NotesFieldMixin, DataImportExportSerializerMixin, InvenTre
InvenTree.tasks.offload_task( InvenTree.tasks.offload_task(
build.tasks.create_child_builds, build.tasks.create_child_builds,
build_order.pk, build_order.pk,
group='build', group='build'
) )
return build_order return build_order

View File

@ -1,11 +1,15 @@
"""Background task definitions for the BuildOrder app.""" """Background task definitions for the BuildOrder app."""
import logging import logging
import random
import time
from datetime import timedelta from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.db import transaction
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from allauth.account.models import EmailAddress from allauth.account.models import EmailAddress
@ -198,9 +202,28 @@ def create_child_builds(build_id: int) -> None:
assembly_items = build_order.part.get_bom_items().filter(sub_part__assembly=True) assembly_items = build_order.part.get_bom_items().filter(sub_part__assembly=True)
# Random delay, to reduce likelihood of race conditions from multiple build orders being created simultaneously
time.sleep(random.random())
with transaction.atomic():
# Atomic transaction to ensure that all child build orders are created together, or not at all
# This is critical to prevent duplicate child build orders being created (e.g. if the task is re-run)
sub_build_ids = []
for item in assembly_items: for item in assembly_items:
quantity = item.quantity * build_order.quantity quantity = item.quantity * build_order.quantity
# Check if the child build order has already been created
if build_models.Build.objects.filter(
part=item.sub_part,
parent=build_order,
quantity=quantity,
status__in=BuildStatusGroups.ACTIVE_CODES
).exists():
continue
sub_order = build_models.Build.objects.create( sub_order = build_models.Build.objects.create(
part=item.sub_part, part=item.sub_part,
quantity=quantity, quantity=quantity,
@ -213,10 +236,13 @@ def create_child_builds(build_id: int) -> None:
responsible=build_order.responsible, responsible=build_order.responsible,
) )
sub_build_ids.append(sub_order.pk)
for pk in sub_build_ids:
# Offload the child build order creation to the background task queue # Offload the child build order creation to the background task queue
InvenTree.tasks.offload_task( InvenTree.tasks.offload_task(
create_child_builds, create_child_builds,
sub_order.pk, pk,
group='build' group='build'
) )