Skip to content

Commit

Permalink
fix: Race conditions with multiple downloads on the same layer (#7422)
Browse files Browse the repository at this point in the history
* fix: Race conditions with multiple downloads on the same layer

* Update format

* Fix lint

* Using uuid for download filename
  • Loading branch information
dkphm authored Sep 1, 2024
1 parent 8bb5837 commit 7d0800a
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 2 deletions.
3 changes: 2 additions & 1 deletion samcli/local/layers/layer_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

import logging
import uuid
from pathlib import Path
from typing import List

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

layer_zip_path = layer.codeuri + ".zip"
layer_zip_path = f"{layer.codeuri}_{uuid.uuid4().hex}.zip"
layer_zip_uri = self._fetch_layer_uri(layer)
unzip_from_uri(
layer_zip_uri,
Expand Down
14 changes: 14 additions & 0 deletions tests/integration/local/start_api/test_start_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3220,6 +3220,20 @@ def test_can_invoke_lambda_layer_successfully(self):
self.assertEqual(response.content.decode("utf-8"), '"Layer1"')


class TestWarmContainersMultipleRemoteLayersInvoke(WarmContainersWithRemoteLayersBase):
template_path = "/testdata/start_api/template-warm-containers-multi-layers.yaml"
container_mode = ContainersInitializationMode.EAGER.value
mode_env_variable = str(uuid.uuid4())
parameter_overrides = {"ModeEnvVariable": mode_env_variable}

@pytest.mark.flaky(reruns=3)
@pytest.mark.timeout(timeout=600, method="thread")
def test_can_invoke_lambda_layer_successfully(self):
response = requests.get(self.url + "/", timeout=300)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content.decode("utf-8"), '"Layer1"')


class TestDisableAuthorizer(StartApiIntegBaseClass):
# integration test for scenario: 'sam local start-api --disable-authorizer'
template_path = "/testdata/start_api/lambda_authorizers/serverless-api-props.yaml"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Parameters:
ModeEnvVariable:
Type: String
LayerArn:
Default: arn:aws:lambda:us-west-2:111111111111:layer:layer:1
Type: String

Resources:
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
CacheClusterEnabled: true
CacheClusterSize: '0.5'
MethodSettings:
- ResourcePath: /
HttpMethod: GET
CachingEnabled: true
CacheTtlInSeconds: 300
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
Handler: main-layers.custom_layer_handler
Runtime: python3.9
FunctionName: customname
CodeUri: .
Timeout: 600
Environment:
Variables:
MODE: !Ref ModeEnvVariable
Layers:
# Test remote layers with warm containers.
- Ref: LayerArn
Events:
ApiEvent:
Type: Api
Properties:
Path: /
Method: get
RestApiId:
Ref: ApiGatewayApi
HelloWorldFunction2:
Type: AWS::Serverless::Function
Properties:
Handler: main-layers.custom_layer_handler
Runtime: python3.9
FunctionName: customname
CodeUri: .
Timeout: 600
Environment:
Variables:
MODE: !Ref ModeEnvVariable
Layers:
# Test remote layers with warm containers.
- Ref: LayerArn
Events:
ApiEvent:
Type: Api
Properties:
Path: /
Method: get
RestApiId:
Ref: ApiGatewayApi
HelloWorldFunction3:
Type: AWS::Serverless::Function
Properties:
Handler: main-layers.custom_layer_handler
Runtime: python3.9
FunctionName: customname
CodeUri: .
Timeout: 600
Environment:
Variables:
MODE: !Ref ModeEnvVariable
Layers:
# Test remote layers with warm containers.
- Ref: LayerArn
Events:
ApiEvent:
Type: Api
Properties:
Path: /
Method: get
RestApiId:
Ref: ApiGatewayApi
6 changes: 5 additions & 1 deletion tests/unit/local/layers/test_download_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ def test_download_layer_that_was_template_defined(self, create_cache_patch, reso
def test_download_layer(
self, is_layer_cached_patch, create_cache_patch, fetch_layer_uri_patch, unzip_from_uri_patch
):
class AnyStringWith(str):
def __eq__(self, other):
return self in other

is_layer_cached_patch.return_value = False

download_layers = LayerDownloader("/home", ".", Mock())
Expand All @@ -118,7 +122,7 @@ def test_download_layer(
fetch_layer_uri_patch.assert_called_once_with(layer_mock)
unzip_from_uri_patch.assert_called_once_with(
"layer/uri",
str(Path("/home/layer1.zip").resolve()),
AnyStringWith(str(Path("/home/layer1_"))),
unzip_output_dir=str(Path("/home/layer1").resolve()),
progressbar_label="Downloading arn:layer:layer1",
)
Expand Down

0 comments on commit 7d0800a

Please sign in to comment.