Skip to content

Commit b8ecb8a

Browse files
Merge pull request #279 from webtech-network/fix/destroy-sandbox-after-grading
Destroy grading sandboxes at pipeline cleanup
2 parents 8f683c9 + 1cdfb2e commit b8ecb8a

5 files changed

Lines changed: 14 additions & 22 deletions

File tree

.github/workflows/docs-pages.yml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@ on:
88
- 'mkdocs.yml'
99
- 'requirements-docs.txt'
1010
- '.github/workflows/docs-pages.yml'
11-
pull_request:
12-
paths:
13-
- 'docs/**'
14-
- 'mkdocs.yml'
15-
- 'requirements-docs.txt'
16-
- '.github/workflows/docs-pages.yml'
1711
workflow_dispatch:
1812

1913
permissions:
@@ -44,17 +38,14 @@ jobs:
4438
run: mkdocs build --strict
4539

4640
- name: Setup Pages
47-
if: github.event_name != 'pull_request'
4841
uses: actions/configure-pages@v5
4942

5043
- name: Upload Pages artifact
51-
if: github.event_name != 'pull_request'
5244
uses: actions/upload-pages-artifact@v3
5345
with:
5446
path: ./site
5547

5648
deploy:
57-
if: github.event_name != 'pull_request'
5849
needs: build
5950
runs-on: ubuntu-latest
6051
environment:

autograder/autograder.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def run(self, submission: Submission):
8686

8787
pipeline_execution.finish_execution() # Generates GradingResult object in pipeline execution
8888

89-
# Cleanup: Release sandbox back to pool if it was created
89+
# Cleanup: Destroy sandbox if it was created
9090
self._cleanup_sandbox(pipeline_execution)
9191

9292
logger.info(
@@ -98,16 +98,17 @@ def run(self, submission: Submission):
9898
return pipeline_execution
9999

100100
def _cleanup_sandbox(self, pipeline_execution: PipelineExecution) -> None:
101-
"""Release sandbox back to pool after pipeline execution."""
101+
"""Destroy sandbox after pipeline execution to avoid cross-submission reuse."""
102102
try:
103103
sandbox = pipeline_execution.sandbox
104104
if sandbox:
105105
from sandbox_manager.manager import get_sandbox_manager
106106
manager = get_sandbox_manager()
107107
language = pipeline_execution.submission.language
108-
manager.release_sandbox(language, sandbox)
108+
manager.destroy_sandbox(language, sandbox)
109+
pipeline_execution.sandbox = None
109110
logger.info(
110-
"Sandbox released: external_user_id=%s, language=%s",
111+
"Sandbox destroyed: external_user_id=%s, language=%s",
111112
pipeline_execution.submission.user_id,
112113
language.value if language else "none",
113114
)

docs/core/pipeline-architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ LOAD_TEMPLATE -> BUILD_TREE -> SANDBOX -> PRE_FLIGHT -> AI_BATCH -> GRADE -> FOC
2424
2. Each step receives the same `PipelineExecution` and appends one `StepResult`.
2525
3. If a step fails, execution stops early.
2626
4. `finish_execution()` assembles `GradingResult` from grade/focus/feedback artifacts.
27-
5. Sandbox cleanup runs at the end.
27+
5. Sandbox cleanup runs at the end and destroys any sandbox used by the submission.
2828

2929
## Core data contract
3030

docs/pipeline/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Key behaviors:
3939
- After each step, the pipeline checks the latest `StepResult`. If it failed, the pipeline sets its status to `FAILED` and breaks.
4040
- Unhandled exceptions set the status to `INTERRUPTED`.
4141
- After all steps (or early exit), `finish_execution()` is called to assemble the final `GradingResult`.
42-
- Sandbox cleanup always runs at the end, regardless of success or failure.
42+
- Sandbox cleanup always runs at the end, regardless of success or failure, and destroys the sandbox used by that submission.
4343

4444
### PipelineExecution
4545

@@ -191,7 +191,7 @@ When a step fails:
191191
2. The pipeline detects the failure via `get_previous_step()` and calls `set_failure()`.
192192
3. No further steps are executed.
193193
4. `finish_execution()` is called — since the status is `FAILED`, `result` remains `None`.
194-
5. Sandbox cleanup always runs: if a Sandbox step created a sandbox, it is released back to the pool regardless of pipeline outcome.
194+
5. Sandbox cleanup always runs: if a Sandbox step created a sandbox, it is destroyed regardless of pipeline outcome, and the pool replenishes with a fresh container.
195195

196196
For unhandled exceptions, the status is set to `INTERRUPTED` and the same cleanup logic applies.
197197

tests/unit/pipeline/test_sandbox_cleanup.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def pipeline_exec(submission):
2424

2525
@patch("sandbox_manager.manager.get_sandbox_manager")
2626
def test_cleanup_sandbox_called_after_pipeline_run(mock_get_manager, pipeline_exec):
27-
"""Verifies that _cleanup_sandbox is called and releases the sandbox correctly."""
27+
"""Verifies that _cleanup_sandbox is called and destroys the sandbox correctly."""
2828
mock_manager = mock_get_manager.return_value
2929
mock_sandbox = MagicMock(spec=SandboxContainer)
3030

@@ -39,11 +39,11 @@ def test_cleanup_sandbox_called_after_pipeline_run(mock_get_manager, pipeline_ex
3939
# Let's test _cleanup_sandbox directly to verify it uses the property
4040
pipeline._cleanup_sandbox(pipeline_exec)
4141

42-
mock_manager.release_sandbox.assert_called_once_with(Language.PYTHON, mock_sandbox)
42+
mock_manager.destroy_sandbox.assert_called_once_with(Language.PYTHON, mock_sandbox)
4343

4444
@patch("sandbox_manager.manager.get_sandbox_manager")
4545
def test_cleanup_sandbox_even_on_step_failure(mock_get_manager, pipeline_exec):
46-
"""Ensures that sandbox cleanup occurs even if the pipeline status is FAILED."""
46+
"""Ensures sandbox destruction occurs even if the pipeline status is FAILED."""
4747
mock_manager = mock_get_manager.return_value
4848
mock_sandbox = MagicMock(spec=SandboxContainer)
4949

@@ -55,16 +55,16 @@ def test_cleanup_sandbox_even_on_step_failure(mock_get_manager, pipeline_exec):
5555
pipeline._cleanup_sandbox(pipeline_exec)
5656

5757
# Cleanup should still happen
58-
mock_manager.release_sandbox.assert_called_once_with(Language.PYTHON, mock_sandbox)
58+
mock_manager.destroy_sandbox.assert_called_once_with(Language.PYTHON, mock_sandbox)
5959

6060
@patch("sandbox_manager.manager.get_sandbox_manager")
6161
def test_cleanup_no_sandbox_no_call(mock_get_manager, pipeline_exec):
62-
"""Verifies that no sandbox release call is made if no sandbox is attached."""
62+
"""Verifies that no sandbox destroy call is made if no sandbox is attached."""
6363
mock_manager = mock_get_manager.return_value
6464

6565
pipeline_exec.sandbox = None
6666

6767
pipeline = AutograderPipeline()
6868
pipeline._cleanup_sandbox(pipeline_exec)
6969

70-
mock_manager.release_sandbox.assert_not_called()
70+
mock_manager.destroy_sandbox.assert_not_called()

0 commit comments

Comments
 (0)