Skip to content

Commit 348bee8

Browse files
committed
Fix pickling and add docs
1 parent b834c75 commit 348bee8

File tree

4 files changed

+38
-11
lines changed

4 files changed

+38
-11
lines changed

docs/backends/amazon-S3.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,13 +259,25 @@ Settings
259259
Setting this overrides the settings for ``addressing_style``, ``signature_version`` and
260260
``proxies``. Include them as arguments to your ``botocore.config.Config`` class if you need them.
261261

262+
``client_ttl`` or ``AWS_S3_CLIENT_TTL``
263+
264+
Default: ``3600``
265+
266+
The amount of seconds to store a boto3 client resource in an S3Storage instance's time-to-live cache.
267+
268+
.. note::
269+
270+
Long-lived boto3 clients have a known `memory leak`_, which is why the client is
271+
periodically recreated to avoid excessive memory consumption.
272+
262273
.. _AWS Signature Version 4: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
263274
.. _S3 region list: https://docs.aws.amazon.com/general/latest/gr/s3.html#s3_region
264275
.. _list of canned ACLs: https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
265276
.. _Boto3 docs for uploading files: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.put_object
266277
.. _Boto3 docs for TransferConfig: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/customizations/s3.html#boto3.s3.transfer.TransferConfig
267278
.. _ManifestStaticFilesStorage: https://docs.djangoproject.com/en/3.1/ref/contrib/staticfiles/#manifeststaticfilesstorage
268279
.. _Botocore docs: https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html#botocore.config.Config
280+
.. _memory leak: https://github.com/boto/boto3/issues/1670
269281

270282
.. _cloudfront-signed-url-header:
271283

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ azure = [
5050
]
5151
boto3 = [
5252
"boto3>=1.4.4",
53+
"cachetools>=5.0.0",
54+
"backports.cached_property; python_version<='3.7'"
5355
]
5456
dropbox = [
5557
"dropbox>=7.2.1",
@@ -63,6 +65,7 @@ libcloud = [
6365
s3 = [
6466
"boto3>=1.4.4",
6567
"cachetools>=5.0.0",
68+
"backports.cached_property; python_version<='3.7'"
6669
]
6770
sftp = [
6871
"paramiko>=1.15",

storages/backends/s3.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@
3636
msg = "Could not import cachetools. Did you run 'pip install django-storages[s3]'?"
3737
raise ImproperlyConfigured(msg) from e
3838

39+
try:
40+
from functools import cached_property
41+
except ImportError: # python_version<='3.7'
42+
try:
43+
from backports.cached_property import cached_property
44+
except (ImportError, ModuleNotFoundError) as e:
45+
msg = "Could not import backports.cached_property. Did you run 'pip install django-storages[s3]'?" # noqa: E501
46+
raise ImproperlyConfigured(msg) from e
47+
3948
try:
4049
import boto3.session
4150
import botocore
@@ -379,10 +388,6 @@ def __init__(self, **settings):
379388
else:
380389
self.cloudfront_signer = None
381390

382-
self._ttl_cache = cachetools.cached(
383-
cache=cachetools.TTLCache(maxsize=2, ttl=3600), lock=threading.Lock()
384-
)
385-
386391
def get_cloudfront_signer(self, key_id, key):
387392
cache_key = f"{key_id}:{key}"
388393
if cache_key not in self.__class__._signers:
@@ -451,6 +456,7 @@ def get_default_settings(self):
451456
"use_threads": setting("AWS_S3_USE_THREADS", True),
452457
"transfer_config": setting("AWS_S3_TRANSFER_CONFIG", None),
453458
"client_config": setting("AWS_S3_CLIENT_CONFIG", None),
459+
"client_ttl": setting("AWS_S3_CLIENT_TTL", 3600),
454460
}
455461

456462
def __getstate__(self):
@@ -465,23 +471,27 @@ def __setstate__(self, state):
465471
def connection(self):
466472
"""
467473
Get the (cached) thread-safe boto3 s3 resource.
468-
469-
This function has a 1 hour time to live cache for the boto3 resource.
470-
We want to avoid storing a resource for too long to avoid their memory leak
471-
ref https://github.com/boto/boto3/issues/1670.
472474
"""
473475
return self._ttl_cache(self._create_connection)()
474476

475477
@property
476478
def unsigned_connection(self):
477479
"""
478480
Get the (cached) thread-safe boto3 s3 resource (unsigned).
481+
"""
482+
return self._ttl_cache(self._create_connection)(unsigned=True)
479483

480-
This function has a 1 hour time to live cache for the boto3 resource.
484+
@cached_property
485+
def _ttl_cache(self):
486+
"""
487+
This time to live cache is used to periodically recreate boto3 clients.
481488
We want to avoid storing a resource for too long to avoid their memory leak
482489
ref https://github.com/boto/boto3/issues/1670.
483490
"""
484-
return self._ttl_cache(self._create_connection)(unsigned=True)
491+
return cachetools.cached(
492+
cache=cachetools.TTLCache(maxsize=2, ttl=self.client_ttl),
493+
lock=threading.Lock(),
494+
)
485495

486496
def _create_connection(self, *, unsigned=False):
487497
"""

tests/test_s3.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ def test_pickle(self):
7575
_ = storage.connection
7676
_ = storage.bucket
7777
p = pickle.dumps(storage)
78-
pickle.loads(p)
78+
new_storage = pickle.loads(p)
79+
_ = new_storage.connection
80+
_ = storage.bucket
7981

8082
def test_storage_url_slashes(self):
8183
"""

0 commit comments

Comments
 (0)