Skip to content

Commit

Permalink
[Bug] Fix for create_child_builds (#8399)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
SchrodingersGat authored Oct 31, 2024
1 parent feefa60 commit 913a05c
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 22 deletions.
3 changes: 2 additions & 1 deletion src/backend/InvenTree/build/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ def validate_reference(self, reference):

return reference

@transaction.atomic
def create(self, validated_data):
"""Save the Build object."""

Expand All @@ -192,7 +193,7 @@ def create(self, validated_data):
InvenTree.tasks.offload_task(
build.tasks.create_child_builds,
build_order.pk,
group='build',
group='build'
)

return build_order
Expand Down
68 changes: 47 additions & 21 deletions src/backend/InvenTree/build/tasks.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""Background task definitions for the BuildOrder app."""

import logging
import random
import time

from datetime import timedelta
from decimal import Decimal

from django.contrib.auth.models import User
from django.template.loader import render_to_string
from django.db import transaction
from django.utils.translation import gettext_lazy as _

from allauth.account.models import EmailAddress
Expand Down Expand Up @@ -198,27 +202,49 @@ def create_child_builds(build_id: int) -> None:

assembly_items = build_order.part.get_bom_items().filter(sub_part__assembly=True)

for item in assembly_items:
quantity = item.quantity * build_order.quantity

sub_order = build_models.Build.objects.create(
part=item.sub_part,
quantity=quantity,
title=build_order.title,
batch=build_order.batch,
parent=build_order,
target_date=build_order.target_date,
sales_order=build_order.sales_order,
issued_by=build_order.issued_by,
responsible=build_order.responsible,
)

# Offload the child build order creation to the background task queue
InvenTree.tasks.offload_task(
create_child_builds,
sub_order.pk,
group='build'
)
# 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:
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(
part=item.sub_part,
quantity=quantity,
title=build_order.title,
batch=build_order.batch,
parent=build_order,
target_date=build_order.target_date,
sales_order=build_order.sales_order,
issued_by=build_order.issued_by,
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
InvenTree.tasks.offload_task(
create_child_builds,
pk,
group='build'
)


def notify_overdue_build_order(bo: build_models.Build):
Expand Down

0 comments on commit 913a05c

Please sign in to comment.