Skip to content

Commit 27fc564

Browse files
authored
Make Lambda reusable for CloudFront Lambda@Edge (#12409)
1 parent 08382d0 commit 27fc564

File tree

5 files changed

+34
-27
lines changed

5 files changed

+34
-27
lines changed

localstack-core/localstack/runtime/analytics.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
TRACKED_ENV_VAR = [
1111
"ALLOW_NONSTANDARD_REGIONS",
1212
"BEDROCK_PREWARM",
13+
"CLOUDFRONT_LAMBDA_EDGE",
1314
"CONTAINER_RUNTIME",
1415
"DEBUG",
1516
"DEFAULT_REGION", # Not functional; deprecated in 0.12.7, removed in 3.0.0

localstack-core/localstack/services/lambda_/invocation/counting_service.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,16 @@ def __init__(self):
8686

8787
@contextlib.contextmanager
8888
def get_invocation_lease(
89-
self, function: Function, function_version: FunctionVersion
89+
self, function: Function | None, function_version: FunctionVersion
9090
) -> InitializationType:
9191
"""An invocation lease reserves the right to schedule an invocation.
9292
The returned lease type can either be on-demand or provisioned.
9393
Scheduling preference:
9494
1) Check for free provisioned concurrency => provisioned
9595
2) Check for reserved concurrency => on-demand
9696
3) Check for unreserved concurrency => on-demand
97+
98+
HACK: We allow the function to be None for Lambda@Edge to skip provisioned and reserved concurrency.
9799
"""
98100
account = function_version.id.account
99101
region = function_version.id.region
@@ -147,25 +149,28 @@ def get_invocation_lease(
147149
)
148150

149151
lease_type = None
150-
with provisioned_tracker.lock:
151-
# 1) Check for free provisioned concurrency
152-
provisioned_concurrency_config = function.provisioned_concurrency_configs.get(
153-
function_version.id.qualifier
154-
)
155-
if provisioned_concurrency_config:
156-
available_provisioned_concurrency = (
157-
provisioned_concurrency_config.provisioned_concurrent_executions
158-
- provisioned_tracker.concurrent_executions[qualified_arn]
152+
# HACK: skip reserved and provisioned concurrency if function not available (e.g., in Lambda@Edge)
153+
if function is not None:
154+
with provisioned_tracker.lock:
155+
# 1) Check for free provisioned concurrency
156+
provisioned_concurrency_config = function.provisioned_concurrency_configs.get(
157+
function_version.id.qualifier
159158
)
160-
if available_provisioned_concurrency > 0:
161-
provisioned_tracker.increment(qualified_arn)
162-
lease_type = "provisioned-concurrency"
159+
if provisioned_concurrency_config:
160+
available_provisioned_concurrency = (
161+
provisioned_concurrency_config.provisioned_concurrent_executions
162+
- provisioned_tracker.concurrent_executions[qualified_arn]
163+
)
164+
if available_provisioned_concurrency > 0:
165+
provisioned_tracker.increment(qualified_arn)
166+
lease_type = "provisioned-concurrency"
163167

164168
if not lease_type:
165169
with on_demand_tracker.lock:
166170
# 2) If reserved concurrency is set AND no provisioned concurrency available:
167171
# => Check if enough reserved concurrency is available for the specific function.
168-
if function.reserved_concurrent_executions is not None:
172+
# HACK: skip reserved if function not available (e.g., in Lambda@Edge)
173+
if function and function.reserved_concurrent_executions is not None:
169174
on_demand_running_invocation_count = on_demand_tracker.concurrent_executions[
170175
unqualified_function_arn
171176
]

localstack-core/localstack/services/lambda_/invocation/lambda_service.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,6 @@ def invoke(
285285
event_manager = self.get_lambda_event_manager(qualified_arn)
286286
except ValueError as e:
287287
state = version and version.config.state.state
288-
# TODO: make such developer hints optional or remove after initial v2 transition period
289288
if state == State.Failed:
290289
status = FunctionStatus.failed_state_error
291290
HINT_LOG.error(

localstack-core/localstack/services/lambda_/invocation/version_manager.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ def __init__(
5656
self,
5757
function_arn: str,
5858
function_version: FunctionVersion,
59-
function: Function,
59+
# HACK allowing None for Lambda@Edge; only used in invoke for get_invocation_lease
60+
function: Function | None,
6061
counting_service: CountingService,
6162
assignment_service: AssignmentService,
6263
):
@@ -75,7 +76,8 @@ def __init__(
7576
# async state
7677
self.provisioned_state = None
7778
self.provisioned_state_lock = threading.RLock()
78-
self.state = None
79+
# https://aws.amazon.com/blogs/compute/coming-soon-expansion-of-aws-lambda-states-to-all-functions/
80+
self.state = VersionState(state=State.Pending)
7981

8082
def start(self) -> VersionState:
8183
try:
@@ -90,14 +92,14 @@ def start(self) -> VersionState:
9092

9193
# code and reason not set for success scenario because only failed states provide this field:
9294
# https://docs.aws.amazon.com/lambda/latest/dg/API_GetFunctionConfiguration.html#SSS-GetFunctionConfiguration-response-LastUpdateStatusReasonCode
93-
new_state = VersionState(state=State.Active)
95+
self.state = VersionState(state=State.Active)
9496
LOG.debug(
9597
"Changing Lambda %s (id %s) to active",
9698
self.function_arn,
9799
self.function_version.config.internal_revision,
98100
)
99101
except Exception as e:
100-
new_state = VersionState(
102+
self.state = VersionState(
101103
state=State.Failed,
102104
code=StateReasonCode.InternalError,
103105
reason=f"Error while creating lambda: {e}",
@@ -109,7 +111,7 @@ def start(self) -> VersionState:
109111
e,
110112
exc_info=True,
111113
)
112-
return new_state
114+
return self.state
113115

114116
def stop(self) -> None:
115117
LOG.debug("Stopping lambda version '%s'", self.function_arn)
@@ -219,18 +221,18 @@ def invoke(self, *, invocation: Invocation) -> InvocationResult:
219221
if invocation_result.is_error:
220222
start_thread(
221223
lambda *args, **kwargs: record_cw_metric_error(
222-
function_name=self.function.function_name,
223-
account_id=self.function_version.id.account,
224-
region_name=self.function_version.id.region,
224+
function_name=function_id.function_name,
225+
account_id=function_id.account,
226+
region_name=function_id.region,
225227
),
226228
name=f"record-cloudwatch-metric-error-{function_id.function_name}:{function_id.qualifier}",
227229
)
228230
else:
229231
start_thread(
230232
lambda *args, **kwargs: record_cw_metric_invocation(
231-
function_name=self.function.function_name,
232-
account_id=self.function_version.id.account,
233-
region_name=self.function_version.id.region,
233+
function_name=function_id.function_name,
234+
account_id=function_id.account,
235+
region_name=function_id.region,
234236
),
235237
name=f"record-cloudwatch-metric-{function_id.function_name}:{function_id.qualifier}",
236238
)

localstack-core/localstack/services/lambda_/lambda_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from localstack.aws.api.lambda_ import Runtime
99

10-
# Custom logger for proactive deprecation hints related to the migration from the old to the new lambda provider
10+
# Custom logger for proactive advice
1111
HINT_LOG = logging.getLogger("localstack.services.lambda_.hints")
1212

1313

0 commit comments

Comments
 (0)