Skip to content

Commit ba785af

Browse files
authored
Fix race condition in logging (#150)
* Fix race condition in logging * bump version v25.37.0 -> v26.8.0 * Bump black version * New tag * bump version v26.8.0 -> v26.8.1
1 parent b5fcb71 commit ba785af

5 files changed

Lines changed: 39 additions & 9 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ repos:
3232
hooks:
3333
- id: prettier
3434
- repo: https://github.com/psf/black
35-
rev: 25.1.0
35+
rev: 26.1.0
3636
hooks:
3737
- id: black
3838
args:

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
# v25.8.1
4+
5+
- Handle race condition when creating directories in logging module, triggered byu many threads trying to create the same directory at the same time.
6+
- Enforce log output bufering to flush after newline characters.
7+
38
# v25.37.0
49

510
- Add additional logging to `sftp` & `local` protocol.

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "opentaskpy"
7-
version = "v25.37.0"
7+
version = "v26.8.1"
88
authors = [{ name = "Adam McDonagh", email = "adam@elitemonkey.net" }]
99
license-files = [ "LICENSE" ]
1010

@@ -42,7 +42,7 @@ requires-python = ">=3.11"
4242
dev = [
4343
"types-requests >=2.28",
4444
"types-paramiko >=3.0",
45-
"black == 25.1.0",
45+
"black == 26.1.0",
4646
"isort",
4747
"pytest",
4848
"bumpver",
@@ -71,7 +71,7 @@ otf-batch-validator = "opentaskpy.cli.batch_validator:main"
7171
profile = 'black'
7272

7373
[tool.bumpver]
74-
current_version = "v25.37.0"
74+
current_version = "v26.8.1"
7575
version_pattern = "vYY.WW.PATCH[-TAG]"
7676
commit_message = "bump version {old_version} -> {new_version}"
7777
commit = true

src/opentaskpy/cli/task_run.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ def main() -> None:
1818
"""Parse args and call TaskRun class."""
1919
parser = argparse.ArgumentParser(
2020
formatter_class=argparse.RawTextHelpFormatter,
21-
epilog=dedent(
22-
"""\
21+
epilog=dedent("""\
2322
Environment Variables:
2423
2524
There are several environment variables that can be used to impact the behaviour:
@@ -44,8 +43,7 @@ def main() -> None:
4443
4544
e.g. OTF_OVERRIDE_TRANSFER_DESTINATION_0_PROTOCOL_CREDENTIALS_USERNAME
4645
47-
"""
48-
),
46+
"""),
4947
)
5048

5149
parser.add_argument(

src/opentaskpy/otflogging.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
import os
66
import re
7+
import threading
78
from datetime import datetime
89

910
OTF_LOG_FORMAT = (
@@ -393,7 +394,11 @@ def close_log_file(logger__: logging.Logger, result: bool = False) -> None:
393394
# Now everything is closed, we can rename the log file
394395
# If result is True, then rename the file and remove _running from the name
395396
if new_log_filename:
396-
os.rename(log_file_name, new_log_filename)
397+
# Use lock to prevent race condition when multiple threads rename same file
398+
with _file_operations_lock:
399+
# Check file still exists before renaming (another thread may have renamed it)
400+
if os.path.exists(log_file_name):
401+
os.rename(log_file_name, new_log_filename)
397402

398403
# Change the basename of the handler to match the new filename, in case it
399404
# wants to log anything else
@@ -443,6 +448,9 @@ def redact(log_message: str) -> str:
443448

444449
logger = init_logging(__name__)
445450

451+
# Thread lock for directory creation and file operations
452+
_file_operations_lock = threading.Lock()
453+
446454

447455
# mypy: ignore-errors
448456
class TaskFileHandler(logging.FileHandler):
@@ -461,3 +469,22 @@ def __init__(self, filename, mode="a", encoding=None, delay=True):
461469
"""
462470
_mkdir(os.path.dirname(filename))
463471
logging.FileHandler.__init__(self, filename, mode, encoding, delay)
472+
473+
def _open(self):
474+
"""Open the log file, ensuring the directory exists.
475+
476+
Overrides the FileHandler._open to ensure directory creation is thread-safe
477+
even when delay=True is used. Also uses line buffering for real-time log updates,
478+
which is important when using network filesystems like EFS.
479+
"""
480+
# Ensure directory exists with thread safety before opening file
481+
with _file_operations_lock:
482+
_mkdir(os.path.dirname(self.baseFilename))
483+
# Open with line buffering (buffering=1) for real-time log visibility on network filesystems
484+
return open(
485+
self.baseFilename,
486+
self.mode,
487+
buffering=1,
488+
encoding=self.encoding,
489+
errors=self.errors,
490+
)

0 commit comments

Comments
 (0)