Skip to content

Commit 8ad40f3

Browse files
chore(launchpad): make artifact upload task idempotent (#94725)
<!-- Describe your PR here. --> <!-- Sentry employees and contractors can delete or ignore the following. --> ### Legal Boilerplate Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.
1 parent e1d02a4 commit 8ad40f3

File tree

1 file changed

+44
-14
lines changed

1 file changed

+44
-14
lines changed

src/sentry/preprod/tasks.py

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def assemble_preprod_artifact(
3939
"""
4040
Creates a preprod artifact from uploaded chunks.
4141
"""
42+
from sentry.models.files.file import File
4243
from sentry.preprod.models import PreprodArtifact, PreprodBuildConfiguration
4344

4445
logger.info(
@@ -47,6 +48,7 @@ def assemble_preprod_artifact(
4748
"timestamp": datetime.datetime.now().isoformat(),
4849
"project_id": project_id,
4950
"organization_id": org_id,
51+
"checksum": checksum,
5052
},
5153
)
5254

@@ -55,23 +57,49 @@ def assemble_preprod_artifact(
5557
project = Project.objects.get(id=project_id, organization=organization)
5658
bind_organization_context(organization)
5759

58-
set_assemble_status(
59-
AssembleTask.PREPROD_ARTIFACT, project_id, checksum, ChunkFileState.ASSEMBLING
60-
)
60+
with transaction.atomic(router.db_for_write(PreprodArtifact)):
61+
# First check if there's already a file with this checksum and type
62+
existing_file = File.objects.filter(checksum=checksum, type="preprod.artifact").first()
6163

62-
assemble_result = assemble_file(
63-
task=AssembleTask.PREPROD_ARTIFACT,
64-
org_or_project=project,
65-
name=f"preprod-artifact-{uuid.uuid4().hex}",
66-
checksum=checksum,
67-
chunks=chunks,
68-
file_type="preprod.artifact",
69-
)
64+
existing_artifact = None
65+
if existing_file:
66+
existing_artifact = (
67+
PreprodArtifact.objects.select_for_update()
68+
.filter(project=project, file_id=existing_file.id)
69+
.first()
70+
)
7071

71-
if assemble_result is None:
72-
return
72+
if existing_artifact:
73+
logger.info(
74+
"PreprodArtifact already exists for this checksum, skipping assembly",
75+
extra={
76+
"preprod_artifact_id": existing_artifact.id,
77+
"project_id": project_id,
78+
"organization_id": org_id,
79+
"checksum": checksum,
80+
},
81+
)
82+
set_assemble_status(
83+
AssembleTask.PREPROD_ARTIFACT, project_id, checksum, ChunkFileState.OK
84+
)
85+
return
86+
87+
set_assemble_status(
88+
AssembleTask.PREPROD_ARTIFACT, project_id, checksum, ChunkFileState.ASSEMBLING
89+
)
90+
91+
assemble_result = assemble_file(
92+
task=AssembleTask.PREPROD_ARTIFACT,
93+
org_or_project=project,
94+
name=f"preprod-artifact-{uuid.uuid4().hex}",
95+
checksum=checksum,
96+
chunks=chunks,
97+
file_type="preprod.artifact",
98+
)
99+
100+
if assemble_result is None:
101+
return
73102

74-
with transaction.atomic(router.db_for_write(PreprodArtifact)):
75103
build_config = None
76104
if build_configuration:
77105
build_config, _ = PreprodBuildConfiguration.objects.get_or_create(
@@ -93,6 +121,7 @@ def assemble_preprod_artifact(
93121
"preprod_artifact_id": preprod_artifact.id,
94122
"project_id": project_id,
95123
"organization_id": org_id,
124+
"checksum": checksum,
96125
},
97126
)
98127

@@ -102,6 +131,7 @@ def assemble_preprod_artifact(
102131
"timestamp": datetime.datetime.now().isoformat(),
103132
"project_id": project_id,
104133
"organization_id": org_id,
134+
"checksum": checksum,
105135
},
106136
)
107137

0 commit comments

Comments
 (0)