mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-08 00:54:38 +02:00
65 lines
2.8 KiB
Python
65 lines
2.8 KiB
Python
"""Normal qualification inspection budgets; no change to test or cleanup deadlines."""
|
|
import subprocess
|
|
import time
|
|
from owned_processes import process_table, command_tokens, ProcessInspectionUnavailable
|
|
|
|
|
|
class ActiveInspectionFailed(Exception):
|
|
"""Fatal observer failure, deliberately not a recoverable test RuntimeError."""
|
|
|
|
|
|
class QualificationInspection:
|
|
def __init__(self, owner, deadline, *, clock=time.monotonic):
|
|
self.owner = owner
|
|
self.deadline = deadline
|
|
self.clock = clock
|
|
|
|
def budget(self):
|
|
deadline = self.owner.stop_deadline
|
|
if deadline is None:
|
|
deadline = self.deadline
|
|
remaining = deadline - self.clock()
|
|
if remaining <= 0:
|
|
if self.owner.stop_deadline is None:
|
|
raise ActiveInspectionFailed('Qualification command observation deadline exhausted')
|
|
raise ProcessInspectionUnavailable('Qualification cleanup observation deadline exhausted')
|
|
return min(5, remaining)
|
|
|
|
def inspect(self, operation, *args, identity_recheck=False):
|
|
try:
|
|
return operation(*args, timeout=self.budget())
|
|
except (ProcessInspectionUnavailable, subprocess.SubprocessError, OSError) as error:
|
|
if self.owner.stop_deadline is None:
|
|
if (identity_recheck and isinstance(error, ProcessInspectionUnavailable)
|
|
and (error.__cause__ is None or isinstance(error.__cause__, (subprocess.CalledProcessError, ValueError)))):
|
|
# A completed ps snapshot can race natural exit/reuse.
|
|
# OwnedProcesses rechecks the exact observed identity;
|
|
# observe() below rejects any still-live unresolved role.
|
|
raise
|
|
# An actual inspection timeout/OS failure is fatal even if a
|
|
# later sample might find the process gone. Never retry it.
|
|
raise ActiveInspectionFailed('Qualification active inspection failed: ' + type(error).__name__) from error
|
|
raise
|
|
|
|
def table(self):
|
|
return self.inspect(process_table)
|
|
|
|
def argv(self, pid):
|
|
return self.inspect(command_tokens, pid, identity_recheck=True)
|
|
|
|
def observe(self, pid):
|
|
self.budget()
|
|
rows = self.owner.observe(pid)
|
|
if any(record['role'] == 'unresolved_inspection'
|
|
and rows.get(owned_pid, {}).get('started') == record['started']
|
|
for owned_pid, record in self.owner.owned.items()):
|
|
raise ActiveInspectionFailed('Qualification active process identity remains unresolved')
|
|
self.budget()
|
|
|
|
|
|
def wait_for_owned_command(child, inspection, *, sleep=time.sleep):
|
|
while child.poll() is None:
|
|
inspection.observe(child.pid)
|
|
sleep(min(.5, inspection.budget()))
|
|
inspection.observe(child.pid)
|