11use std:: collections:: BTreeMap ;
2+ use std:: num:: NonZeroU16 ;
23use std:: sync:: Arc ;
34
4- use a3s_box_core:: { resolve_execution, CreateExecutionRequest , ExecutionManager , NetworkMode } ;
5+ use a3s_box_core:: {
6+ resolve_execution, CreateExecutionRequest , ExecutionLease , ExecutionManager ,
7+ ExecutionManagerError , ExecutionPortConnector , NetworkMode ,
8+ } ;
59use chrono:: { DateTime , Duration , Utc } ;
610use thiserror:: Error ;
711
812use super :: {
9- Clock , CompareAndSwapResult , IdentityProviderError , LifecycleError , LifecycleFailure ,
13+ Clock , CompareAndSwapResult , EnvdMode , IdentityProviderError , LifecycleError , LifecycleFailure ,
1014 LifecyclePolicy , LifecycleState , NewSandboxRecord , RepositoryError , SandboxCredentials ,
1115 SandboxIdentityProvider , SandboxListFilter , SandboxPage , SandboxRecord , SandboxRepository ,
1216 SecretToken , TemplateProvider , TemplateProviderError , TokenIssuer , TokenIssuerError ,
1317 TokenResolver , TokenScope ,
1418} ;
19+ use crate :: routing:: ENVD_PORT ;
20+
21+ const RUNTIME_ENVD_READY_TIMEOUT : std:: time:: Duration = std:: time:: Duration :: from_secs ( 30 ) ;
22+ const RUNTIME_ENVD_CONNECT_TIMEOUT : std:: time:: Duration = std:: time:: Duration :: from_millis ( 250 ) ;
23+ const RUNTIME_ENVD_RETRY_INTERVAL : std:: time:: Duration = std:: time:: Duration :: from_millis ( 25 ) ;
1524
1625#[ derive( Debug , Clone ) ]
1726pub struct CreateSandboxRequest {
@@ -79,6 +88,7 @@ pub type ControlServiceResult<T> = std::result::Result<T, ControlServiceError>;
7988pub struct ControlService {
8089 repository : Arc < dyn SandboxRepository > ,
8190 executions : Arc < dyn ExecutionManager > ,
91+ ports : Arc < dyn ExecutionPortConnector > ,
8292 clock : Arc < dyn Clock > ,
8393 identities : Arc < dyn SandboxIdentityProvider > ,
8494 templates : Arc < dyn TemplateProvider > ,
@@ -89,6 +99,7 @@ pub struct ControlService {
8999pub struct ControlServiceDependencies {
90100 pub repository : Arc < dyn SandboxRepository > ,
91101 pub executions : Arc < dyn ExecutionManager > ,
102+ pub ports : Arc < dyn ExecutionPortConnector > ,
92103 pub clock : Arc < dyn Clock > ,
93104 pub identities : Arc < dyn SandboxIdentityProvider > ,
94105 pub templates : Arc < dyn TemplateProvider > ,
@@ -101,6 +112,7 @@ impl ControlService {
101112 Self {
102113 repository : dependencies. repository ,
103114 executions : dependencies. executions ,
115+ ports : dependencies. ports ,
104116 clock : dependencies. clock ,
105117 identities : dependencies. identities ,
106118 templates : dependencies. templates ,
@@ -180,6 +192,24 @@ impl ControlService {
180192 return Err ( error. into ( ) ) ;
181193 }
182194 } ;
195+ if template. envd_mode == EnvdMode :: Runtime {
196+ if let Err ( readiness_error) = self . wait_for_runtime_envd ( & lease) . await {
197+ let cleanup = self
198+ . executions
199+ . kill ( & lease. execution_id , lease. generation )
200+ . await ;
201+ let expected = record. generation ( ) ;
202+ record. mark_failed ( LifecycleFailure :: RuntimeFailed ) ?;
203+ self . replace ( expected, record) . await ?;
204+ return Err ( match cleanup {
205+ Ok ( _) => readiness_error,
206+ Err ( cleanup_error) => ExecutionManagerError :: Internal ( format ! (
207+ "{readiness_error}; runtime cleanup failed: {cleanup_error}"
208+ ) ) ,
209+ }
210+ . into ( ) ) ;
211+ }
212+ }
183213
184214 let expected = record. generation ( ) ;
185215 if let Err ( error) = record. mark_running ( lease) {
@@ -197,6 +227,44 @@ impl ControlService {
197227 } )
198228 }
199229
230+ async fn wait_for_runtime_envd (
231+ & self ,
232+ lease : & ExecutionLease ,
233+ ) -> Result < ( ) , ExecutionManagerError > {
234+ let port = NonZeroU16 :: new ( ENVD_PORT ) . ok_or_else ( || {
235+ ExecutionManagerError :: Internal ( "envd port must be non-zero" . to_string ( ) )
236+ } ) ?;
237+ let deadline = tokio:: time:: Instant :: now ( ) + RUNTIME_ENVD_READY_TIMEOUT ;
238+ loop {
239+ let last_error = match self
240+ . ports
241+ . connect_port (
242+ & lease. execution_id ,
243+ lease. generation ,
244+ port,
245+ RUNTIME_ENVD_CONNECT_TIMEOUT ,
246+ )
247+ . await
248+ {
249+ Ok ( stream) => {
250+ drop ( stream) ;
251+ return Ok ( ( ) ) ;
252+ }
253+ Err ( error @ ExecutionManagerError :: InvalidRequest ( _) )
254+ | Err ( error @ ExecutionManagerError :: Internal ( _) ) => return Err ( error) ,
255+ Err ( error) => error,
256+ } ;
257+ if tokio:: time:: Instant :: now ( ) >= deadline {
258+ return Err ( ExecutionManagerError :: Unavailable ( format ! (
259+ "runtime envd did not become ready within {} seconds: {}" ,
260+ RUNTIME_ENVD_READY_TIMEOUT . as_secs( ) ,
261+ last_error
262+ ) ) ) ;
263+ }
264+ tokio:: time:: sleep ( RUNTIME_ENVD_RETRY_INTERVAL ) . await ;
265+ }
266+ }
267+
200268 pub async fn connect (
201269 & self ,
202270 owner_id : & str ,
0 commit comments