Skip to content

Commit 7d0800a

Browse files
authored
fix: Race conditions with multiple downloads on the same layer (#7422)
* fix: Race conditions with multiple downloads on the same layer * Update format * Fix lint * Using uuid for download filename
1 parent 8bb5837 commit 7d0800a

4 files changed

Lines changed: 109 additions & 2 deletions

File tree

samcli/local/layers/layer_downloader.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import logging
6+
import uuid
67
from pathlib import Path
78
from typing import List
89

@@ -106,7 +107,7 @@ def download(self, layer: LayerVersion, force=False) -> LayerVersion:
106107
LOG.info("%s is already cached. Skipping download", layer.arn)
107108
return layer
108109

109-
layer_zip_path = layer.codeuri + ".zip"
110+
layer_zip_path = f"{layer.codeuri}_{uuid.uuid4().hex}.zip"
110111
layer_zip_uri = self._fetch_layer_uri(layer)
111112
unzip_from_uri(
112113
layer_zip_uri,

tests/integration/local/start_api/test_start_api.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3220,6 +3220,20 @@ def test_can_invoke_lambda_layer_successfully(self):
32203220
self.assertEqual(response.content.decode("utf-8"), '"Layer1"')
32213221

32223222

3223+
class TestWarmContainersMultipleRemoteLayersInvoke(WarmContainersWithRemoteLayersBase):
3224+
template_path = "/testdata/start_api/template-warm-containers-multi-layers.yaml"
3225+
container_mode = ContainersInitializationMode.EAGER.value
3226+
mode_env_variable = str(uuid.uuid4())
3227+
parameter_overrides = {"ModeEnvVariable": mode_env_variable}
3228+
3229+
@pytest.mark.flaky(reruns=3)
3230+
@pytest.mark.timeout(timeout=600, method="thread")
3231+
def test_can_invoke_lambda_layer_successfully(self):
3232+
response = requests.get(self.url + "/", timeout=300)
3233+
self.assertEqual(response.status_code, 200)
3234+
self.assertEqual(response.content.decode("utf-8"), '"Layer1"')
3235+
3236+
32233237
class TestDisableAuthorizer(StartApiIntegBaseClass):
32243238
# integration test for scenario: 'sam local start-api --disable-authorizer'
32253239
template_path = "/testdata/start_api/lambda_authorizers/serverless-api-props.yaml"
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
AWSTemplateFormatVersion : '2010-09-09'
2+
Transform: AWS::Serverless-2016-10-31
3+
4+
Parameters:
5+
ModeEnvVariable:
6+
Type: String
7+
LayerArn:
8+
Default: arn:aws:lambda:us-west-2:111111111111:layer:layer:1
9+
Type: String
10+
11+
Resources:
12+
ApiGatewayApi:
13+
Type: AWS::Serverless::Api
14+
Properties:
15+
StageName: prod
16+
CacheClusterEnabled: true
17+
CacheClusterSize: '0.5'
18+
MethodSettings:
19+
- ResourcePath: /
20+
HttpMethod: GET
21+
CachingEnabled: true
22+
CacheTtlInSeconds: 300
23+
HelloWorldFunction:
24+
Type: AWS::Serverless::Function
25+
Properties:
26+
Handler: main-layers.custom_layer_handler
27+
Runtime: python3.9
28+
FunctionName: customname
29+
CodeUri: .
30+
Timeout: 600
31+
Environment:
32+
Variables:
33+
MODE: !Ref ModeEnvVariable
34+
Layers:
35+
# Test remote layers with warm containers.
36+
- Ref: LayerArn
37+
Events:
38+
ApiEvent:
39+
Type: Api
40+
Properties:
41+
Path: /
42+
Method: get
43+
RestApiId:
44+
Ref: ApiGatewayApi
45+
HelloWorldFunction2:
46+
Type: AWS::Serverless::Function
47+
Properties:
48+
Handler: main-layers.custom_layer_handler
49+
Runtime: python3.9
50+
FunctionName: customname
51+
CodeUri: .
52+
Timeout: 600
53+
Environment:
54+
Variables:
55+
MODE: !Ref ModeEnvVariable
56+
Layers:
57+
# Test remote layers with warm containers.
58+
- Ref: LayerArn
59+
Events:
60+
ApiEvent:
61+
Type: Api
62+
Properties:
63+
Path: /
64+
Method: get
65+
RestApiId:
66+
Ref: ApiGatewayApi
67+
HelloWorldFunction3:
68+
Type: AWS::Serverless::Function
69+
Properties:
70+
Handler: main-layers.custom_layer_handler
71+
Runtime: python3.9
72+
FunctionName: customname
73+
CodeUri: .
74+
Timeout: 600
75+
Environment:
76+
Variables:
77+
MODE: !Ref ModeEnvVariable
78+
Layers:
79+
# Test remote layers with warm containers.
80+
- Ref: LayerArn
81+
Events:
82+
ApiEvent:
83+
Type: Api
84+
Properties:
85+
Path: /
86+
Method: get
87+
RestApiId:
88+
Ref: ApiGatewayApi

tests/unit/local/layers/test_download_layers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ def test_download_layer_that_was_template_defined(self, create_cache_patch, reso
9898
def test_download_layer(
9999
self, is_layer_cached_patch, create_cache_patch, fetch_layer_uri_patch, unzip_from_uri_patch
100100
):
101+
class AnyStringWith(str):
102+
def __eq__(self, other):
103+
return self in other
104+
101105
is_layer_cached_patch.return_value = False
102106

103107
download_layers = LayerDownloader("/home", ".", Mock())
@@ -118,7 +122,7 @@ def test_download_layer(
118122
fetch_layer_uri_patch.assert_called_once_with(layer_mock)
119123
unzip_from_uri_patch.assert_called_once_with(
120124
"layer/uri",
121-
str(Path("/home/layer1.zip").resolve()),
125+
AnyStringWith(str(Path("/home/layer1_"))),
122126
unzip_output_dir=str(Path("/home/layer1").resolve()),
123127
progressbar_label="Downloading arn:layer:layer1",
124128
)

0 commit comments

Comments
 (0)