Skip to content

[Feat] SandboxManager 구현#32

Open
chowon442 wants to merge 2 commits into
devfrom
feat/17-sandbox-manager
Open

[Feat] SandboxManager 구현#32
chowon442 wants to merge 2 commits into
devfrom
feat/17-sandbox-manager

Conversation

@chowon442
Copy link
Copy Markdown
Member

@chowon442 chowon442 commented May 15, 2026

📌 관련 이슈

🏷️ PR 타입

  • ✨ 기능 추가 (Feature)
  • 🐛 버그 수정 (Bug Fix)
  • ♻️ 리팩토링 (Refactoring)
  • 📝 문서 수정 (Documentation)
  • 🎨 스타일 변경 (Style)
  • ✅ 테스트 추가 (Test)

📝 작업 내용

  • SandboxManager.create_executor()를 추가해 SandboxConfig 기반 Daytona sandbox 생성과 CodeExecutor 래핑을 처리했습니다.
  • SandboxManager.destroy_executor()를 추가해 executor cleanup 실패 여부와 무관하게 sandbox delete를 시도하고, 단계별 timeout으로 teardown hang을 방지했습니다.
  • #18에서 확장할 최소 CodeExecutor scaffold와 SandboxManager 단위 테스트를 추가했습니다.

📸 스크린샷

  • 테스트 결과
    • uv run pytest tests/common/sandbox/test_manager.py -v → 7 passed
    • uv run pytest tests/common/sandbox -v → 27 passed
    • uv run ruff check src/proovy_agent/common/sandbox tests/common/sandbox → passed
    • uv run pytest → 31 passed

✅ 체크리스트

  • 코드 리뷰를 받을 준비가 완료되었습니다
  • 테스트를 작성하고 모두 통과했습니다
  • 문서를 업데이트했습니다 (필요한 경우)
  • 코드 스타일 가이드를 준수했습니다
  • 셀프 리뷰를 완료했습니다

📎 기타 참고사항

  • CodeExecutor는 #17에서 필요한 최소 scaffold만 포함했습니다. run_python(), context 관리, E2E 통합 테스트는 [Feat] CodeExecutor 구현 + 통합 테스트 (executor.py) #18 범위로 분리했습니다.
  • Daytona SDK 확인 결과 현재 환경에서 AsyncSandbox.delete가 존재해 SandboxManager.destroy_executor()sandbox.delete()를 사용합니다.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 코드를 격리된 환경에서 안전하게 실행할 수 있는 샌드박스 실행 기능이 추가되었습니다.
    • 샌드박스 인스턴스의 자동 생성 및 삭제 관리 기능이 추가되었습니다.
  • 테스트

    • 샌드박스 관리 기능에 대한 포괄적인 테스트 커버리지가 추가되었습니다.

Review Change Stack

Daytona 샌드박스 생성과 cleanup/delete 수명주기를 관리하는 SandboxManager를 추가합니다.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

Warning

Rate limit exceeded

@chowon442 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 39 minutes and 17 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3db9b125-b160-41d6-a934-f53bf5648e80

📥 Commits

Reviewing files that changed from the base of the PR and between fc128ef and ac8def4.

📒 Files selected for processing (2)
  • src/proovy_agent/common/sandbox/executor.py
  • tests/common/sandbox/test_manager.py
📝 Walkthrough

Walkthrough

Daytona 샌드박스 래핑 클래스 CodeExecutor와 생명주기 관리 클래스 SandboxManager를 신규 추가했다. create_executor는 설정으로부터 샌드박스를 생성하고 CodeExecutor로 반환하며, destroy_executor는 cleanup과 delete를 계층적 타임아웃으로 보호하면서 어떤 상황에서도 삭제를 시도하는 cancel-safe 패턴으로 구현했다. 포괄적인 테스트 제품군으로 정상 흐름, 오류 변환, 예외 처리 탄력성을 검증한다.

Changes

CodeExecutor 및 SandboxManager 구현

Layer / File(s) Summary
CodeExecutor 클래스 및 기본 구조
src/proovy_agent/common/sandbox/executor.py
CodeExecutor는 Daytona 샌드박스를 래핑하고, 생성자에서 sandbox/code_timeout/max_output_chars/preamble_code를 내부 필드로 저장한다. sandbox 프로퍼티(읽기 전용)와 비동기 cleanup() 메서드(현재 pass)를 제공한다.
SandboxManager 및 create_executor 생성 흐름
src/proovy_agent/common/sandbox/manager.py (1/2)
SandboxManager는 AsyncDaytona 클라이언트를 보관하고, create_executor()에서 SandboxConfig 또는 settings 기본값으로부터 CreateSandboxFromSnapshotParams를 구성한다. thread_id 라벨, CPU/메모리/디스크 리소스, auto_stop_interval, network_block_all을 구성하여 Daytona에 샌드박스를 생성하고, 성공 시 생성된 샌드박스와 config 값들(코드 타임아웃, 출력 제한, preamble)을 CodeExecutor로 래핑하며, 생성 실패 시 SandboxCreationError로 변환한다.
SandboxManager destroy_executor 계층적 타임아웃 및 정리
src/proovy_agent/common/sandbox/manager.py (2/2)
destroy_executor()는 executor.cleanup()을 3초 타임아웃으로 시도(실패 시 예외 로깅)하고, sandbox.delete()를 5초 타임아웃으로 별도 시도(실패 시 auto_stop 폴백 경고 로깅)한다. 전체 파기 작업은 전달된 timeout으로 보호되며, 타임아웃 시 에러를 기록하되 호출자에게 예외를 전파하지 않는다.
테스트 인프라 및 Fake 타입 정의
tests/common/sandbox/test_manager.py (1/2)
FakeResources, FakeCreateSandboxFromSnapshotParams, FakeSandbox, FakeClient, FakeCodeExecutor를 정의하여 SDK 및 CodeExecutor 의존성을 제거한다. autouse 픽스처에서 manager 모듈의 실제 타입들을 이들 fake 구현으로 monkeypatch한다.
create_executor 및 destroy_executor 포괄적 테스트
tests/common/sandbox/test_manager.py (2/2)
create_executor는 settings 기본값 적용, 명시적 config 반영, SDK 예외 변환을 검증한다. destroy_executor는 정리 순서(cleanup → delete), cleanup 실패 시에도 delete 진행(cancel-safe), delete 실패 예외 억제, 외부 타임아웃 초과 시 정상 반환(예외 없음)을 검증한다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • Team-Proovy/proovy-agent#29: 메인 PR의 SandboxManager/CodeExecutor가 주입하는 preamble_code와 설정 기반 Daytona 샌드박스 필드 및 get_whitelisted_preamble 화이트리스트를 마련한 점에서 retrieved PR과 직접적으로 코드 연동된다.
  • Team-Proovy/proovy-agent#31: 메인 PR의 SandboxManager/CodeExecutor가 사용하는 SandboxConfig 필드들(code_timeout, max_output_chars, preamble_code)과 예외 타입(SandboxCreationError)이 retrieved PR의 샌드박스 모델/예외 정의로부터 직접 맞물려 코드 레벨에서 구성·처리 로직이 연동된다.

Suggested labels

준원

Suggested reviewers

  • gaeunee2

Poem

🐰 샌드박스 요새를 튼튼히 지으니,
생성과 삭제가 춤을 춘다네!
타임아웃 계층으로 무너지지 않고,
cleanup 실패도 delete는 진행—
진정한 cancel-safe 우아함이라! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 '[Feat] SandboxManager 구현'으로 변경 내용의 주요 기능(SandboxManager 구현)을 명확하고 간결하게 설명합니다.
Description check ✅ Passed PR 설명이 템플릿 구조(관련 이슈, PR 타입, 작업 내용, 스크린샷, 체크리스트, 기타 참고사항)를 완전히 따르며 모든 필수 섹션을 충실히 작성했습니다.
Linked Issues check ✅ Passed 코드 변경이 이슈 #17의 요구사항을 충족합니다: SandboxManager 클래스, create_executor/destroy_executor 메서드, 계층적 timeout 패턴, SandboxCreationError 처리, 그리고 7개의 단위 테스트가 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경이 #17 범위 내입니다. CodeExecutor 최소 scaffold, SandboxManager, 그리고 단위 테스트는 모두 이슈 요구사항에 부합하며, run_python()과 E2E 테스트는 의도적으로 #18로 분리했습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/17-sandbox-manager

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chowon442 chowon442 self-assigned this May 15, 2026
@chowon442 chowon442 marked this pull request as ready for review May 15, 2026 15:35
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/proovy_agent/common/sandbox/manager.py (1)

53-53: 💤 Low value

noqa: ASYNC109 주석이 필요한지 재검토하세요.

ASYNC109 규칙은 async 함수에 timeout 매개변수가 있지만 asyncio.wait_for()asyncio.timeout()을 사용하지 않을 때 경고합니다. 그런데 이 함수는 72번 줄에서 asyncio.wait_for(_destroy(), timeout=timeout)으로 timeout을 올바르게 사용하고 있으므로, 이 noqa 주석이 불필요할 수 있습니다.

🔍 noqa 주석 제거 제안
     async def destroy_executor(
         self,
         executor: CodeExecutor,
         *,
-        timeout: float = 10.0,  # noqa: ASYNC109
+        timeout: float = 10.0,
     ) -> None:

Ruff를 다시 실행하여 실제로 ASYNC109 경고가 발생하는지 확인하세요. 경고가 없다면 noqa 주석을 제거하는 것이 코드를 더 깔끔하게 만듭니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/proovy_agent/common/sandbox/manager.py` at line 53, The ASYNC109
suppression on the timeout parameter appears unnecessary because the function
actually uses asyncio.wait_for(_destroy(), timeout=timeout) (see the timeout
argument usage), so run Ruff to confirm whether ASYNC109 is reported; if Ruff
reports no ASYNC109, remove the "noqa: ASYNC109" comment from the timeout
parameter in the function signature (the parameter on the async function that
calls asyncio.wait_for/_destroy) so the code is cleaner, otherwise keep the
noqa.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/proovy_agent/common/sandbox/executor.py`:
- Line 13: 파일의 sandbox 매개변수와 관련 프로퍼티에 Any를 사용해 타입 안전성이 손실되었으니,
src/proovy_agent/common/sandbox/executor.py의 함수/클래스에서 사용된 sandbox 타입 표기(예: 함수
매개변수 sandbox 및 관련 프로퍼티/멤버)를 Any에서 Daytona의 AsyncSandbox로 바꾸고 파일 상단에 from daytona
import AsyncSandbox를 추가하여 정확한 타입을 사용하도록 수정하세요; 구체적으로 AsyncSandbox를 참조하는 위치는
sandbox 매개변수(현재 선언된 sandbox: Any)와 해당 프로퍼티 선언(현재 Any로 표기된 부분)을 찾아 AsyncSandbox로
교체하면 됩니다.

In `@tests/common/sandbox/test_manager.py`:
- Around line 155-164: The test must assert the call order (cleanup before
delete) for SandboxManager.destroy_executor: modify FakeSandbox and
FakeCodeExecutor to append ordered events to a shared list (e.g.,
events.append("executor.cleanup") in FakeCodeExecutor.cleanup and
events.append("sandbox.delete") in FakeSandbox.delete), pass that shared list
into the test's instances, call sandbox_manager.destroy_executor(executor), then
assert events == ["executor.cleanup", "sandbox.delete"] in addition to the
existing cleaned/deleted checks so the cleanup→delete ordering is explicitly
verified.
- Around line 198-207: The test currently only ensures no exception on timeout;
update test_destroy_executor_outer_timeout_returns_without_raising to also
assert that the cleanup/delete did not complete after the short timeout by
checking the executor and sandbox state (e.g., assert executor.cleaned is False
or sandbox.deleted is False) for the SlowCleanupExecutor/FakeSandbox used and
the call to manager.SandboxManager.destroy_executor; if cleanup completion is
set inside cleanup, consider exposing a cleanup_started or cleaned flag on
SlowCleanupExecutor to make this assertion reliable.

---

Nitpick comments:
In `@src/proovy_agent/common/sandbox/manager.py`:
- Line 53: The ASYNC109 suppression on the timeout parameter appears unnecessary
because the function actually uses asyncio.wait_for(_destroy(), timeout=timeout)
(see the timeout argument usage), so run Ruff to confirm whether ASYNC109 is
reported; if Ruff reports no ASYNC109, remove the "noqa: ASYNC109" comment from
the timeout parameter in the function signature (the parameter on the async
function that calls asyncio.wait_for/_destroy) so the code is cleaner, otherwise
keep the noqa.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 425f3f8b-6080-4ba4-ac6c-773092c600aa

📥 Commits

Reviewing files that changed from the base of the PR and between 26b7385 and fc128ef.

📒 Files selected for processing (3)
  • src/proovy_agent/common/sandbox/executor.py
  • src/proovy_agent/common/sandbox/manager.py
  • tests/common/sandbox/test_manager.py

Comment thread src/proovy_agent/common/sandbox/executor.py Outdated
Comment thread tests/common/sandbox/test_manager.py
Comment thread tests/common/sandbox/test_manager.py
CodeExecutor의 sandbox 타입을 구체화하고 destroy_executor 테스트에서 cleanup/delete 순서와 timeout 상태를 검증합니다.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] SandboxManager 구현 (manager.py)

1 participant