Skip to content

Commit 7ac85ef

Browse files
authored
Separate CRT logging from Python logging to avoid deadlock when enabling CRT logs (#156)
* Separate CRT logging from Python logging to avoid deadlock when enabling CRT logs. * Use sys.executable and leverage rusty-fork crate for separate process testing, and return error on logger initialization failure. * Update logging setup to log to file if CRT_LOGS_DIR_PATH environment variable is set with a logging directory path.
1 parent dea17f8 commit 7ac85ef

File tree

8 files changed

+348
-58
lines changed

8 files changed

+348
-58
lines changed

CHANGELOG.md

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

3+
### New features
4+
5+
### Bug Fixes
6+
* Fix deadlock when enabling CRT debug logs. Removed former experimental method _enable_debug_logging().
7+
8+
39
## v1.1.4 (February 26, 2024)
410

511
### New features

s3torchconnectorclient/Cargo.lock

Lines changed: 100 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

s3torchconnectorclient/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ futures = "0.3.28"
2222
mountpoint-s3-client = { version = "0.7.0", features = ["mock"] }
2323
mountpoint-s3-crt = "0.6.1"
2424
log = "0.4.20"
25-
tracing = { version = "0.1.37", features = ["log"] }
26-
tracing-subscriber = "0.3.17"
25+
tracing = { version = "0.1.40", default-features = false, features = ["std", "log"] }
26+
tracing-subscriber = { version = "0.3.18", features = ["fmt", "env-filter"]}
2727
nix = { version = "0.27.1", features = ["process"] }
28+
rusty-fork = "0.3.0"
29+
tracing-appender = "0.2.3"
2830

2931
[features]
3032
extension-module = ["pyo3/extension-module"]

s3torchconnectorclient/python/src/s3torchconnectorclient/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import copyreg
55

66
from ._logger_patch import TRACE as LOG_TRACE
7-
from ._logger_patch import _install_trace_logging, _enable_debug_logging
7+
from ._logger_patch import _install_trace_logging
88
from ._mountpoint_s3_client import S3Exception, __version__
99

1010
_install_trace_logging()
@@ -16,4 +16,4 @@ def _s3exception_reduce(exc: S3Exception):
1616

1717
copyreg.pickle(S3Exception, _s3exception_reduce)
1818

19-
__all__ = ["LOG_TRACE", "S3Exception", "__version__", "_enable_debug_logging"]
19+
__all__ = ["LOG_TRACE", "S3Exception", "__version__"]

s3torchconnectorclient/python/src/s3torchconnectorclient/_logger_patch.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,9 @@
22
# // SPDX-License-Identifier: BSD
33

44
import logging
5-
import atexit
6-
7-
from ._mountpoint_s3_client import _enable_crt_logging, _disable_logging
85

96
TRACE = 5
107

118

129
def _install_trace_logging():
1310
logging.addLevelName(TRACE, "TRACE")
14-
15-
16-
# Experimental method for enabling verbose logging.
17-
# Please do NOT use unless otherwise instructed.
18-
def _enable_debug_logging():
19-
atexit.register(_disable_logging)
20-
_enable_crt_logging()
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# // SPDX-License-Identifier: BSD
3+
import os.path
4+
import sys
5+
import tempfile
6+
7+
import pytest
8+
9+
PYTHON_TEST_CODE = """
10+
import logging
11+
import os
12+
import sys
13+
14+
s3_uri = sys.argv[1]
15+
region = sys.argv[2]
16+
os.environ["ENABLE_CRT_LOGS"] = sys.argv[3]
17+
logs_dir_path = sys.argv[4]
18+
if logs_dir_path != "":
19+
os.environ["CRT_LOGS_DIR_PATH"] = logs_dir_path
20+
21+
from s3torchconnector import S3MapDataset
22+
23+
logging.basicConfig(
24+
stream=sys.stdout,
25+
format="%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s",
26+
)
27+
logging.getLogger().setLevel(logging.INFO)
28+
29+
map_dataset = S3MapDataset.from_prefix(s3_uri, region=region)
30+
obj = map_dataset[0]
31+
assert obj is not None
32+
"""
33+
34+
import subprocess
35+
36+
37+
@pytest.mark.parametrize(
38+
"log_level, should_contain, should_not_contain",
39+
[
40+
(
41+
"info",
42+
["INFO s3torchconnector.s3map_dataset"],
43+
["DEBUG", "TRACE"],
44+
),
45+
(
46+
"DEBUG",
47+
[
48+
"INFO s3torchconnector.s3map_dataset",
49+
"DEBUG awscrt::AWSProfile",
50+
"DEBUG awscrt::AuthCredentialsProvider",
51+
"DEBUG awscrt::S3Client",
52+
],
53+
["TRACE"],
54+
),
55+
(
56+
"TRACE",
57+
[
58+
"INFO s3torchconnector.s3map_dataset",
59+
"DEBUG awscrt::AWSProfile",
60+
"DEBUG awscrt::AuthCredentialsProvider",
61+
"TRACE awscrt::event-loop",
62+
"TRACE awscrt::socket",
63+
"TRACE awscrt::event-loop",
64+
],
65+
# Python log level is set to INFO in the test script
66+
["TRACE s3torchconnector.s3map_dataset"],
67+
),
68+
("OFF", ["INFO s3torchconnector.s3map_dataset"], ["awscrt"]),
69+
],
70+
)
71+
def test_logging_valid(log_level, should_contain, should_not_contain, image_directory):
72+
out, err = _start_subprocess(log_level, image_directory)
73+
assert err == ""
74+
assert all(s in out for s in should_contain)
75+
assert all(s not in out for s in should_not_contain)
76+
77+
78+
def test_logging_to_file(image_directory):
79+
with tempfile.TemporaryDirectory() as log_dir:
80+
print("Created temporary directory", log_dir)
81+
out, err = _start_subprocess("INFO", image_directory, log_dir)
82+
# Standard output contains Python output
83+
assert err == ""
84+
assert "INFO s3torchconnector.s3map_dataset" in out
85+
assert "awscrt" not in out
86+
files = os.listdir(log_dir)
87+
assert len(files) == 1
88+
log_file = os.path.join(log_dir, files[0])
89+
assert os.path.isfile(log_file)
90+
with open(log_file) as f:
91+
file_content = f.read()
92+
assert all(s in file_content for s in ["awscrt", "INFO"])
93+
assert all(
94+
s not in file_content
95+
for s in ["INFO s3torchconnector.s3map_dataset", "DEBUG", "TRACE"]
96+
)
97+
98+
99+
def _start_subprocess(log_level, image_directory, logs_directory=""):
100+
process = subprocess.Popen(
101+
[
102+
sys.executable,
103+
"-c",
104+
PYTHON_TEST_CODE,
105+
image_directory.s3_uri,
106+
image_directory.region,
107+
log_level,
108+
logs_directory,
109+
],
110+
stdout=subprocess.PIPE,
111+
stderr=subprocess.PIPE,
112+
text=True,
113+
)
114+
return process.communicate()

0 commit comments

Comments
 (0)