Skip to content

Commit 28e63bf

Browse files
authored
fail incomplete task(s) at startup #432 (#433)
1 parent 4df5516 commit 28e63bf

5 files changed

Lines changed: 83 additions & 2 deletions

File tree

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ services:
1414
- .:/usr/src/app
1515
command: >
1616
bash -c "
17-
python manage.py makemigrations &&
18-
python manage.py migrate
17+
python manage.py migrate &&
18+
python manage.py fail_incomplete_tasks
1919
"
2020
django:
2121
extends: django_env
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from django.core.cache import cache
2+
from django.core.management.base import BaseCommand
3+
from django.utils import timezone
4+
5+
from stixify.web import models
6+
7+
8+
class Command(BaseCommand):
9+
help = "Fail incomplete jobs and remove their upload locks."
10+
11+
def handle(self, *args, **options):
12+
incomplete_jobs = models.Job.objects.filter(
13+
state__in=(models.JobState.PENDING, models.JobState.PROCESSING)
14+
)
15+
job_ids = list(incomplete_jobs.values_list("id", flat=True))
16+
updated = incomplete_jobs.update(
17+
state=models.JobState.CANCELED,
18+
error="canceled automatically during restart",
19+
completion_time=timezone.now(),
20+
)
21+
cache.delete_many([f"arango_upload_lock:{job_id}" for job_id in job_ids])
22+
cache.delete("arango_upload_active_count")
23+
self.stdout.write(f"Failed {updated} incomplete job(s).")
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2.12 on 2026-08-12 14:38
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('stixify_core', '0028_rename_reprocess_job_type'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='job',
15+
name='state',
16+
field=models.CharField(choices=[('pending', 'Pending'), ('processing', 'Processing'), ('failed', 'Failed'), ('completed', 'Completed'), ('canceled', 'Canceled')], default='pending', max_length=20),
17+
),
18+
]

stixify/web/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ class JobState(models.TextChoices):
297297
PROCESSING = "processing"
298298
FAILED = "failed"
299299
COMPLETED = "completed"
300+
CANCELED = "canceled"
300301

301302
class JobType(models.TextChoices):
302303
IMPORT_FILE = "import-file"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import pytest
2+
from django.core.cache import cache
3+
from django.core.management import call_command
4+
5+
from stixify.web import models
6+
7+
8+
@pytest.mark.django_db
9+
def test_fail_incomplete_tasks(stixify_file):
10+
pending_job = models.Job.objects.create(file=stixify_file)
11+
processing_job = models.Job.objects.create(
12+
file=stixify_file, state=models.JobState.PROCESSING
13+
)
14+
completed_job = models.Job.objects.create(
15+
file=stixify_file, state=models.JobState.COMPLETED
16+
)
17+
lock_keys = [
18+
f"arango_upload_lock:{pending_job.id}",
19+
# f"arango_upload_lock:{processing_job.id}", // test that cache delete_many works with missing keys
20+
]
21+
cache.set_many({key: "locked" for key in lock_keys})
22+
cache.set("arango_upload_active_count", 2)
23+
cache.set("unrelated", "preserved")
24+
25+
call_command("fail_incomplete_tasks")
26+
27+
pending_job.refresh_from_db()
28+
processing_job.refresh_from_db()
29+
completed_job.refresh_from_db()
30+
for job in (pending_job, processing_job):
31+
assert job.state == models.JobState.CANCELED
32+
assert job.error == "canceled automatically during restart"
33+
assert job.completion_time is not None
34+
assert completed_job.state == models.JobState.COMPLETED
35+
assert completed_job.error is None
36+
assert completed_job.completion_time is None
37+
assert cache.get_many(lock_keys) == {}
38+
assert cache.get("arango_upload_active_count") is None
39+
assert cache.get("unrelated") == "preserved"

0 commit comments

Comments
 (0)