Skip to content

Commit 8869e9e

Browse files
PasiSamarkkuriekkinen
authored andcommitted
Fix failures during publish stage of course build
Removal of temporary files might block the execution for tens of seconds if a course has many files in build. During this time the cached configuration was not pointing to right paths, and caused failures if accessed. Fix postpones file removal until the end of build process.
1 parent bec6493 commit 8869e9e

File tree

3 files changed

+24
-9
lines changed

3 files changed

+24
-9
lines changed

access/views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,13 +191,15 @@ def aplus_json(request: HttpRequest, course_key: str) -> HttpResponse:
191191
except ConfigError as e:
192192
errors.append(f"Failed to load newly built course due to this error: {e}")
193193
errors.append("Attempting to load previous version of the course...")
194+
logger.warn(f"Failed to load newly built course due to this error: {e}")
194195
else:
195196
defaults_path = CourseConfig.defaults_path(course_key, source=ConfigSource.STORE)
196197

197198
if config is None:
198199
try:
199200
config = CourseConfig.get(course_key, source=ConfigSource.PUBLISH)
200201
except ConfigError as e:
202+
logger.error(f"aplus_json: failed to get config for {course_key}")
201203
try:
202204
Course.objects.get(key=course_key)
203205
except:

builder/builder.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from access.config import INDEX, ConfigSource, CourseConfig, load_meta, META
2929
from access.parser import ConfigError
3030
from builder.configure import configure_graders, publish_graders
31-
from util.files import is_subpath, renames, rm_path, FileLock
31+
from util.files import is_subpath, renames, rm_path, rm_paths, FileLock
3232
from util.git import checkout, clone_if_doesnt_exist, get_commit_hash, get_commit_metadata
3333
from util.pydantic import validation_error_str, validation_warning_str
3434
from util.static import static_url, static_url_path, symbolic_link
@@ -336,14 +336,16 @@ def publish(course_key: str) -> List[str]:
336336

337337
config = None
338338
errors = []
339+
tmpfiles = []
339340
if Path(store_path).exists():
340341
with FileLock(store_path):
341342
try:
342343
config = CourseConfig.get(course_key, source=ConfigSource.STORE)
343344
except ConfigError as e:
344345
errors.append(f"Failed to load newly built course for this reason: {e}")
346+
logger.warn(f"Failed to load newly built course for this reason: {e}")
345347
else:
346-
renames([
348+
tmpfiles = renames([
347349
(store_path, prod_path),
348350
(store_defaults_path, prod_defaults_path),
349351
(store_version_path, prod_version_path),
@@ -356,6 +358,7 @@ def publish(course_key: str) -> List[str]:
356358
config = CourseConfig.get(course_key, source=ConfigSource.PUBLISH)
357359
except ConfigError as e:
358360
errors.append(f"Failed to load already published config: {e}")
361+
logger.error(f"Failed to load already published config: {e}")
359362

360363
if config is None:
361364
if errors:
@@ -364,8 +367,13 @@ def publish(course_key: str) -> List[str]:
364367
raise Exception(f"Course directory not found for {course_key} - the course probably has not been built")
365368

366369
symbolic_link(config)
370+
errors = errors + publish_graders(config)
367371

368-
return errors + publish_graders(config)
372+
# Remove temporary files that were created during renaming.
373+
# This may block the execution for a while on courses with many files.
374+
rm_paths(tmpfiles)
375+
376+
return errors
369377

370378

371379
# the task locks can get stuck if the program suddenly shuts down,

util/files.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import tempfile
1010
import time
1111
from types import TracebackType
12-
from typing import Dict, Generator, Iterable, Optional, Tuple, Type, Union
12+
from typing import Dict, Generator, Iterable, Optional, Tuple, Type, Union, List
1313

1414
from django.conf import settings
1515
from django.http.response import FileResponse as DjangoFileResponse, HttpResponse
@@ -46,6 +46,12 @@ def rm_path(path: Union[str, Path]) -> None:
4646
path.unlink()
4747

4848

49+
def rm_paths(paths: Iterable[Union[str, Path]]) -> None:
50+
for path in paths:
51+
if path is not None:
52+
rm_path(path)
53+
54+
4955
def is_subpath(child: PathLike, parent: Optional[PathLike] = None) -> bool:
5056
"""
5157
If parent is not None, returns whether child is a subpath of (contained in)
@@ -168,9 +174,10 @@ def rename(src: PathLike, dst: PathLike, keep_tmp=False) -> Optional[str]:
168174
return tmpdst
169175

170176

171-
def renames(pairs: Iterable[Tuple[PathLike, PathLike]]) -> None:
177+
def renames(pairs: Iterable[Tuple[PathLike, PathLike]]) -> List[str]:
172178
"""
173179
Renames multiple files and directories while making sure that either all or none succeed.
180+
Returns a list of generated tmp directories (for later removal).
174181
"""
175182
done = set()
176183
try:
@@ -183,10 +190,8 @@ def renames(pairs: Iterable[Tuple[PathLike, PathLike]]) -> None:
183190
if os.path.exists(tmp):
184191
rename(tmp, dst)
185192
raise
186-
else:
187-
for _, _, tmp in done:
188-
if tmp is not None:
189-
rm_path(tmp)
193+
194+
return list(map(lambda x: x[2], done))
190195

191196

192197
def _try_lockf(lockfile, flags) -> Optional[OSError]:

0 commit comments

Comments
 (0)