chronaxis.results.budget
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""维护共享资源账本、自有RAM交易槽及未返回owner的有限清理协议。23输入为显式正整数限额及申请bytes,输出为检查后的当前/峰值统计。4RAM申请先校验再分配,释放只能抵销已申请金额;spool保留诊断文件,累计不释放。5AnalysisRunner及执行组件共用账本登记数组nbytes、状态和历史存储费用;它不代表全进程RSS上限。6三个资源事务helper只围绕直接预算调用登记实际归属,或委托真实owner.close,7不创建来源、数组或文件。轻量累计器可复用这些协议,无需加载结果编解码和暂存。8"""910# 明确超限异常,首版不降采样、丢结果或偷改窗口。11from .errors import BudgetExceededError121314def _reserve_owned_ram(owner, amount: int, slot: str) -> None:15 """预约一笔RAM并按实际账本登记owner及指定槽位,覆盖成功后取消。1617 参数: owner已初始化_budget/_held_cost/slot;amount为bytes;slot为实际费用字段。18 返回: None。异常: 原预算/取消传播,未实际入账不增加自有费用。19 边界: 仅围绕一次单线程直接预算调用判断,不用子操作后的总额猜测借用费用。20 """21 # 预算回调可能先更新再抛异常,finally必须登记已成功的这一笔申请。22 before = owner._budget.ram_bytes23 try:24 owner._budget.reserve_ram(amount)25 finally:26 if owner._budget.ram_bytes == before + amount:27 owner._held_cost += amount28 setattr(owner, slot, getattr(owner, slot) + amount)293031def _release_owned_ram(owner, amount: int, slot: str) -> None:32 """抵销一笔实际自有RAM,释放前取消保留金额,释放后取消立即清账。3334 参数: owner持有实际_budget/_held_cost/slot;amount为bytes;slot为对应实际费用。35 返回: None。异常: 原预算/取消传播,恢复后不得重复抵销已释放金额。36 """37 # 空槽不调用外部预算回调,也不把未付金额用于抵销其他owner的费用。38 if amount == 0:39 return40 before = owner._budget.ram_bytes41 try:42 owner._budget.release_ram(amount)43 finally:44 if owner._budget.ram_bytes == before - amount:45 owner._held_cost -= amount46 setattr(owner, slot, getattr(owner, slot) - amount)474849def _cleanup_owner(owner, exc: BaseException, attribute: str, label: str) -> None:50 """尝试关闭构造未返回的真实owner,持续失败将恢复句柄附在原异常。5152 参数: owner为实际部分构造对象;exc为原异常;attribute为恢复属性名;label为说明。53 返回: None,不覆盖原异常。异常: 关闭错误记录在原异常notes中,不向外替换它。54 """55 # 至多两次处理瞬时关闭取消;持续故障保留原对象,调用方恢复后显式close。56 for attempt in range(2):57 try:58 owner.close()59 break60 except BaseException as cleanup:61 exc.add_note(f'{label}清理第{attempt + 1}次失败: {type(cleanup).__name__}')62 if not (getattr(owner, '_closed', False) or getattr(owner, 'closed', False)):63 setattr(exc, attribute, owner)64 exc.add_note(f'释放恢复后调用{attribute}.close()')656667class ResourceBudget:68 """保存限额与单线程共享计数;每次申请失败都保持原计数不变。"""6970 def __init__(self, memory_budget_bytes: int = 268435456, spool_quota_bytes: int | None = None):71 """校验并初始化资源计数,不分配数组或创建文件。7273 参数: memory_budget_bytes为RAM估算bytes;spool_quota_bytes为可选累计限额。74 返回: 新账本。异常: ValueError为bool、非正数或非原生int。75 """76 # 配置从计划传入也重新校验,不能把None磁盘配额解释为无限物理磁盘。77 for value in (memory_budget_bytes, spool_quota_bytes):78 if value is not None and (type(value) is not int or value <= 0):79 raise ValueError('资源限额需要正原生int')80 if memory_budget_bytes is None:81 raise ValueError('RAM限额不能省略')82 self.memory_budget_bytes = memory_budget_bytes83 self.spool_quota_bytes = spool_quota_bytes84 self.ram_bytes = self.peak_ram_bytes = self.spool_bytes = 08586 def reserve_ram(self, amount: int) -> None:87 """在分配前保守申请新层RAM,失败不修改账本。8889 参数: amount为非负原生int bytes。返回: None。90 异常: ValueError为金额错误;BudgetExceededError为超限。91 """92 # 使用Python整数避免账本定宽溢出。93 self._amount(amount)94 if self.ram_bytes + amount > self.memory_budget_bytes:95 raise BudgetExceededError(f'新分析层RAM估算超限: {self.ram_bytes}+{amount}>{self.memory_budget_bytes}')96 self.ram_bytes += amount97 self.peak_ram_bytes = max(self.peak_ram_bytes, self.ram_bytes)9899 def release_ram(self, amount: int) -> None:100 """释放已有申请,不能凭负数或重复释放隐藏状态开销。101102 参数: amount为非负原生int bytes。返回: None。103 异常: ValueError为金额错误或超出当前申请。104 """105 self._amount(amount)106 if amount > self.ram_bytes:107 raise ValueError('RAM释放超出当前申请')108 self.ram_bytes -= amount109110 def reserve_spool(self, amount: int) -> None:111 """写入前累计申请磁盘量,失败或部分写入也保守保留申请。112113 参数: amount为非负原生int bytes。返回: None。114 异常: ValueError为金额错误;BudgetExceededError为累计超配额。115 """116 self._amount(amount)117 if self.spool_quota_bytes is not None and self.spool_bytes + amount > self.spool_quota_bytes:118 raise BudgetExceededError('累计磁盘暂存超过spool_quota_bytes')119 self.spool_bytes += amount120121 @staticmethod122 def _amount(amount: int) -> None:123 """检查金额,防止布尔和负数影响预算。124125 参数: amount为申请金额。返回: None。异常: ValueError为非法金额。126 """127 if type(amount) is not int or amount < 0:128 raise ValueError('资源金额需要非负原生int')129130 def snapshot(self) -> dict:131 """返回独立的小型资源快照,不修改计数。132133 参数: 无。返回: 限额、当前/峰值RAM估算和累计spool预留。134 异常: 无;整进程RSS不在此模型内。135 """136 return {'memory_budget_bytes': self.memory_budget_bytes, 'spool_quota_bytes': self.spool_quota_bytes,137 'ram_estimated_bytes': self.ram_bytes, 'peak_ram_estimated_bytes': self.peak_ram_bytes,138 'spool_reserved_bytes': self.spool_bytes, 'whole_process_rss': 'not_measured'}139