Skip to content

Commit f750fdb

Browse files
fix: added lock to prevent same task run multiple time at the same time (#784)
* fix: added lock to prevent same task run multiple time at the same time * fix: add debounce strategy before calling the celery tasks to avoid queuing extra tasks * chore: version bumped
1 parent ab52b0c commit f750fdb

4 files changed

Lines changed: 54 additions & 2 deletions

File tree

src/ol_openedx_git_auto_export/ol_openedx_git_auto_export/constants.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,12 @@
55

66
COURSE_RERUN_STATE_SUCCEEDED = "succeeded"
77
REPOSITORY_NAME_MAX_LENGTH = 100 # Max length from GitHub for repo name
8+
9+
# Debounce settings for the signal handler.
10+
# A single course save triggers 10-30 COURSE_PUBLISHED signals in one request.
11+
# cache.add() on this key ensures only the first signal schedules a task; all
12+
# subsequent signals within the window are silently dropped before hitting the broker.
13+
# The task is scheduled with countdown=EXPORT_DEBOUNCE_DELAY so it runs after
14+
# the burst window has closed and the course state is fully settled.
15+
EXPORT_DEBOUNCE_DELAY = 5 # seconds — must exceed the publish burst window
16+
EXPORT_DEBOUNCE_CACHE_KEY = "git_export_debounce:{course_key}"

src/ol_openedx_git_auto_export/ol_openedx_git_auto_export/tasks.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from ol_openedx_git_auto_export.models import CourseGitRepository
1818
from ol_openedx_git_auto_export.utils import (
19+
clear_stale_git_lock,
1920
export_course_to_git,
2021
github_repo_name_format,
2122
is_auto_repo_creation_enabled,
@@ -39,6 +40,10 @@ def async_export_to_git(course_key_string, user=None):
3940
"Starting async course content export to git (course id: %s)",
4041
course_module.id,
4142
)
43+
# Remove any stale .git/index.lock left by a previously crashed worker.
44+
# Dirty working-tree files from a prior crash are cleaned by the
45+
# `git reset --hard origin/<branch>` + `git clean` inside export_to_git.
46+
clear_stale_git_lock(course_repo.git_url)
4247
export_to_git(course_module.id, course_repo.git_url, user=user)
4348
else:
4449
LOGGER.info(

src/ol_openedx_git_auto_export/ol_openedx_git_auto_export/utils.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
import logging
66
import os
77
import re
8+
from pathlib import Path
89

910
from django.conf import settings
1011
from django.contrib.auth.models import User
12+
from django.core.cache import cache
1113
from django.core.exceptions import ImproperlyConfigured
1214
from xmodule.modulestore.django import modulestore
1315

1416
from ol_openedx_git_auto_export.constants import (
1517
ENABLE_AUTO_GITHUB_REPO_CREATION,
1618
ENABLE_GIT_AUTO_EXPORT,
19+
EXPORT_DEBOUNCE_CACHE_KEY,
20+
EXPORT_DEBOUNCE_DELAY,
1721
REPOSITORY_NAME_MAX_LENGTH,
1822
)
1923

@@ -100,7 +104,41 @@ def export_course_to_git(course_key):
100104
)
101105

102106
user = get_publisher_username(course_module)
103-
async_export_to_git.delay(str(course_key), user)
107+
108+
debounce_key = EXPORT_DEBOUNCE_CACHE_KEY.format(course_key=str(course_key))
109+
if cache.add(debounce_key, "1", timeout=EXPORT_DEBOUNCE_DELAY):
110+
log.info(
111+
"Scheduling git export for course %s with %ds debounce delay",
112+
course_key,
113+
EXPORT_DEBOUNCE_DELAY,
114+
)
115+
async_export_to_git.apply_async(
116+
args=[str(course_key), user],
117+
countdown=EXPORT_DEBOUNCE_DELAY,
118+
)
119+
else:
120+
log.info(
121+
"Git export already scheduled for course %s, skipping duplicate signal",
122+
course_key,
123+
)
124+
125+
126+
def clear_stale_git_lock(git_url):
127+
"""
128+
Remove a stale .git/index.lock file for the local clone of git_url, if present.
129+
130+
A stale lock file can be left behind when a worker process is killed mid-operation.
131+
"""
132+
git_repo_export_dir = getattr(
133+
settings, "GIT_REPO_EXPORT_DIR", "/openedx/export_course_repos"
134+
)
135+
rdir = git_url.rsplit("/", 1)[-1].rsplit(".git", 1)[0]
136+
index_lock = Path(git_repo_export_dir) / rdir / ".git" / "index.lock"
137+
if index_lock.exists():
138+
log.warning(
139+
"Removing stale .git/index.lock for repo %s at %s", git_url, index_lock
140+
)
141+
index_lock.unlink()
104142

105143

106144
def is_auto_repo_creation_enabled():

src/ol_openedx_git_auto_export/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "ol-openedx-git-auto-export"
3-
version = "0.7.1"
3+
version = "0.7.2"
44
description = "A plugin that auto saves the course OLX to git when an author publishes it"
55
authors = [
66
{name = "MIT Office of Digital Learning"}

0 commit comments

Comments
 (0)