jev_provider_helper.mojo (28411B)
1 """Strict scripted Jev provider HTTP fixture (ADR-0012 D29 / ADR-0014 D33). 2 3 Same parent-owned lifecycle and phase/case/reason reporting as the MaxLocal 4 fixture: parent-enforced startup/read/write/wait deadlines (FX06), bounded 5 reporting (FX07) and exception-safe reaping (FX08). 6 """ 7 8 from std.collections import List 9 10 from flare.net import SocketAddr 11 from flare.tcp import TcpListener 12 from flare.utils import usleep 13 14 from parent_lifecycle import ( 15 FIXTURE_DEFAULT_DEADLINE_MS, 16 TERMINATION_GRACE_MS, 17 CleanupGuard, 18 PipedChildState, 19 PipeFds, 20 ProcessStatus, 21 child_exit, 22 close_fd, 23 dup2_fd, 24 finalize_owned_failure, 25 fork_owned_or_close, 26 make_pipe, 27 parse_ready_or_cleanup, 28 piped_child_state, 29 set_alarm, 30 sleep_ms, 31 write_raw, 32 ) 33 from strict_fixture import ( 34 STRICT_MAX_REPORT_BYTES, 35 json_escape, 36 STRICT_COMPLETION_GRACE_MS, 37 ConnectionReader, 38 ExchangeScript, 39 FramedRequest, 40 ServeReport, 41 authorization_reason, 42 exchange_script, 43 parse_report, 44 render_response, 45 report_line, 46 report_status_matches_exit, 47 serve_scripts, 48 validate_convenience, 49 verify_exchange, 50 ) 51 52 53 comptime STUB_ALARM_SECONDS = 20 54 comptime JEV_INTENDED_PATH = "/v1/systemone" 55 56 57 def allowed_jev_paths() -> List[String]: 58 var paths = List[String]() 59 paths.append(JEV_INTENDED_PATH) 60 return paths^ 61 62 63 def allowed_jev_methods() -> List[String]: 64 var methods = List[String]() 65 methods.append("POST") 66 return methods^ 67 68 69 def require_bearer_for(mode: String) -> Bool: 70 return False 71 72 73 def analysis() -> String: 74 return ( 75 '{"model":"jev-1.13.0","answers":{' 76 '"supply_status":{"type":"choice","choice":"offered","probabilities":{"offered":1.0,"forecast":0.0,"unclear":0.0},"confidence":1.0},' 77 '"seconds_ok":{"type":"noul","noul":0.9},' 78 '"culinary_fit":{"type":"score","score":2,"legend":{"0":"u","1":"l","2":"s"},"probabilities":{"0":0.0,"1":0.0,"2":1.0},"confidence":1.0}},' 79 '"usage":{"input_tokens":10,"output_tokens":5}}' 80 ) 81 82 83 def _status(mode: String) -> Int: 84 if mode == "rate_limit": 85 return 429 86 if mode == "server_error": 87 return 500 88 if mode == "overloaded": 89 return 529 90 if mode == "auth": 91 return 401 92 return 200 93 94 95 def _raw_response(mode: String) -> String: 96 if mode == "truncated": 97 return ( 98 "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\n" 99 "content-length: 999\r\nconnection: close\r\n\r\n" 100 '{"model":"jev' 101 ) 102 if mode == "redirect": 103 return ( 104 "HTTP/1.1 302 Found\r\nlocation:" 105 " http://127.0.0.1:1/steal\r\ncontent-length:" 106 " 0\r\nconnection: close\r\n\r\n" 107 ) 108 return "" 109 110 111 def _delay_ms(mode: String) -> Int: 112 if mode == "slow": 113 return 2000 114 return 0 115 116 117 def header_names_json(headers_raw: String) -> String: 118 """Captured request header *names* as a JSON array, values redacted. 119 120 H006 characterization: the loopback fixture can report which headers it 121 captured without ever embedding a real or sentinel credential value. 122 """ 123 var out = String("[") 124 var first = True 125 for line in headers_raw.split("\r\n"): 126 var entry = String(line) 127 var colon = entry.find(":") 128 if colon <= 0: 129 continue 130 var name = String(entry[byte=0:colon]).strip().lower() 131 if name == "": 132 continue 133 if not first: 134 out += "," 135 out += json_escape(name) 136 first = False 137 return out + "]" 138 139 140 def _body(mode: String) -> String: 141 if mode == "ok" or mode == "slow": 142 return analysis() 143 if mode == "rate_limit": 144 return '{"error":{"message":"slow down"}}' 145 if mode == "server_error": 146 return '{"error":{"message":"boom"}}' 147 if mode == "overloaded": 148 return '{"error":{"message":"overloaded"}}' 149 if mode == "auth": 150 return '{"error":{"message":"bad key"}}' 151 if mode == "malformed_json": 152 return "not json" 153 if mode == "model_mismatch": 154 return analysis().replace("jev-1.13.0", "jev-other") 155 return '{"error":{"message":"unsupported_mode"}}' 156 157 158 def _build_script(mode: String, framed: FramedRequest) -> ExchangeScript: 159 var script = exchange_script( 160 mode, framed.method, JEV_INTENDED_PATH, _status(mode), "" 161 ) 162 script.delay_ms = _delay_ms(mode) 163 script.close_connection = True 164 script.require_bearer = require_bearer_for(mode) 165 var raw = _raw_response(mode) 166 if raw != "": 167 script.raw_response = raw 168 elif mode == "echo_headers": 169 # Header capture with values redacted: only names are echoed. 170 script.response_body = ( 171 '{"captured_headers":' + header_names_json(framed.headers_raw) + "}" 172 ) 173 elif mode == "echo_authorization": 174 var auth = authorization_reason(framed.headers_raw, True) 175 if auth != "": 176 script.status = 401 177 script.response_body = '{"error":{"message":"' + auth + '"}}' 178 else: 179 script.echo_authorization = True 180 else: 181 script.response_body = _body(mode) 182 return script^ 183 184 185 def serve_jev(port: Int, mode: String, requests: Int) raises -> ServeReport: 186 var allowed = allowed_jev_paths() 187 var methods = allowed_jev_methods() 188 var listener = TcpListener.bind(SocketAddr.localhost(UInt16(port))) 189 var actual_port = Int(listener.local_addr().port) 190 write_raw(1, "ready " + String(actual_port) + "\n") 191 var request_count = 0 192 var connection_count = 0 193 try: 194 while request_count < requests: 195 var stream = listener.accept() 196 connection_count += 1 197 var reader = ConnectionReader(stream^) 198 while request_count < requests: 199 var framed = reader.read() 200 if not framed.ok: 201 if framed.error == "empty": 202 if request_count < requests: 203 return ServeReport( 204 False, 205 "accounting", 206 mode, 207 "missing_exchanges", 208 request_count, 209 connection_count, 210 ) 211 break 212 return ServeReport( 213 False, 214 "read", 215 mode, 216 framed.error, 217 request_count, 218 connection_count, 219 ) 220 var reason = validate_convenience( 221 framed, allowed, methods, False 222 ) 223 if reason != "": 224 return ServeReport( 225 False, 226 "exchange", 227 mode, 228 reason, 229 request_count, 230 connection_count, 231 ) 232 var next_index = request_count + 1 233 var script = _build_script(mode, framed) 234 var verify = verify_exchange(script, framed) 235 if verify != "": 236 return ServeReport( 237 False, 238 "exchange", 239 mode, 240 verify, 241 request_count, 242 connection_count, 243 ) 244 if next_index == requests: 245 var extra = reader.probe_completion( 246 STRICT_COMPLETION_GRACE_MS 247 ) 248 if extra != "": 249 return ServeReport( 250 False, 251 "accounting", 252 mode, 253 extra, 254 request_count, 255 connection_count, 256 ) 257 request_count = next_index 258 if script.delay_ms > 0: 259 usleep(script.delay_ms * 1000) 260 reader.write_all( 261 render_response( 262 script, 263 framed.headers_raw, 264 request_count, 265 connection_count, 266 ) 267 ) 268 if script.close_connection: 269 break 270 if request_count < requests: 271 return ServeReport( 272 False, 273 "accounting", 274 mode, 275 "missing_exchanges", 276 request_count, 277 connection_count, 278 ) 279 return ServeReport( 280 True, "complete", mode, "ok", request_count, connection_count 281 ) 282 except: 283 return ServeReport( 284 False, "read", mode, "io_error", request_count, connection_count 285 ) 286 287 288 # ── Parent side ───────────────────────────────────────────────────────────── 289 290 291 struct SpawnedJevStub(Movable): 292 """Single-owner Jev fixture handle; the body receives a view.""" 293 294 var pid: Int 295 var port: Int 296 var state: PipedChildState 297 298 def __init__(out self, pid: Int, port: Int, var state: PipedChildState): 299 self.pid = pid 300 self.port = port 301 self.state = state^ 302 303 def __enter__(mut self) -> SpawnedJevStubView: 304 return SpawnedJevStubView(self.pid, UnsafePointer(to=self)) 305 306 def __exit__(mut self): 307 self.cleanup() 308 309 def cleanup(mut self): 310 """Fast, non-raising owned cleanup for assertion/error/early return. 311 312 Ownership is released only once the child is provably collected. An 313 uncertain wait keeps the handle retryable, records the failure in the 314 required caller-held guard and retains the exact pid/report descriptor 315 for recovery, instead of being silently marked complete. Never raises, 316 so a body/assertion cause is preserved at scope exit. 317 """ 318 if self.state.reaped: 319 return 320 var report_fd = self.state.report_fd 321 var status = self.state.terminate_once(self.pid, TERMINATION_GRACE_MS) 322 self.state.status = status.copy() 323 if status.cleanup_proved(): 324 self.state.reaped = True 325 self.state.close_reader() 326 self.state.guard[].resolve_pid(self.pid) 327 return 328 self.state.record_unproved( 329 self.pid, 330 report_fd, 331 "owned-child cleanup unproved", 332 "unreaped:" + status.describe(), 333 ) 334 335 def ok(self) -> Bool: 336 return self.state.ok 337 338 def phase(self) -> String: 339 return String(self.state.phase) 340 341 def failure_case(self) -> String: 342 return String(self.state.case_label) 343 344 def reason(self) -> String: 345 return String(self.state.reason) 346 347 def request_count(self) -> Int: 348 return self.state.requests 349 350 def connection_count(self) -> Int: 351 return self.state.connections 352 353 def cleanup_error(self) -> String: 354 return String(self.state.cleanup_error) 355 356 def describe(self) -> String: 357 return ( 358 "phase=" 359 + self.state.phase 360 + " case=" 361 + self.state.case_label 362 + " reason=" 363 + self.state.reason 364 + " requests=" 365 + String(self.state.requests) 366 + " connections=" 367 + String(self.state.connections) 368 ) 369 370 def status(mut self) -> ProcessStatus: 371 """Observe child status without losing ownership or report truth. 372 373 A terminal observation is cached so a later ``reap`` never performs a 374 new wait on a stale/reused identity. A transient ``wait_error`` or any 375 other nonterminal result is returned but deliberately *not* cached, so 376 it stays retryable and cannot overwrite a valid terminal result. 377 """ 378 if self.state.reaped: 379 return self.state.status.copy() 380 if self.state.observed_valid: 381 return self.state.observed.copy() 382 var st = self.state.wait_once(self.pid) 383 if st.state == "reaped" or st.state == "gone": 384 self.state.observed = st.copy() 385 self.state.observed_valid = True 386 return st^ 387 388 def reap(mut self): 389 """Reap the owned child within one declared finite work budget. 390 391 Never raises (so ``__exit__`` cannot mask a body error). Startup, wait, 392 report line and EOF drain share ``deadline_ms`` measured from spawn: an 393 expired budget fails with ``work_budget_expired`` and only the bounded 394 cleanup allowance, and no fresh successful interval is granted. 395 """ 396 if self.state.reaped: 397 return 398 if self.state.faults.wait_delay_ms > 0: 399 sleep_ms(self.state.faults.wait_delay_ms) 400 self.state.faults.wait_delay_ms = 0 401 var remaining = self.state.work_remaining_ms() 402 if remaining <= 0: 403 self.state.store( 404 False, "watchdog", "-", "work_budget_expired", 0, 0 405 ) 406 var expired = self.state.terminate_once( 407 self.pid, TERMINATION_GRACE_MS 408 ) 409 self.state.status = expired.copy() 410 if expired.cleanup_proved(): 411 self.state.reaped = True 412 self.state.close_reader() 413 self.state.guard[].resolve_pid(self.pid) 414 else: 415 self.state.reason = "work_budget_expired_unreaped" 416 self.state.record_unproved( 417 self.pid, 418 self.state.report_fd, 419 "owned-child cleanup unproved", 420 "unreaped:" + expired.describe(), 421 ) 422 return 423 var status = ProcessStatus("pending", False, -1, 0, 0, "") 424 if self.state.observed_valid: 425 status = self.state.observed.copy() 426 else: 427 status = self.state.wait_until(self.pid, remaining) 428 self.state.status = status.copy() 429 if status.state == "running" or status.state == "interrupted": 430 var term = self.state.terminate_once(self.pid, TERMINATION_GRACE_MS) 431 self.state.status = term.copy() 432 self.state.store(False, "watchdog", "-", "timeout", 0, 0) 433 if term.cleanup_proved(): 434 self.state.reaped = True 435 self.state.close_reader() 436 self.state.guard[].resolve_pid(self.pid) 437 else: 438 self.state.reason = "timeout_unreaped" 439 self.state.record_unproved( 440 self.pid, 441 self.state.report_fd, 442 "owned-child cleanup unproved", 443 "unreaped:" + term.describe(), 444 ) 445 return 446 if status.state == "gone": 447 # No waitable owned child remains: cleanup is proved without a 448 # signal and the report cannot be trusted, but ownership is done. 449 self.state.store(False, "watchdog", "-", "gone", 0, 0) 450 self.state.reaped = True 451 self.state.close_reader() 452 self.state.guard[].resolve_pid(self.pid) 453 return 454 if status.state == "wait_error": 455 # Identity/ownership is unproved: retain it for a retry and record 456 # the uncertainty instead of claiming the child was collected. 457 self.state.store(False, "watchdog", "-", "wait_error", 0, 0) 458 self.state.record_unproved( 459 self.pid, 460 self.state.report_fd, 461 "owned-child wait unproved", 462 "wait_error:" + status.error, 463 ) 464 return 465 if status.state != "reaped": 466 # Unexpected nonterminal/unknown taxonomy: fail closed and keep the 467 # exact ownership so a later retry can still collect it. 468 self.state.store(False, "watchdog", "-", "unexpected_status", 0, 0) 469 self.state.record_unproved( 470 self.pid, 471 self.state.report_fd, 472 "owned-child unexpected status", 473 "unexpected:" + status.describe(), 474 ) 475 return 476 var report_text = "" 477 var report_error = "" 478 var report_remaining = self.state.work_remaining_ms() 479 if report_remaining <= 0: 480 report_error = "report_budget_expired" 481 else: 482 try: 483 report_text = self.state.read_line( 484 STRICT_MAX_REPORT_BYTES, report_remaining 485 ) 486 if self.state.last_terminated: 487 var drain_remaining = self.state.work_remaining_ms() 488 if drain_remaining <= 0: 489 report_error = "drain_deadline_expired" 490 else: 491 var surplus = self.state.drain_surplus( 492 STRICT_MAX_REPORT_BYTES, drain_remaining 493 ) 494 if surplus > 0: 495 report_error = "duplicate_report" 496 elif report_text.byte_length() > 0: 497 # Bytes at EOF without a terminating newline are a 498 # truncated report, never a complete one. 499 report_error = "unterminated_report" 500 except e: 501 report_error = String(e) 502 self.state.close_reader() 503 if report_text == "" and report_error == "": 504 if status.exited and status.exit_code == 0: 505 self.state.store(False, "startup", "-", "missing_report", 0, 0) 506 elif status.exited: 507 self.state.store( 508 False, 509 "startup", 510 "-", 511 "exit_" + String(status.exit_code), 512 0, 513 0, 514 ) 515 else: 516 self.state.store( 517 False, 518 "watchdog", 519 "-", 520 "signal_" + String(status.signal), 521 0, 522 0, 523 ) 524 self.state.reaped = True 525 self.state.guard[].resolve_pid(self.pid) 526 return 527 if report_error != "": 528 self.state.store(False, "parse", "-", report_error, -1, -1) 529 self.state.reaped = True 530 self.state.guard[].resolve_pid(self.pid) 531 return 532 var parsed = parse_report(report_text) 533 if parsed.phase == "parse": 534 self.state.store(False, "parse", "-", parsed.reason, -1, -1) 535 self.state.reaped = True 536 self.state.guard[].resolve_pid(self.pid) 537 return 538 self.state.store( 539 parsed.ok, 540 parsed.phase, 541 parsed.case_label, 542 parsed.reason, 543 parsed.requests, 544 parsed.connections, 545 ) 546 if not report_status_matches_exit( 547 status.exited, status.exit_code, parsed.ok 548 ): 549 self.state.ok = False 550 self.state.phase = "startup" 551 self.state.reason = "report_status_mismatch_" + status.describe() 552 elif parsed.ok and parsed.requests != self.state.expected_requests: 553 self.state.ok = False 554 self.state.phase = "accounting" 555 self.state.reason = "request_count_mismatch" 556 elif parsed.ok and ( 557 parsed.connections < 1 558 or parsed.connections > self.state.expected_requests 559 ): 560 self.state.ok = False 561 self.state.phase = "accounting" 562 self.state.reason = "connection_count_invalid" 563 self.state.reaped = True 564 self.state.guard[].resolve_pid(self.pid) 565 566 def wait(mut self) raises: 567 self.reap() 568 if not self.state.ok: 569 raise Error("fixture-failure " + self.describe()) 570 571 def terminate(mut self) raises: 572 if self.state.reaped: 573 return 574 var report_fd = self.state.report_fd 575 var status = self.state.terminate_once(self.pid, TERMINATION_GRACE_MS) 576 self.state.status = status.copy() 577 if status.cleanup_proved(): 578 self.state.reaped = True 579 self.state.close_reader() 580 self.state.guard[].resolve_pid(self.pid) 581 return 582 self.state.record_unproved( 583 self.pid, 584 report_fd, 585 "owned-child cleanup unproved", 586 "unreaped:" + status.describe(), 587 ) 588 raise Error("lifecycle: owned child not reaped: " + status.describe()) 589 590 591 struct SpawnedJevStubView(Movable): 592 """Body-scope view of an owned Jev fixture handle.""" 593 594 var pid: Int 595 var target: UnsafePointer[SpawnedJevStub, MutAnyOrigin] 596 597 def __init__( 598 out self, pid: Int, target: UnsafePointer[SpawnedJevStub, MutAnyOrigin] 599 ): 600 self.pid = pid 601 self.target = target 602 603 def ok(self) -> Bool: 604 return self.target[].ok() 605 606 def phase(self) -> String: 607 return self.target[].phase() 608 609 def failure_case(self) -> String: 610 return self.target[].failure_case() 611 612 def reason(self) -> String: 613 return self.target[].reason() 614 615 def request_count(self) -> Int: 616 return self.target[].request_count() 617 618 def connection_count(self) -> Int: 619 return self.target[].connection_count() 620 621 def cleanup_error(self) -> String: 622 return self.target[].cleanup_error() 623 624 def describe(self) -> String: 625 return self.target[].describe() 626 627 def status(mut self) -> ProcessStatus: 628 return self.target[].status() 629 630 def reap(mut self): 631 self.target[].reap() 632 633 def wait(mut self) raises: 634 self.target[].wait() 635 636 def terminate(mut self) raises: 637 self.target[].terminate() 638 639 def inject_cleanup_failure(mut self): 640 """Test-only bounded fault: force one unproved cleanup attempt. 641 642 The exact-owned child keeps running, so the follow-up retry exercises 643 the real recoverable ownership path rather than a synthetic identity. 644 """ 645 self.target[].state.faults.cleanup_failures += 1 646 647 def inject_wait_error(mut self): 648 """Test-only bounded fault: force one transient wait error.""" 649 self.target[].state.faults.wait_errors += 1 650 651 def inject_nonterminal_status(mut self): 652 """Test-only bounded fault: force one unexpected nonterminal status.""" 653 self.target[].state.faults.nonterminal += 1 654 655 def inject_wait_delay_ms(mut self, ms: Int): 656 """Test-only bounded fault: consume work-budget time in the wait phase. 657 """ 658 self.target[].state.faults.wait_delay_ms = ms 659 660 661 @fieldwise_init 662 struct SpawnedJevStubAuto(Movable): 663 var port: Int 664 var stub: SpawnedJevStub 665 666 def __enter__(mut self) -> SpawnedJevStubAutoView: 667 return SpawnedJevStubAutoView( 668 self.port, 669 SpawnedJevStubView(self.stub.pid, UnsafePointer(to=self.stub)), 670 ) 671 672 def __exit__(mut self): 673 self.stub.cleanup() 674 675 676 struct SpawnedJevStubAutoView(Movable): 677 """Body-scope view of an auto-port Jev fixture handle.""" 678 679 var port: Int 680 var stub: SpawnedJevStubView 681 682 def __init__(out self, port: Int, var stub: SpawnedJevStubView): 683 self.port = port 684 self.stub = stub^ 685 686 687 def reserve_jev_port() raises -> Int: 688 var listener = TcpListener.bind(SocketAddr.localhost(0)) 689 var port = Int(listener.local_addr().port) 690 listener.close() 691 return port 692 693 694 def spawn_jev_stub_auto( 695 mode: String, 696 requests: Int, 697 mut guard: CleanupGuard, 698 deadline_ms: Int = FIXTURE_DEFAULT_DEADLINE_MS, 699 ) raises -> SpawnedJevStubAuto: 700 var stub = _spawn_jev_stub(0, mode, requests, deadline_ms, guard) 701 return SpawnedJevStubAuto(port=stub.port, stub=stub^) 702 703 704 def spawn_jev_stub( 705 port: Int, 706 mode: String, 707 requests: Int, 708 mut guard: CleanupGuard, 709 deadline_ms: Int = FIXTURE_DEFAULT_DEADLINE_MS, 710 ) raises -> SpawnedJevStub: 711 return _spawn_jev_stub(port, mode, requests, deadline_ms, guard) 712 713 714 def serve_jev_scripted( 715 port: Int, var scripts: List[ExchangeScript] 716 ) raises -> ServeReport: 717 var listener = TcpListener.bind(SocketAddr.localhost(UInt16(port))) 718 var actual_port = Int(listener.local_addr().port) 719 write_raw(1, "ready " + String(actual_port) + "\n") 720 return serve_scripts(listener, scripts^, "scripted") 721 722 723 def spawn_jev_scripted_auto( 724 var scripts: List[ExchangeScript], 725 mut guard: CleanupGuard, 726 deadline_ms: Int = FIXTURE_DEFAULT_DEADLINE_MS, 727 ) raises -> SpawnedJevStubAuto: 728 return _spawn_jev_scripted(0, scripts^, deadline_ms, guard) 729 730 731 def _spawn_child_or_cleanup( 732 pipe: PipeFds, 733 pid: Int, 734 mode: String, 735 deadline_ms: Int, 736 requests: Int, 737 guard: UnsafePointer[CleanupGuard, MutAnyOrigin], 738 ) raises -> SpawnedJevStub: 739 """Build the owned state, read exact readiness, or clean up and raise.""" 740 var state = piped_child_state( 741 pid, pipe.read_fd, deadline_ms, requests, guard 742 ) 743 var ready_line = "" 744 try: 745 ready_line = state.read_line(STRICT_MAX_REPORT_BYTES, deadline_ms) 746 except e: 747 var st = finalize_owned_failure( 748 state, pid, "jev startup readiness cleanup unproved" 749 ) 750 raise Error( 751 "jev stub readiness failed (" 752 + String(e) 753 + " pid=" 754 + String(pid) 755 + " / " 756 + st.describe() 757 + " cleanup=" 758 + ("proved" if st.cleanup_proved() else "unreaped") 759 + ")" 760 ) 761 var reported_port = 0 762 try: 763 reported_port = parse_ready_or_cleanup(pid, ready_line, 256) 764 except e: 765 var st = finalize_owned_failure( 766 state, pid, "jev startup malformed-readiness cleanup unproved" 767 ) 768 raise Error( 769 "jev stub malformed readiness (" 770 + String(e) 771 + " pid=" 772 + String(pid) 773 + " cleanup=" 774 + ("proved" if st.cleanup_proved() else "unreaped") 775 + ")" 776 ) 777 _ = mode 778 return SpawnedJevStub(pid, reported_port, state^) 779 780 781 def _spawn_jev_scripted( 782 port: Int, 783 var scripts: List[ExchangeScript], 784 deadline_ms: Int, 785 mut guard: CleanupGuard, 786 ) raises -> SpawnedJevStubAuto: 787 var total = len(scripts) 788 var pipe = make_pipe() 789 var pid = fork_owned_or_close(pipe.copy()) 790 if pid == 0: 791 if dup2_fd(pipe.write_fd, 1) < 0: 792 child_exit(126) 793 close_fd(pipe.read_fd) 794 close_fd(pipe.write_fd) 795 _ = set_alarm(STUB_ALARM_SECONDS) 796 try: 797 var report = serve_jev_scripted(port, scripts^) 798 _ = write_raw(1, report_line(report) + "\n") 799 child_exit(0 if report.ok else 125) 800 except: 801 var failed = ServeReport( 802 False, "startup", "scripted", "serve_failed", 0, 0 803 ) 804 _ = write_raw(1, report_line(failed) + "\n") 805 child_exit(125) 806 close_fd(pipe.write_fd) 807 var stub = _spawn_child_or_cleanup( 808 pipe, pid, "scripted", deadline_ms, total, UnsafePointer(to=guard) 809 ) 810 return SpawnedJevStubAuto(port=stub.port, stub=stub^) 811 812 813 def _spawn_jev_stub( 814 port: Int, 815 mode: String, 816 requests: Int, 817 deadline_ms: Int, 818 mut guard: CleanupGuard, 819 ) raises -> SpawnedJevStub: 820 var pipe = make_pipe() 821 var pid = fork_owned_or_close(pipe.copy()) 822 if pid == 0: 823 if dup2_fd(pipe.write_fd, 1) < 0: 824 child_exit(126) 825 close_fd(pipe.read_fd) 826 close_fd(pipe.write_fd) 827 _ = set_alarm(STUB_ALARM_SECONDS) 828 try: 829 var report = serve_jev(port, mode, requests) 830 _ = write_raw(1, report_line(report) + "\n") 831 child_exit(0 if report.ok else 125) 832 except: 833 var failed = ServeReport( 834 False, "startup", mode, "serve_failed", 0, 0 835 ) 836 _ = write_raw(1, report_line(failed) + "\n") 837 child_exit(125) 838 close_fd(pipe.write_fd) 839 return _spawn_child_or_cleanup( 840 pipe, pid, mode, deadline_ms, requests, UnsafePointer(to=guard) 841 )