Chronaxis用户手册English
本页目录

chronaxis.batch.process_transport

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""传递独立计划快照和小型结果,保留工作进程中的失败清理责任。23管道仅连接本次创建的可信子进程,不读取外部序列化对象。计划重建后核对身份,4大型结果在子进程内写出并关闭。清理失败的进程暂停接活,由报告句柄远程重试。5"""67from dataclasses import replace8import multiprocessing9from pathlib import Path10from time import monotonic1112from ..planning.plan import AnalysisPlan13from ..planning.checks import json_value14from .._control_wire import ControlChannel15from ..results.budget import _cleanup_owner16from .models import BatchJob, BatchJobResult, CleanupHandle17from .process_tree import ProcessTree18from .._process_start import spawn_entry19from .._batch_worker import worker_main202122def job_packet(job):23    """提取无活动资源的计划。参数: BatchJob。返回: 原生dict。异常: 配置转换错误。"""24    if isinstance(job.plan, Path):25        return dict(job_id=job.job_id, path=str(job.plan.absolute()))26    return dict(job_id=job.job_id, config=json_value(job.plan.raw_config),27                base_dir=str(job.plan.base_dir), identity=job.plan.identity)282930def restore_job(packet):31    """恢复独立计划。参数: 本进程收到的声明。返回: BatchJob。异常: 重建或身份不一致。"""32    if 'path' in packet:33        return BatchJob(packet['job_id'], Path(packet['path']))34    plan = AnalysisPlan.from_dict(packet['config'], base_dir=packet['base_dir'])35    if plan.identity != packet['identity']:36        raise ValueError('跨进程重建后的计划身份不一致')37    return BatchJob(packet['job_id'], plan)383940class ProcessWorker:41    """保存一个可复用工作进程、私有管道及其后代的清理责任。"""4243    def __init__(self, *, fast=False):44        """启动等待命令的spawn进程。参数: 无。返回: 新对象。异常: 启动/树登记失败立即回收。"""45        context = multiprocessing.get_context('spawn')46        self.connection = self._child = self.process = self.tree = self._startup = None47        self.fast_cancel = None48        self.cancel_deadline = self.fast_rows = self.pid = None49        self.closed = self.pending = self._pipe_lost = self._stop_requested = False50        self._constructing = True51        try:52            self.connection, self._child = context.Pipe()53            self.connection = ControlChannel(self.connection,'batch',parent=True)54            self._child = ControlChannel(self._child,'batch',parent=False)55            self.fast_cancel = context.Event() if fast else None56            args = (self._child, self.fast_cancel) if fast else (self._child,)57            try:58                target,args,self._startup=spawn_entry(context,worker_main,args)59            except BaseException as error:60                self._startup=getattr(error,'process_start_cleanup_owner',None)61                raise62            self.process = context.Process(target=target, args=args, daemon=False)63            self.process.start()64            self.pid = self.process.pid65            try:66                self.tree = (ProcessTree(self.pid) if self._startup is None else67                    ProcessTree(self.pid,registration=self._startup,process=self.process))68            except BaseException as error:69                self.tree = getattr(error, 'process_tree_cleanup_owner', None)70                raise71            if self._startup is not None:72                self._startup.close();self._startup=None73            self._child.close()74            self._child = None75            self._constructing = False76        except BaseException as error:77            # 未成功返回的工作进程不参与握手;树或管道持续关闭失败仍保留真实owner。78            self._pipe_lost = True79            _cleanup_owner(self, error, 'batch_worker_cleanup_owner', '批量工作进程构造')80            raise8182    def submit(self, job, directory, backend):83        """发送独立任务。参数: job/目录/后端。返回: None。异常: 转换或通信错误。"""84        self.connection.send(('run', job_packet(job), str(directory), backend))8586    def submit_fast(self, job, directory, spec, total):87        """只传准备完成的文件参数,工作进程不重新解析模板或建立下层进程。"""88        packet = dict(job_id=job.job_id, spec=spec, total=total,89                      path=str(job.plan) if isinstance(job.plan, Path) else None)90        self.connection.send(('fast', packet, str(directory), None))9192    def cancel_fast(self):93        """通知在途文件,协作退出超过五秒才结束本次拥有的进程树。返回是否已强制回收。"""94        if self.cancel_deadline is None:95            self.fast_cancel.set()96            self.cancel_deadline = monotonic() + 597        elif monotonic() >= self.cancel_deadline:98            self._pipe_lost = True99            self.close()100            return True101        return False102103    def ready(self):104        """查询结果或异常退出。参数: 无。返回: bool。异常: 原管道错误。"""105        try:106            return self.connection.poll() or not self.process.is_alive()107        except (EOFError, OSError) as error:108            if isinstance(error, (EOFError, BrokenPipeError)):109                self._pipe_lost = True110            raise ChildProcessError('分析工作进程管道已断开,PID=' + str(self.pid)) from error111112    def receive(self):113        """取结果并挂远程恢复句柄。参数: 无。返回: BatchJobResult。异常: 进程死亡或协议错误。"""114        try:115            # Windows 同伴退出时,探测或随后的读取都可能先报告断管。116            if not self.connection.poll():117                raise ChildProcessError('分析工作进程异常退出,PID=' + str(self.pid))118            message = self.connection.recv()119        except (EOFError, BrokenPipeError) as error:120            self._pipe_lost = True121            raise ChildProcessError('分析工作进程断开,PID=' + str(self.pid)) from error122        if len(message) == 4 and getattr(self,'fast_cancel',None) is not None:123            kind, result, self.pending, self.fast_rows = message124        else:125            kind, result, self.pending = message126        if kind == 'fast_result' and getattr(self,'fast_cancel',None) is not None and isinstance(result,dict):127            result = BatchJobResult(**result)128            kind = 'result'129        if kind != 'result' or not isinstance(result, BatchJobResult):130            raise RuntimeError('批量工作进程返回无效结果')131        return replace(result, recovery=CleanupHandle(self) if self.pending else None)132133    def close(self):134        """重试远程清理后关闭实际进程树及管道,半构造失败也可恢复。135136        参数: 无。返回: None。异常: 清理失败保留尚未关闭对象供下一次close。137        已关闭进程不再次调用is_alive;构造后未启动的进程也关闭其本地句柄。138        """139        if self.closed:140            return141        process = self.process142        active = (process is not None and getattr(process, '_closed', False) is not True143                  and process.pid is not None)144        if active:145            if not self._pipe_lost and not self._stop_requested and process.is_alive():146                try:147                    if self.pending:148                        self.connection.send(('cleanup',))149                        if self.tree is not None and getattr(self.tree,'_linux',False) is True and not self.connection.poll(5):150                            self._pipe_lost=True151                            raise TimeoutError('等待工作进程清理回复超时;保留进程树关闭责任')152                        kind, detail = self.connection.recv()153                        if kind != 'cleaned':154                            raise RuntimeError('工作进程资源尚未关闭:' + '; '.join(detail))155                        self.pending = False156                    self.connection.send(('close',))157                    self._stop_requested = True158                except (EOFError, BrokenPipeError):159                    self._pipe_lost = True160            if self._pipe_lost:161                if not self._stop_requested:162                    if self.tree is not None:163                        self.tree.close()164                        self.tree = None165                        # 仅构造失败时作业可能尚未登记该进程,不能由空作业推断进程已结束。166                        if getattr(self, '_constructing', False) and process.is_alive():167                            process.kill()168                    elif process.is_alive():169                        process.kill()170                    self._stop_requested = True171                process.join(timeout=5)172                if process.is_alive():173                    raise TimeoutError('等待断管工作进程退出超时,PID=' + str(self.pid))174            else:175                if self.tree is not None and getattr(self.tree,'_linux',False) is True:176                    process.join(timeout=5)177                    if process.is_alive():178                        self.tree.close();self.tree=None179                        raise TimeoutError('工作进程未按关闭协议退出')180                else:process.join()181        if self.tree is not None:182            self.tree.close()183            self.tree = None184        for name in ('connection', '_child', '_startup'):185            connection = getattr(self, name, None)186            if connection is not None:187                try:188                    connection.close()189                finally:190                    if connection.closed:191                        setattr(self, name, None)192        if process is not None and getattr(process, '_closed', False) is not True:193            process.close()194        self.fast_cancel = None195        self.pending, self.closed = False, True196
Chronaxis 用户手册 · 离线阅读