Handle a CALL command.
(command_line, user_module, default_function_name)
| 369 | |
| 370 | |
| 371 | def handle_call(command_line, user_module, default_function_name): |
| 372 | """Handle a CALL command.""" |
| 373 | response_file = None |
| 374 | try: |
| 375 | # Parse command: "CALL|<function_name>|<request_file>|<response_file>" |
| 376 | # or legacy: "CALL|<request_file>|<response_file>" |
| 377 | # Note: Using pipe (|) delimiter to avoid conflicts with Windows drive letters (C:) |
| 378 | parts = command_line.split("|", 3) |
| 379 | |
| 380 | # Extract response_file first (always the last part) so it's available even if validation fails |
| 381 | # This ensures we can write an error response file if command format is invalid |
| 382 | if len(parts) >= 3: |
| 383 | response_file = parts[-1] |
| 384 | |
| 385 | if len(parts) == 4: |
| 386 | # New format: CALL|<function_name>|<request_file>|<response_file> |
| 387 | _, function_name, request_file, _ = parts |
| 388 | elif len(parts) == 3: |
| 389 | # Legacy format: CALL|<request_file>|<response_file> |
| 390 | _, request_file, _ = parts |
| 391 | function_name = default_function_name |
| 392 | else: |
| 393 | raise ValueError(f"Invalid CALL command format: {command_line}") |
| 394 | |
| 395 | # Resolve the callable for this call |
| 396 | method_callable = get_callable(user_module, function_name) |
| 397 | |
| 398 | # Read request |
| 399 | with open(request_file, "r", encoding="utf-8") as f: |
| 400 | args = json.load(f) |
| 401 | |
| 402 | # Execute user function (with automatic tracing if enabled) |
| 403 | try: |
| 404 | result = _traced_call(method_callable, args, function_name) |
| 405 | response = {"type": "result", "data": result} |
| 406 | except Exception as e: |
| 407 | response = { |
| 408 | "type": "error", |
| 409 | "error": str(e), |
| 410 | "traceback": traceback.format_exc(), |
| 411 | } |
| 412 | |
| 413 | # Write response |
| 414 | with open(response_file, "w", encoding="utf-8") as f: |
| 415 | json.dump(response, f, ensure_ascii=False) |
| 416 | f.flush() # Flush Python buffer |
| 417 | os.fsync(f.fileno()) # Force OS to write to disk (critical for Windows) |
| 418 | |
| 419 | # Verify file is readable before signaling done (prevents race conditions) |
| 420 | # Retry up to 3 times with small delays if file isn't immediately readable |
| 421 | for verify_attempt in range(3): |
| 422 | try: |
| 423 | with open(response_file, "r", encoding="utf-8") as f: |
| 424 | _ = f.read() |
| 425 | break # Successfully read, exit retry loop |
| 426 | except Exception as e: |
| 427 | if verify_attempt < 2: |
| 428 | import time |
no test coverage detected
searching dependent graphs…