django-lambda-tasks 0.6.5__tar.gz → 0.6.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/steering/product.md +1 -1
  2. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/PKG-INFO +1 -1
  3. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/handler.py +12 -17
  4. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/pyproject.toml +1 -1
  5. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_memory_limit.py +43 -59
  6. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.github/workflows/ci.yml +0 -0
  7. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.github/workflows/release.yml +0 -0
  8. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.gitignore +0 -0
  9. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/async-local-execution/.config.kiro +0 -0
  10. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/async-local-execution/design.md +0 -0
  11. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/async-local-execution/requirements.md +0 -0
  12. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/async-local-execution/tasks.md +0 -0
  13. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/deferred-task-enqueue/.config.kiro +0 -0
  14. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/deferred-task-enqueue/design.md +0 -0
  15. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/deferred-task-enqueue/requirements.md +0 -0
  16. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/deferred-task-enqueue/tasks.md +0 -0
  17. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/eager-mode-example-app/.config.kiro +0 -0
  18. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/eager-mode-example-app/design.md +0 -0
  19. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/eager-mode-example-app/requirements.md +0 -0
  20. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/eager-mode-example-app/tasks.md +0 -0
  21. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/ignore-errors-decorator-option/.config.kiro +0 -0
  22. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/ignore-errors-decorator-option/design.md +0 -0
  23. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/ignore-errors-decorator-option/requirements.md +0 -0
  24. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/ignore-errors-decorator-option/tasks.md +0 -0
  25. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/import-string-task-resolution/.config.kiro +0 -0
  26. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/import-string-task-resolution/design.md +0 -0
  27. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/import-string-task-resolution/requirements.md +0 -0
  28. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/import-string-task-resolution/tasks.md +0 -0
  29. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/retry-delay/.config.kiro +0 -0
  30. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/retry-delay/design.md +0 -0
  31. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/retry-delay/requirements.md +0 -0
  32. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/retry-delay/tasks.md +0 -0
  33. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/rse-background-tasks/.config.kiro +0 -0
  34. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/rse-background-tasks/design.md +0 -0
  35. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/rse-background-tasks/requirements.md +0 -0
  36. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/rse-background-tasks/tasks.md +0 -0
  37. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/rse-background-tasks-bugfix/.config.kiro +0 -0
  38. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/rse-background-tasks-bugfix/bugfix.md +0 -0
  39. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/rse-background-tasks-bugfix/design.md +0 -0
  40. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/rse-background-tasks-bugfix/tasks.md +0 -0
  41. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/singleton-task/.config.kiro +0 -0
  42. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/singleton-task/design.md +0 -0
  43. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/singleton-task/requirements.md +0 -0
  44. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/singleton-task/tasks.md +0 -0
  45. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/ssm-environment-loader/.config.kiro +0 -0
  46. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/ssm-environment-loader/design.md +0 -0
  47. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/ssm-environment-loader/requirements.md +0 -0
  48. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/ssm-environment-loader/tasks.md +0 -0
  49. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/task-retry/.config.kiro +0 -0
  50. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/task-retry/design.md +0 -0
  51. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/task-retry/requirements.md +0 -0
  52. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/specs/task-retry/tasks.md +0 -0
  53. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/steering/structure.md +0 -0
  54. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.kiro/steering/tech.md +0 -0
  55. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.pre-commit-config.yaml +0 -0
  56. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/.vscode/settings.json +0 -0
  57. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/README.md +0 -0
  58. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/example/README.md +0 -0
  59. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/example/example_app/__init__.py +0 -0
  60. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/example/example_app/apps.py +0 -0
  61. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/example/example_app/tasks.py +0 -0
  62. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/example/example_app/urls.py +0 -0
  63. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/example/example_app/views.py +0 -0
  64. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/example/example_project/__init__.py +0 -0
  65. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/example/example_project/settings.py +0 -0
  66. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/example/example_project/urls.py +0 -0
  67. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/example/example_project/wsgi.py +0 -0
  68. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/example/manage.py +0 -0
  69. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/__init__.py +0 -0
  70. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/admin.py +0 -0
  71. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/apps.py +0 -0
  72. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/decorators.py +0 -0
  73. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/environment_loader.py +0 -0
  74. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/local_executor.py +0 -0
  75. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/logging.py +0 -0
  76. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/migrations/0001_initial.py +0 -0
  77. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/migrations/0002_alter_taskrecord_status.py +0 -0
  78. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/migrations/__init__.py +0 -0
  79. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/models.py +0 -0
  80. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/secret_loader.py +0 -0
  81. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/settings.py +0 -0
  82. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/tasks.py +0 -0
  83. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/lambda_tasks/timeouts.py +0 -0
  84. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/conftest.py +0 -0
  85. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/settings.py +0 -0
  86. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_admin.py +0 -0
  87. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_batch.py +0 -0
  88. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_batch_worker.py +0 -0
  89. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_decorator.py +0 -0
  90. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_decorators.py +0 -0
  91. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_deferred_enqueue.py +0 -0
  92. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_environment_loader.py +0 -0
  93. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_handler.py +0 -0
  94. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_kwargs_only.py +0 -0
  95. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_local_executor.py +0 -0
  96. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_logging.py +0 -0
  97. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_models.py +0 -0
  98. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_secret_loader.py +0 -0
  99. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_serializer.py +0 -0
  100. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_settings.py +0 -0
  101. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_tasks.py +0 -0
  102. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_timeout_validation.py +0 -0
  103. {django_lambda_tasks-0.6.5 → django_lambda_tasks-0.6.6}/tests/test_timeouts.py +0 -0
@@ -151,7 +151,7 @@ class SQSLambdaTaskMessage(BaseModel):
151
151
  - A module-level `_cold_start_done` sentinel ensures the sequence runs only once; subsequent warm invocations skip it
152
152
  - Both loaders run unconditionally (outside the `DJANGO_SETTINGS_MODULE` check) — the environment secret may provide that var, and individual secrets may depend on environment-loaded vars
153
153
  - A temporary `StreamHandler` is attached to the `lambda_tasks` logger for the duration of the loaders so their log output is visible before Django's `LOGGING` dictConfig has run; it is removed immediately after so that Django's configuration is the sole authority on logging from that point on
154
- - `resource.setrlimit(RLIMIT_DATA)` is set from `AWS_LAMBDA_FUNCTION_MEMORY_SIZE` so that excessive allocation raises `MemoryError` instead of triggering the OOM killer
154
+ - `resource.setrlimit(RLIMIT_AS)` is set from `AWS_LAMBDA_FUNCTION_MEMORY_SIZE` (Lambda) or the ECS task metadata endpoint (Fargate/Batch) so that excessive allocation raises `MemoryError` instead of triggering the OOM killer
155
155
 
156
156
  `main()` in `handler.py` — AWS Batch container entry point (`python -m lambda_tasks.handler`):
157
157
  - Reads `LAMBDA_TASKS_MESSAGE` env var (the serialized task JSON, set by `submit_batch_job` via container overrides)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-lambda-tasks
3
- Version: 0.6.5
3
+ Version: 0.6.6
4
4
  Summary: Run async tasks in a lambda function
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: awslambdaric
@@ -12,12 +12,13 @@ timeouts. The sequence is guarded by a module-level sentinel so subsequent
12
12
  warm invocations skip it.
13
13
  """
14
14
 
15
+ import json
15
16
  import logging
16
17
  import os
17
18
  import resource
18
19
  import sys
20
+ import urllib.request
19
21
  import uuid
20
- from pathlib import Path
21
22
 
22
23
  import django
23
24
  from django.apps import apps
@@ -32,37 +33,31 @@ _cold_start_done: bool = False
32
33
 
33
34
  _MEMORY_RESERVED_MB = 128
34
35
  _MEMORY_MINIMUM_LIMIT_MB = 64
35
- _CGROUP_V2_MEMORY_MAX_PATH = Path("/sys/fs/cgroup/memory.max")
36
- _CGROUP_V1_MEMORY_LIMIT_PATH = Path("/sys/fs/cgroup/memory/memory.limit_in_bytes")
37
36
 
38
37
 
39
38
  def _get_memory_mb() -> int | None:
40
- """Get container memory limit from Lambda env var or cgroup."""
39
+ """Get container memory limit from Lambda env var or ECS metadata endpoint."""
41
40
  memory_mb_str = os.environ.get("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")
42
41
  if memory_mb_str is not None:
43
42
  return int(memory_mb_str)
44
43
 
45
- try:
46
- value = _CGROUP_V2_MEMORY_MAX_PATH.read_text().strip()
47
- if value == "max":
44
+ metadata_uri = os.environ.get("ECS_CONTAINER_METADATA_URI_V4")
45
+ if metadata_uri is not None:
46
+ try:
47
+ with urllib.request.urlopen(f"{metadata_uri}/task", timeout=2) as response:
48
+ data = json.loads(response.read())
49
+ return int(data["Limits"]["Memory"])
50
+ except Exception:
48
51
  return None
49
- else:
50
- return int(value) // (1024 * 1024)
51
- except (FileNotFoundError, ValueError):
52
- pass
53
52
 
54
- try:
55
- value = _CGROUP_V1_MEMORY_LIMIT_PATH.read_text().strip()
56
- return int(value) // (1024 * 1024)
57
- except (FileNotFoundError, ValueError):
58
- return None
53
+ return None
59
54
 
60
55
 
61
56
  def _set_memory_limit() -> None:
62
57
  """Set RLIMIT_AS from the container memory limit.
63
58
 
64
59
  On Lambda, reads AWS_LAMBDA_FUNCTION_MEMORY_SIZE. On Fargate/Batch,
65
- reads the cgroup v2 memory limit. This causes Python to raise
60
+ queries the ECS task metadata endpoint. This causes Python to raise
66
61
  MemoryError on excessive allocation instead of being killed by the
67
62
  OOM killer.
68
63
 
@@ -7,7 +7,7 @@ packages = ["lambda_tasks"]
7
7
 
8
8
  [project]
9
9
  name = "django-lambda-tasks"
10
- version = "0.6.5"
10
+ version = "0.6.6"
11
11
  description = "Run async tasks in a lambda function"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
@@ -6,30 +6,18 @@ memory allocation raises MemoryError instead of triggering the Lambda OOM
6
6
  killer.
7
7
  """
8
8
 
9
+ import json
9
10
  import resource
10
11
 
11
12
  import lambda_tasks.handler as handler_module
12
13
 
13
14
 
14
- class _FakePath:
15
- def __init__(self, *, content: str | None) -> None:
16
- self._content = content
17
-
18
- def read_text(self) -> str:
19
- if self._content is None:
20
- raise FileNotFoundError
21
- return self._content
22
-
23
-
24
- def fake_path(content: str | None) -> _FakePath:
25
- return _FakePath(content=content)
26
-
27
-
28
15
  class TestMemoryLimitSetDuringColdStart:
29
16
  def test_rlimit_as_set_from_env_var(self, monkeypatch):
30
17
  """When AWS_LAMBDA_FUNCTION_MEMORY_SIZE is set, RLIMIT_AS is configured with 128 MB reserved."""
31
18
  monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
32
19
  monkeypatch.setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "512")
20
+ monkeypatch.delenv("ECS_CONTAINER_METADATA_URI_V4", raising=False)
33
21
  monkeypatch.setattr("lambda_tasks.handler.resolve_environment", lambda: None)
34
22
  monkeypatch.setattr(
35
23
  "lambda_tasks.handler.resolve_secrets_into_env", lambda: None
@@ -48,20 +36,15 @@ class TestMemoryLimitSetDuringColdStart:
48
36
  expected = (512 - 128) * 1024 * 1024
49
37
  assert calls == [(resource.RLIMIT_AS, (expected, expected))]
50
38
 
51
- def test_rlimit_as_not_set_when_env_var_missing_and_no_cgroup(self, monkeypatch):
52
- """When AWS_LAMBDA_FUNCTION_MEMORY_SIZE is not set and cgroup is unavailable, RLIMIT_AS is unchanged."""
39
+ def test_rlimit_as_not_set_when_no_memory_source(self, monkeypatch):
40
+ """When neither Lambda env var nor ECS metadata is available, RLIMIT_AS is unchanged."""
53
41
  monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
54
42
  monkeypatch.delenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", raising=False)
43
+ monkeypatch.delenv("ECS_CONTAINER_METADATA_URI_V4", raising=False)
55
44
  monkeypatch.setattr("lambda_tasks.handler.resolve_environment", lambda: None)
56
45
  monkeypatch.setattr(
57
46
  "lambda_tasks.handler.resolve_secrets_into_env", lambda: None
58
47
  )
59
- monkeypatch.setattr(
60
- handler_module, "_CGROUP_V2_MEMORY_MAX_PATH", fake_path(None)
61
- )
62
- monkeypatch.setattr(
63
- handler_module, "_CGROUP_V1_MEMORY_LIMIT_PATH", fake_path(None)
64
- )
65
48
 
66
49
  calls: list[tuple] = []
67
50
 
@@ -75,25 +58,33 @@ class TestMemoryLimitSetDuringColdStart:
75
58
 
76
59
  assert calls == []
77
60
 
78
- def test_rlimit_as_set_from_cgroup_v2(self, monkeypatch):
79
- """When AWS_LAMBDA_FUNCTION_MEMORY_SIZE is not set, falls back to cgroup v2."""
61
+ def test_rlimit_as_set_from_ecs_metadata(self, monkeypatch):
62
+ """When ECS_CONTAINER_METADATA_URI_V4 is set, queries the task endpoint for memory."""
80
63
  monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
81
64
  monkeypatch.delenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", raising=False)
65
+ monkeypatch.setenv("ECS_CONTAINER_METADATA_URI_V4", "http://169.254.170.2/v4")
82
66
  monkeypatch.setattr("lambda_tasks.handler.resolve_environment", lambda: None)
83
67
  monkeypatch.setattr(
84
68
  "lambda_tasks.handler.resolve_secrets_into_env", lambda: None
85
69
  )
86
- # 2 GB in bytes
87
- monkeypatch.setattr(
88
- handler_module,
89
- "_CGROUP_V2_MEMORY_MAX_PATH",
90
- fake_path(str(2048 * 1024 * 1024)),
91
- )
92
- monkeypatch.setattr(
93
- handler_module,
94
- "_CGROUP_V1_MEMORY_LIMIT_PATH",
95
- fake_path(None),
96
- )
70
+
71
+ task_metadata = json.dumps({"Limits": {"CPU": 2, "Memory": 8192}}).encode()
72
+
73
+ class FakeResponse:
74
+ def read(self):
75
+ return task_metadata
76
+
77
+ def __enter__(self):
78
+ return self
79
+
80
+ def __exit__(self, *args):
81
+ pass
82
+
83
+ def fake_urlopen(url, *, timeout=None):
84
+ assert url == "http://169.254.170.2/v4/task"
85
+ return FakeResponse()
86
+
87
+ monkeypatch.setattr("urllib.request.urlopen", fake_urlopen)
97
88
 
98
89
  calls: list[tuple] = []
99
90
 
@@ -105,23 +96,23 @@ class TestMemoryLimitSetDuringColdStart:
105
96
  handler_module._cold_start_done = False
106
97
  handler_module.handler(event={"Records": []}, context=None)
107
98
 
108
- expected = (2048 - 128) * 1024 * 1024
99
+ expected = (8192 - 128) * 1024 * 1024
109
100
  assert calls == [(resource.RLIMIT_AS, (expected, expected))]
110
101
 
111
- def test_rlimit_as_not_set_when_cgroup_is_max(self, monkeypatch):
112
- """When cgroup memory.max is 'max' (unlimited), RLIMIT_AS is unchanged."""
102
+ def test_rlimit_as_not_set_when_ecs_metadata_fails(self, monkeypatch):
103
+ """When ECS metadata endpoint fails, RLIMIT_AS is unchanged."""
113
104
  monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
114
105
  monkeypatch.delenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", raising=False)
106
+ monkeypatch.setenv("ECS_CONTAINER_METADATA_URI_V4", "http://169.254.170.2/v4")
115
107
  monkeypatch.setattr("lambda_tasks.handler.resolve_environment", lambda: None)
116
108
  monkeypatch.setattr(
117
109
  "lambda_tasks.handler.resolve_secrets_into_env", lambda: None
118
110
  )
119
- monkeypatch.setattr(
120
- handler_module, "_CGROUP_V2_MEMORY_MAX_PATH", fake_path("max")
121
- )
122
- monkeypatch.setattr(
123
- handler_module, "_CGROUP_V1_MEMORY_LIMIT_PATH", fake_path(None)
124
- )
111
+
112
+ def fake_urlopen(url, *, timeout=None):
113
+ raise OSError("connection refused")
114
+
115
+ monkeypatch.setattr("urllib.request.urlopen", fake_urlopen)
125
116
 
126
117
  calls: list[tuple] = []
127
118
 
@@ -135,25 +126,15 @@ class TestMemoryLimitSetDuringColdStart:
135
126
 
136
127
  assert calls == []
137
128
 
138
- def test_rlimit_as_set_from_cgroup_v1(self, monkeypatch):
139
- """When cgroup v2 is unavailable, falls back to cgroup v1 (Fargate)."""
129
+ def test_lambda_env_var_takes_precedence_over_ecs_metadata(self, monkeypatch):
130
+ """AWS_LAMBDA_FUNCTION_MEMORY_SIZE is preferred over ECS metadata."""
140
131
  monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
141
- monkeypatch.delenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", raising=False)
132
+ monkeypatch.setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "512")
133
+ monkeypatch.setenv("ECS_CONTAINER_METADATA_URI_V4", "http://169.254.170.2/v4")
142
134
  monkeypatch.setattr("lambda_tasks.handler.resolve_environment", lambda: None)
143
135
  monkeypatch.setattr(
144
136
  "lambda_tasks.handler.resolve_secrets_into_env", lambda: None
145
137
  )
146
- monkeypatch.setattr(
147
- handler_module,
148
- "_CGROUP_V2_MEMORY_MAX_PATH",
149
- fake_path(None),
150
- )
151
- # 4 GB in bytes
152
- monkeypatch.setattr(
153
- handler_module,
154
- "_CGROUP_V1_MEMORY_LIMIT_PATH",
155
- fake_path(str(4096 * 1024 * 1024)),
156
- )
157
138
 
158
139
  calls: list[tuple] = []
159
140
 
@@ -165,13 +146,14 @@ class TestMemoryLimitSetDuringColdStart:
165
146
  handler_module._cold_start_done = False
166
147
  handler_module.handler(event={"Records": []}, context=None)
167
148
 
168
- expected = (4096 - 128) * 1024 * 1024
149
+ expected = (512 - 128) * 1024 * 1024
169
150
  assert calls == [(resource.RLIMIT_AS, (expected, expected))]
170
151
 
171
152
  def test_memory_limit_set_before_loaders(self, monkeypatch):
172
153
  """The memory limit is set before resolve_environment runs."""
173
154
  monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
174
155
  monkeypatch.setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "256")
156
+ monkeypatch.delenv("ECS_CONTAINER_METADATA_URI_V4", raising=False)
175
157
 
176
158
  call_order: list[str] = []
177
159
 
@@ -207,6 +189,7 @@ class TestMemoryLimitSetDuringColdStart:
207
189
 
208
190
  monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
209
191
  monkeypatch.setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "1024")
192
+ monkeypatch.delenv("ECS_CONTAINER_METADATA_URI_V4", raising=False)
210
193
  monkeypatch.setattr("lambda_tasks.handler.resolve_environment", lambda: None)
211
194
  monkeypatch.setattr(
212
195
  "lambda_tasks.handler.resolve_secrets_into_env", lambda: None
@@ -224,6 +207,7 @@ class TestMemoryLimitSetDuringColdStart:
224
207
  """When AWS_LAMBDA_FUNCTION_MEMORY_SIZE <= reserved, RLIMIT_AS is set to the 64 MB minimum."""
225
208
  monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
226
209
  monkeypatch.setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128")
210
+ monkeypatch.delenv("ECS_CONTAINER_METADATA_URI_V4", raising=False)
227
211
  monkeypatch.setattr("lambda_tasks.handler.resolve_environment", lambda: None)
228
212
  monkeypatch.setattr(
229
213
  "lambda_tasks.handler.resolve_secrets_into_env", lambda: None