(
client: &reqwest::Client,
api_key: &str,
code: &str,
)
| 550 | } |
| 551 | |
| 552 | async fn run_in_e2b( |
| 553 | client: &reqwest::Client, |
| 554 | api_key: &str, |
| 555 | code: &str, |
| 556 | ) -> Result<ExecOutcome, String> { |
| 557 | // Block here until we have a concurrency slot. The slot is held for the |
| 558 | // whole sandbox lifetime, including the exec stream and the DELETE call, |
| 559 | // so we never count more live sandboxes than E2B allows. |
| 560 | let _permit = e2b_semaphore() |
| 561 | .acquire() |
| 562 | .await |
| 563 | .map_err(|_| "ExecPython semaphore closed (node-runner is shutting down)".to_string())?; |
| 564 | |
| 565 | let handle = create_sandbox_with_retry(client, api_key).await?; |
| 566 | |
| 567 | // Always tear down the sandbox, even if exec failed, so we don't pay for idle. |
| 568 | let exec_result = exec_in_sandbox( |
| 569 | client, |
| 570 | &handle.sandbox_id, |
| 571 | &handle.envd_token, |
| 572 | handle.traffic_token.as_deref(), |
| 573 | code, |
| 574 | ).await; |
| 575 | |
| 576 | delete_sandbox(client, api_key, &handle.sandbox_id).await; |
| 577 | |
| 578 | exec_result |
| 579 | } |
| 580 | |
| 581 | /// Typed E2B JSONL event. Known variants are strict; unknown tags map to |
| 582 | /// `Unknown` which we log and skip. That way a new E2B event type surfaces in |
no test coverage detected