mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-24 23:22:38 +08:00
refactor(core): Switch native Python task runner to forkserver (#19718)
This commit is contained in:
@@ -144,3 +144,7 @@ ERROR_DANGEROUS_ATTRIBUTE = "Access to attribute '{attr}' is disallowed, because
|
||||
ERROR_DYNAMIC_IMPORT = (
|
||||
"Dynamic __import__() calls are not allowed for security reasons."
|
||||
)
|
||||
ERROR_WINDOWS_NOT_SUPPORTED = (
|
||||
"Error: This task runner is not supported on Windows. "
|
||||
"Please use a Unix-like system (Linux or macOS)."
|
||||
)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
import platform
|
||||
from typing import Optional
|
||||
|
||||
from src.constants import ERROR_WINDOWS_NOT_SUPPORTED
|
||||
from src.config.health_check_config import HealthCheckConfig
|
||||
from src.config.sentry_config import SentryConfig
|
||||
from src.config.task_runner_config import TaskRunnerConfig
|
||||
@@ -63,4 +65,8 @@ async def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if platform.system() == "Windows":
|
||||
print(ERROR_WINDOWS_NOT_SUPPORTED, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -6,6 +6,7 @@ import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import logging
|
||||
|
||||
from src.errors import (
|
||||
TaskResultMissingError,
|
||||
@@ -23,9 +24,11 @@ from src.constants import (
|
||||
)
|
||||
from typing import Any, Set
|
||||
|
||||
from multiprocessing.context import SpawnProcess
|
||||
from multiprocessing.context import ForkServerProcess
|
||||
|
||||
MULTIPROCESSING_CONTEXT = multiprocessing.get_context("fork")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MULTIPROCESSING_CONTEXT = multiprocessing.get_context("forkserver")
|
||||
MAX_PRINT_ARGS_ALLOWED = 100
|
||||
|
||||
PrintArgs = list[list[Any]] # Args to all `print()` calls in a Python code task
|
||||
@@ -70,7 +73,7 @@ class TaskExecutor:
|
||||
|
||||
@staticmethod
|
||||
def execute_process(
|
||||
process: SpawnProcess,
|
||||
process: ForkServerProcess,
|
||||
queue: multiprocessing.Queue,
|
||||
task_timeout: int,
|
||||
continue_on_fail: bool,
|
||||
@@ -80,7 +83,12 @@ class TaskExecutor:
|
||||
print_args: PrintArgs = []
|
||||
|
||||
try:
|
||||
process.start()
|
||||
try:
|
||||
process.start()
|
||||
except (ProcessLookupError, ConnectionError, BrokenPipeError) as e:
|
||||
logger.error(f"Failed to start child process: {e}")
|
||||
raise TaskProcessExitError(-1)
|
||||
|
||||
process.join(timeout=task_timeout)
|
||||
|
||||
if process.is_alive():
|
||||
@@ -95,6 +103,10 @@ class TaskExecutor:
|
||||
returned = queue.get_nowait()
|
||||
except Empty:
|
||||
raise TaskResultMissingError()
|
||||
except EOFError as e:
|
||||
logger.error(f"Failed to retrieve results from child process: {e}")
|
||||
raise TaskResultMissingError()
|
||||
|
||||
finally:
|
||||
queue.close()
|
||||
queue.join_thread()
|
||||
@@ -122,17 +134,21 @@ class TaskExecutor:
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def stop_process(process: SpawnProcess | None):
|
||||
def stop_process(process: ForkServerProcess | None):
|
||||
"""Stop a running subprocess, gracefully else force-killing."""
|
||||
|
||||
if process is None or not process.is_alive():
|
||||
return
|
||||
|
||||
process.terminate()
|
||||
process.join(timeout=1) # 1s grace period
|
||||
try:
|
||||
process.terminate()
|
||||
process.join(timeout=1) # 1s grace period
|
||||
|
||||
if process.is_alive():
|
||||
process.kill()
|
||||
if process.is_alive():
|
||||
process.kill()
|
||||
except (ProcessLookupError, ConnectionError, BrokenPipeError):
|
||||
# subprocess is dead or unreachable
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _all_items(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from multiprocessing.context import SpawnProcess
|
||||
from multiprocessing.context import ForkServerProcess
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class TaskStatus(Enum):
|
||||
class TaskState:
|
||||
task_id: str
|
||||
status: TaskStatus
|
||||
process: Optional[SpawnProcess] = None
|
||||
process: Optional[ForkServerProcess] = None
|
||||
workflow_name: Optional[str] = None
|
||||
workflow_id: Optional[str] = None
|
||||
node_name: Optional[str] = None
|
||||
|
||||
Reference in New Issue
Block a user