| 185 | 185 | | limits: limits::Limits, |
| 186 | 186 | | /// Whether repositories' Actions are on, read once per request. |
| 187 | 187 | | actions_on: protection::ActionsOn, |
| 188 | + | /// In a request, queued jobs are not started inline: whoever made the |
| 189 | + | /// request may be gone before a sandbox starts, which can take seconds |
| 190 | + | /// while Cloudflare has no container free, and the start would be |
| 191 | + | /// cancelled with it, after the jobs were marked as running. They are |
| 192 | + | /// started after the answer instead (`fetch`, `wait_until`). |
| 193 | + | defer_starts: bool, |
| 194 | + | /// Whether this request queued jobs or freed room for them. |
| 195 | + | starts_wanted: std::cell::Cell<bool>, |
| 188 | 196 | | } |
| 189 | 197 | | |
| 190 | 198 | | impl Actions { |
| ⋯ |
| 205 | 213 | | blob_key: env.secret("ACTIONS_KEY").ok().map(|key| runtime::blob_key(&key.to_string())), |
| 206 | 214 | | limits: limits::Limits::from_env(env), |
| 207 | 215 | | actions_on: protection::ActionsOn::default(), |
| 216 | + | defer_starts: false, |
| 217 | + | starts_wanted: std::cell::Cell::new(false), |
| 208 | 218 | | }) |
| 209 | 219 | | } |
| 210 | 220 | | |
| ⋯ |
| 284 | 294 | | } |
| 285 | 295 | | |
| 286 | 296 | | #[event(fetch)] |
| 287 | | − | async fn fetch(mut request: Request, env: Env, _ctx: Context) -> Result<Response> { |
| 297 | + | async fn fetch(mut request: Request, env: Env, ctx: Context) -> Result<Response> { |
| 288 | 298 | | let Some(method) = rpc_method(&request) else { |
| 289 | 299 | | return Response::error("Not found", 404); |
| 290 | 300 | | }; |
| 291 | 301 | | let body: Value = request.json().await?; |
| 292 | | − | let service = Actions::new(&env)?; |
| 293 | | − | match method.as_str() { |
| 302 | + | let service = Actions { defer_starts: true, ..Actions::new(&env)? }; |
| 303 | + | let response = answer(&service, &method, body).await; |
| 304 | + | // The jobs this request queued or made room for start after the |
| 305 | + | // answer, where the caller leaving cannot cancel them. A start that is |
| 306 | + | // cut short anyway is tried again (FIRST_REPORT_MS). |
| 307 | + | if service.starts_wanted.get() { |
| 308 | + | ctx.wait_until(async move { |
| 309 | + | match Actions::new(&env) { |
| 310 | + | Ok(service) => { |
| 311 | + | if let Err(error) = service.start_queued().await { |
| 312 | + | worker::console_error!("actions: starting queued jobs after {method} failed: {error}"); |
| 313 | + | } |
| 314 | + | } |
| 315 | + | Err(error) => worker::console_error!("actions: could not start: {error}"), |
| 316 | + | } |
| 317 | + | }); |
| 318 | + | } |
| 319 | + | response |
| 320 | + | } |
| 321 | + | |
| 322 | + | /// One RPC's answer. |
| 323 | + | async fn answer(service: &Actions, method: &str, body: Value) -> Result<Response> { |
| 324 | + | match method { |
| 294 | 325 | | "workflows" => reply(&service.workflows(args(body)?).await?), |
| 295 | 326 | | "workflow" => reply(&service.workflow(args(body)?).await?), |
| 296 | 327 | | "runs" => reply(&service.runs(args(body)?).await?), |