Skip to content

Commit

Permalink
fix: running parallel bundle submits no longer clobbers config file
Browse files Browse the repository at this point in the history
Fixes: aws-deadline#386

Problem:

The customer reports that the config file can get clobbered when running
many bundle submit commands in parallel. When clobbered, the config file
will only contain the job-id for the a submitted job; all of the farm, queue, etc
information will be gone.

Solution:

A standard pattern for concurrent file modification is to write changes to a temp file,
and then move that temp file overtop of the config file via a filesystem rename operation.
The rename is atomic, so that prevents the file content from being clobbered.

Signed-off-by: Daniel Neilson <[email protected]>
  • Loading branch information
ddneilson committed Sep 10, 2024
1 parent 66af656 commit 2fe40b4
Showing 1 changed file with 8 additions and 17 deletions.
25 changes: 8 additions & 17 deletions src/deadline/client/config/config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
import getpass
import os
import platform
import stat
import subprocess
from configparser import ConfigParser
from pathlib import Path
from typing import Any, Dict, List, Optional
import tempfile

import boto3
from deadline.job_attachments.models import FileConflictResolution
Expand Down Expand Up @@ -268,10 +268,6 @@ def write_config(config: ConfigParser) -> None:
"""
config_file_path = get_config_file_path()
config_file_path.parent.mkdir(parents=True, exist_ok=True)
try:
config_file_path.unlink()
except FileNotFoundError:
pass

if platform.system() == "Windows":
username = getpass.getuser()
Expand All @@ -280,21 +276,16 @@ def write_config(config: ConfigParser) -> None:
# CI - Sub-directories will inherit
# F - Full control
_reset_directory_permissions_windows(config_file_parent_path, username, "(OI)(CI)(F)")
with open(config_file_path, "w", encoding="utf8") as configfile:
config.write(configfile)

else:
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
mode = stat.S_IRUSR | stat.S_IWUSR
# Using the config file path as the prefix ensures that the tmpfile and real file are
# on the same filesystem. This is a requirement for os.rename to be atomic.
file_descriptor, tmp_file_name = tempfile.mkstemp(prefix=str(config_file_path), text=True)

original_umask = os.umask(0)
try:
file_descriptor = os.open(config_file_path, flags, mode)
finally:
os.umask(original_umask)
# Note: The file descriptor is closed when exiting the context manager.
with os.fdopen(file_descriptor, "w", encoding="utf8") as configfile:
config.write(configfile)

with os.fdopen(file_descriptor, "w", encoding="utf8") as configfile:
config.write(configfile)
os.rename(tmp_file_name, config_file_path)


def _get_setting_config(setting_name: str) -> dict:
Expand Down

0 comments on commit 2fe40b4

Please sign in to comment.