Verify bounded ordinary-input approach to the prison gate
This commit is contained in:
+220
-12
@@ -51,6 +51,8 @@ def game_arguments(owner, args):
|
||||
raise Refused('Native observer nonce mismatch')
|
||||
if getattr(args, 'enable_private_look_proof', False) and option(native['args'], '-errOrdinaryLookObserve') != args.nonce:
|
||||
raise Refused('Native look observer nonce mismatch')
|
||||
if getattr(args, 'enable_private_approach_proof', False) and option(native['args'], '-errOrdinaryGateObserve') != args.nonce:
|
||||
raise Refused('Native gate observer nonce mismatch')
|
||||
if option(native['args'], '-errOrdinaryScenario') not in ('core', 'chat'):
|
||||
raise Refused('Not an ordinary successor scenario')
|
||||
if option(native['args'], '-profile') != 'Z:' + str(args.profile):
|
||||
@@ -115,8 +117,9 @@ def yaw_delta(first, second):
|
||||
|
||||
|
||||
class RelativeMouse:
|
||||
def __init__(self, query, keyboard, owner):
|
||||
def __init__(self, query, keyboard, owner, enable_approach=False):
|
||||
self.query, self.keyboard, self.owner = query, keyboard, owner
|
||||
self.enable_approach = enable_approach
|
||||
self.xt = keyboard.xt
|
||||
self.xt.XTestFakeRelativeMotionEvent.restype = C.c_int
|
||||
self.xt.XTestFakeRelativeMotionEvent.argtypes = [C.c_void_p, C.c_int, C.c_int, C.c_ulong]
|
||||
@@ -148,12 +151,27 @@ class RelativeMouse:
|
||||
def move(self, dx, dy, guard):
|
||||
if type(dx) is not int or dx not in (-24, 24) or dy != 0 or type(dy) is not int:
|
||||
raise Refused('Unsupported relative look step')
|
||||
self._move(dx, dy, guard)
|
||||
|
||||
def approach_move(self, dx, dy, guard, *, deadline, clock=time.monotonic):
|
||||
if (self.enable_approach is not True or type(dx) is not int or not 1 <= abs(dx) <= 128
|
||||
or type(dy) is not int or dy != 0):
|
||||
raise Refused('Unsupported or unenabled approach relative step')
|
||||
def within_lease():
|
||||
if clock() >= deadline:
|
||||
raise Refused("Original approach deadline exceeded before relative input")
|
||||
within_lease()
|
||||
self._move(dx, dy, guard, before_send=within_lease)
|
||||
|
||||
def _move(self, dx, dy, guard, before_send=None):
|
||||
target = guard.check()
|
||||
self.keyboard.require_idle()
|
||||
self.validate_pointer(target, dx, dy)
|
||||
# Recheck process/focus after the pointer roundtrip, with no focus changes.
|
||||
target = guard.check()
|
||||
self.validate_pointer(target, dx, dy)
|
||||
if before_send is not None:
|
||||
before_send()
|
||||
if not self.xt.XTestFakeRelativeMotionEvent(self.query.d, dx, dy, 0):
|
||||
raise Refused('Native relative motion request failed')
|
||||
self.query.x.XFlush(self.query.d)
|
||||
@@ -252,7 +270,7 @@ class Observer:
|
||||
"""Only records appended after this exact owned console was opened count."""
|
||||
pattern = re.compile(r'ERR INPUT OBSERVE: run=([A-Za-z0-9]{1,32}) sample=(\d+) real=(\d+) world=(\d+(?:\.\d+)?) (.+)$')
|
||||
|
||||
def __init__(self, path, nonce, clock=time.monotonic, enable_look=False):
|
||||
def __init__(self, path, nonce, clock=time.monotonic, enable_look=False, enable_approach=False):
|
||||
self.path, self.nonce, self.clock = path, nonce, clock
|
||||
self.fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
|
||||
info = os.fstat(self.fd)
|
||||
@@ -269,7 +287,14 @@ class Observer:
|
||||
self.raw = []
|
||||
self.bytes = 0
|
||||
self.terminal = False
|
||||
if enable_approach and not enable_look:
|
||||
os.close(self.fd)
|
||||
raise Refused("Approach observation requires native look")
|
||||
self.enable_look = enable_look
|
||||
self.enable_approach = enable_approach
|
||||
self.approach_lines = []; self.gate_records = []; self.gate_received = {}
|
||||
self.gate_complete_end = 0; self.approach_previous = None
|
||||
self.ready_at = self.ready_real = self.queued_real = None
|
||||
self.look_records = []; self.input_samples = []; self.ready_sample = None
|
||||
|
||||
def close(self):
|
||||
@@ -289,6 +314,20 @@ class Observer:
|
||||
lines = self.pending.split(b'\n'); self.pending = lines.pop()
|
||||
for raw in lines:
|
||||
line = raw.decode('utf-8', 'strict').rstrip('\r')
|
||||
if self.enable_approach and any(prefix in line for prefix in ('ERR INPUT OBSERVE:', 'ERR LOOK OBSERVE:', 'ERR GATE OBSERVE:')):
|
||||
if len(self.approach_lines) >= 1900:
|
||||
raise Refused('Native approach record bound exceeded')
|
||||
self.approach_lines.append(line)
|
||||
if self.enable_approach and 'ERR GATE OBSERVE:' in line:
|
||||
from ordinary_gate_evidence import parse_gate_record
|
||||
record = parse_gate_record(line, self.nonce)
|
||||
self.gate_records.append(record)
|
||||
if len(self.gate_records) > 902:
|
||||
raise Refused('Native gate record bound exceeded')
|
||||
if record['kind'] == 'geometry':
|
||||
self.gate_complete_end = len(self.approach_lines)
|
||||
self.gate_received.setdefault(record['sample'], self.clock())
|
||||
continue
|
||||
if self.enable_look and 'ERR LOOK OBSERVE:' in line:
|
||||
record = parse_look_record(line, self.nonce)
|
||||
if len(self.look_records) >= 453:
|
||||
@@ -326,11 +365,16 @@ class Observer:
|
||||
self.input_samples.append(parse_stationary_sample(sample, real, detail))
|
||||
self.sample, self.real, self.world = sample, real, world
|
||||
self.raw.append(line)
|
||||
if self.enable_approach and detail == 'QUEUED':
|
||||
if self.queued_real is not None:
|
||||
raise Refused('Repeated native queued clock')
|
||||
self.queued_real = real
|
||||
if detail == 'READY':
|
||||
if self.events or self.last_sample_at is None:
|
||||
raise Refused('Repeated or unbound READY')
|
||||
self.events.append('READY')
|
||||
self.ready_sample = sample
|
||||
self.ready_at = self.clock(); self.ready_real = real
|
||||
elif detail in ('INVENTORY_OPEN', 'INVENTORY_CLOSED'):
|
||||
expected = ['READY'] if detail == 'INVENTORY_OPEN' else ['READY', 'INVENTORY_OPEN']
|
||||
if self.events != expected:
|
||||
@@ -347,6 +391,47 @@ class Observer:
|
||||
raise Refused('Native observer samples are stale')
|
||||
|
||||
|
||||
def approach_data(self):
|
||||
"""Reparse complete raw triples, retaining incomplete final pairs for next poll."""
|
||||
from ordinary_gate_evidence import join_gate_snapshots, validate_gate_geometry
|
||||
self.fresh()
|
||||
if (not self.enable_approach or self.events != ['READY'] or not self.gate_complete_end or
|
||||
any(r['kind'] == 'cancelled' for r in self.gate_records + self.look_records)):
|
||||
raise Refused('Native approach context is unavailable or ended')
|
||||
content = '\n'.join(self.approach_lines[:self.gate_complete_end])
|
||||
data = join_gate_snapshots(content, self.nonce)
|
||||
index = max(data['snapshots'])
|
||||
geometry = validate_gate_geometry(content, self.nonce, data['ready']['sample'], index)
|
||||
if not geometry['verified']:
|
||||
raise Refused('Native gate geometry refused: ' + '; '.join(geometry['errors']))
|
||||
for item in data['snapshots'].values():
|
||||
action = item['action']
|
||||
if item['sample'] >= data['ready']['sample'] and (action['pressed'] != 0 or action['performing'] or action['progress'] != 0):
|
||||
raise Refused('Gate action input or progress changed during approach-only observation')
|
||||
if (self.clock() - self.gate_received[index] > .5 or
|
||||
self.input_samples[-1]['real_ms'] - data['snapshots'][index]['real_ms'] > 500):
|
||||
raise Refused('Complete native gate snapshot is stale')
|
||||
return content, data
|
||||
|
||||
def approach_snapshots(self):
|
||||
_, data = self.approach_data()
|
||||
snapshots = data['snapshots']; latest = max(snapshots)
|
||||
first = latest if self.approach_previous is None else min(self.approach_previous + 1, latest)
|
||||
if self.approach_previous is not None and latest < self.approach_previous:
|
||||
raise Refused('Native approach snapshot index reversed')
|
||||
result = []
|
||||
for index in range(first, latest + 1):
|
||||
item = snapshots[index]; geo, action, native, pose = item['geometry'], item['action'], item['input'], item['look']
|
||||
result.append({'sample': index, 'real_ms': item['real_ms'], 'received_monotonic': self.gate_received[index],
|
||||
'origin': native['origin'], 'direction': pose['direction'], 'body_forward': pose['body_forward'],
|
||||
'input': native['input'], 'forward': native['forward'], 'velocity': native['velocity'],
|
||||
'inventory': native['inventory'], 'phase': action['phase'], 'door_state': geo['door_state'], 'door_control': geo['door_control'],
|
||||
'geometry': {'nonce': self.nonce, 'basis': 'registered-prison-lock-native-interaction',
|
||||
**{key: geo[key] for key in ('prison', 'gate', 'gate_forward', 'context', 'radius')}}})
|
||||
self.approach_previous = latest
|
||||
return result
|
||||
|
||||
|
||||
class Keyboard:
|
||||
"""Only W/Tab key events on the pre-existing explicit owned connection."""
|
||||
def __init__(self, query):
|
||||
@@ -439,7 +524,7 @@ def confirm_release(guard, keyboard, report, key, clock=time.monotonic, sleep=ti
|
||||
observation['end'] = clock()
|
||||
|
||||
|
||||
def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time.sleep, mouse=None):
|
||||
def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time.sleep, mouse=None, approach=False):
|
||||
deadline = clock() + 70
|
||||
held = None
|
||||
report.update(events=[], paired_release_errors=[], sequence_completed=False)
|
||||
@@ -493,11 +578,26 @@ def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time
|
||||
try:
|
||||
wait_for('READY', 45)
|
||||
deadline = clock() + 12
|
||||
if approach:
|
||||
if mouse is None or not observer.enable_approach or observer.ready_at is None:
|
||||
raise Refused('Approach requires explicit look and gate observation')
|
||||
deadline = observer.ready_at + 35
|
||||
if clock() >= deadline:
|
||||
raise Refused('Original approach deadline exceeded before calibration')
|
||||
notify({'kind': 'approach-pending', 'deadline_monotonic': deadline})
|
||||
if observer.events != ['READY']:
|
||||
raise Refused('Inventory was already changed before input')
|
||||
if mouse is not None:
|
||||
perform_look(guard, keyboard, observer, mouse, report, clock, sleep)
|
||||
deadline = clock() + 12
|
||||
if not approach:
|
||||
deadline = clock() + 12
|
||||
if approach:
|
||||
run_approach(guard, keyboard, observer, mouse, report, clock, sleep)
|
||||
if clock() >= deadline:
|
||||
raise Refused("Original approach deadline exceeded")
|
||||
keyboard.require_idle()
|
||||
report["sequence_completed"] = True
|
||||
return
|
||||
pulse('W', 0.30)
|
||||
check()
|
||||
pulse('Tab', 0.05)
|
||||
@@ -526,20 +626,86 @@ def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time
|
||||
except (OSError, ValueError) as error:
|
||||
report['paired_release_errors'].append(str(error))
|
||||
report['unconfirmed_held_key'] = held if report['paired_release_errors'] else None
|
||||
report['all_attempted_presses_released'] = not report.get('unconfirmed_held_key')
|
||||
if approach:
|
||||
route = report.get('approach_proof', {})
|
||||
report['paired_release_errors'].extend(route.get('paired_release_errors', []))
|
||||
report['unconfirmed_held_key'] = route.get('unconfirmed_held_key')
|
||||
notify({'kind': 'approach-finished', 'successful': report['sequence_completed']})
|
||||
report['all_attempted_presses_released'] = not report.get('unconfirmed_held_key') and not report['paired_release_errors']
|
||||
|
||||
|
||||
class ApproachKeyboard:
|
||||
"""W-only capability for the separate approach module; never F/Tab."""
|
||||
def __init__(self, keyboard, report, clock, deadline):
|
||||
self.keyboard, self.report, self.clock, self.deadline = keyboard, report, clock, deadline
|
||||
def require_idle(self):
|
||||
self.keyboard.require_idle()
|
||||
def send(self, key, down):
|
||||
if key != 'W' or type(down) is not bool:
|
||||
raise Refused('Approach permits W key pairs only')
|
||||
if down and self.clock() >= self.deadline:
|
||||
raise Refused('Original approach deadline exceeded before W press')
|
||||
self.report['input_attempted'] = True
|
||||
self.keyboard.send(key, down)
|
||||
self.report['events'].append({'key': key, 'down': down, 'monotonic': self.clock()})
|
||||
self.report['input_sent'] = True
|
||||
|
||||
|
||||
class ApproachMouse:
|
||||
def __init__(self, mouse, report, clock, deadline):
|
||||
self.mouse, self.report, self.clock, self.deadline = mouse, report, clock, deadline
|
||||
def move(self, dx, dy, guard):
|
||||
self.report['input_attempted'] = True
|
||||
self.mouse.approach_move(dx, dy, guard, deadline=self.deadline, clock=self.clock)
|
||||
self.report['input_sent'] = True
|
||||
|
||||
|
||||
def run_approach(guard, keyboard, observer, mouse, report, clock, sleep):
|
||||
from ordinary_look_evidence import validate_look_evidence
|
||||
from prison_approach_proof import perform_approach
|
||||
content, data = observer.approach_data()
|
||||
validated = validate_look_evidence(content, observer.nonce, report['look_proof'])
|
||||
if not validated['verified']:
|
||||
raise Refused('Native approach calibration refused: ' + '; '.join(validated['errors']))
|
||||
poses = {r['sample']: r for r in observer.look_records if r['kind'] == 'sample'}
|
||||
proof = report['look_proof']
|
||||
if observer.queued_real is None or observer.ready_at is None or observer.ready_real is None:
|
||||
raise Refused('Original native observation clocks missing')
|
||||
calibration = {'nonce': observer.nonce, 'completed': True, 'pixels': 24,
|
||||
'baseline_direction': poses[proof['baseline_samples'][-1]]['direction'],
|
||||
'response_direction': poses[proof['response_sample']]['direction'],
|
||||
'ready_real_ms': observer.ready_real, 'ready_monotonic': observer.ready_at,
|
||||
'observer_start_real_ms': observer.queued_real}
|
||||
geometry = observer.approach_snapshots()[-1]['geometry']
|
||||
# The adapter has only read here. The module's initial snapshot can repeat
|
||||
# this latest sample; every subsequent native sample is returned in order.
|
||||
def route_notify(event):
|
||||
# Only the sequence owner can mark completion after its final checks.
|
||||
if event.get("kind") != "approach-finished":
|
||||
notify(event)
|
||||
deadline = observer.ready_at + 35
|
||||
perform_approach(guard, ApproachKeyboard(keyboard, report, clock, deadline), ApproachMouse(mouse, report, clock, deadline),
|
||||
observer.approach_snapshots, geometry, calibration, report,
|
||||
clock=clock, sleep=sleep, notify=route_notify, key_state_busy=KeyStateBusy)
|
||||
if not report.get('approach_proof', {}).get('completed'):
|
||||
raise Refused('Approach module did not complete')
|
||||
|
||||
|
||||
def arguments(argv=None):
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--enable-private-keyboard-proof', action='store_true', required=True)
|
||||
p.add_argument('--enable-private-look-proof', action='store_true')
|
||||
p.add_argument('--enable-private-approach-proof', action='store_true', help='After look calibration, replace W/Tab cycle with bounded interior approach only')
|
||||
for name in ('prefix', 'game-executable', 'profile', 'report'):
|
||||
p.add_argument('--' + name, type=Path, required=True)
|
||||
p.add_argument('--console', type=Path, help='Existing exact session; omit only in wrapper mode')
|
||||
p.add_argument('--host-display', required=True)
|
||||
p.add_argument('--nonce', required=True)
|
||||
p.add_argument('command', nargs=argparse.REMAINDER)
|
||||
return p.parse_args(argv)
|
||||
args = p.parse_args(argv)
|
||||
if args.enable_private_approach_proof and not args.enable_private_look_proof:
|
||||
p.error("Approach proof requires explicit private look proof")
|
||||
return args
|
||||
|
||||
|
||||
def wrapper_command(args):
|
||||
@@ -569,6 +735,8 @@ def wrapper_command(args):
|
||||
raise Refused('Wrapped native observer context differs')
|
||||
if args.enable_private_look_proof and option(native, '-errOrdinaryLookObserve') != args.nonce:
|
||||
raise Refused('Wrapped native look observer context differs')
|
||||
if getattr(args, 'enable_private_approach_proof', False) and option(native, '-errOrdinaryGateObserve') != args.nonce:
|
||||
raise Refused('Wrapped native gate observer context differs')
|
||||
return command
|
||||
|
||||
|
||||
@@ -589,6 +757,31 @@ def console_path(args):
|
||||
raise Refused('Expected exact canonical native session console')
|
||||
|
||||
|
||||
APPROACH_MODULES = ('prison_approach_proof.py', 'ordinary_gate_evidence.py',
|
||||
'ordinary_look_evidence.py', 'prison_approach_evidence.py')
|
||||
|
||||
|
||||
def approach_module_hashes():
|
||||
folder = Path(__file__).resolve().parent
|
||||
return {name: hashlib.sha256((folder / name).read_bytes()).hexdigest() for name in APPROACH_MODULES}
|
||||
|
||||
|
||||
def report_for_write(report):
|
||||
"""Keep raw console as pose authority; archive bounded references, not duplicate poses."""
|
||||
if report.get('approach_requested') is not True:
|
||||
return report
|
||||
result = dict(report)
|
||||
route = report.get('approach_proof')
|
||||
if isinstance(route, dict):
|
||||
result['approach_proof'] = dict(route)
|
||||
samples = route.get('samples')
|
||||
if isinstance(samples, list):
|
||||
result['approach_proof']['samples'] = [
|
||||
{key: sample.get(key) for key in ('sample', 'real_ms', 'received_monotonic')}
|
||||
if isinstance(sample, dict) else {'invalid_reference': True} for sample in samples]
|
||||
return result
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
args = arguments(argv)
|
||||
args.observe_seconds = 25; args.query_worker = False
|
||||
@@ -601,11 +794,14 @@ def main(argv=None):
|
||||
bound = display.binding(args.host_display, args.prefix)
|
||||
report = {'schema': 1, 'kind': 'headless-private-keyboard-experiment', 'input_sent': False,
|
||||
'look_requested': args.enable_private_look_proof,
|
||||
'approach_requested': getattr(args, 'enable_private_approach_proof', False),
|
||||
'sequence_completed': False, 'release_qualified': False, 'gameplay_qualified': False,
|
||||
'hard_supervision': NOTIFY is not None, 'nonce': args.nonce, 'binding': bound, 'command': command, 'report': str(args.report), 'input_attempted': False,
|
||||
'helper_sha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
||||
'ownership_helper_sha256': hashlib.sha256(Path(display.__file__).read_bytes()).hexdigest(),
|
||||
'prefix': str(args.prefix), 'profile': str(args.profile), 'game_executable': str(args.game_executable)}
|
||||
if report['approach_requested']:
|
||||
report['approach_module_sha256'] = approach_module_hashes()
|
||||
fd = os.open(args.report, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
|
||||
info = os.fstat(fd)
|
||||
notify({'kind': 'report', 'path': str(args.report), 'device': info.st_dev, 'inode': info.st_ino,
|
||||
@@ -642,7 +838,8 @@ def main(argv=None):
|
||||
if args.console is None:
|
||||
raise Refused('New native console deadline exceeded')
|
||||
report['console'] = str(args.console)
|
||||
observer = Observer(args.console, args.nonce, enable_look=args.enable_private_look_proof)
|
||||
observer = Observer(args.console, args.nonce, enable_look=args.enable_private_look_proof,
|
||||
enable_approach=getattr(args, "enable_private_approach_proof", False))
|
||||
query = display.X11(bound['display'])
|
||||
while time.monotonic() < deadline:
|
||||
if child is not None and child.poll() is not None:
|
||||
@@ -669,8 +866,8 @@ def main(argv=None):
|
||||
guard.check()
|
||||
keyboard = Keyboard(query)
|
||||
keyboard.require_idle()
|
||||
mouse = RelativeMouse(query, keyboard, owner) if args.enable_private_look_proof else None
|
||||
sequence(guard, keyboard, observer, report, mouse=mouse)
|
||||
mouse = RelativeMouse(query, keyboard, owner, enable_approach=getattr(args, "enable_private_approach_proof", False)) if args.enable_private_look_proof else None
|
||||
sequence(guard, keyboard, observer, report, mouse=mouse, approach=getattr(args, "enable_private_approach_proof", False))
|
||||
except (OSError, ValueError, UnicodeError, subprocess.SubprocessError) as error:
|
||||
report['error'] = str(error)
|
||||
finally:
|
||||
@@ -680,12 +877,14 @@ def main(argv=None):
|
||||
'end_offset': observer.offset, 'records': observer.raw, 'events': observer.events}
|
||||
if args.enable_private_look_proof:
|
||||
report['look_observation'] = observer.look_records
|
||||
if getattr(args, "enable_private_approach_proof", False):
|
||||
report["gate_observation"] = observer.gate_records
|
||||
observer.close()
|
||||
if query:
|
||||
query.close()
|
||||
with os.fdopen(fd, 'w') as stream:
|
||||
fd = None
|
||||
json.dump(report, stream, indent=2); stream.write('\n'); stream.flush(); os.fsync(stream.fileno())
|
||||
json.dump(report_for_write(report), stream, indent=2); stream.write('\n'); stream.flush(); os.fsync(stream.fileno())
|
||||
notify({'kind': 'proof-finished', 'successful': report['sequence_completed'] and not report.get('error')})
|
||||
child_code = 0
|
||||
try:
|
||||
@@ -773,6 +972,7 @@ def supervise(argv=None):
|
||||
os.kill(child.pid, signum)
|
||||
for signum in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP):
|
||||
previous[signum] = signal.signal(signum, forward)
|
||||
approach_deadline = None; approach_finished = False
|
||||
look_at = None; held_at = None; held_key = None; owned_child = None; report_record = None; proof_finished = False; expired = False
|
||||
deadline = time.monotonic() + 190
|
||||
try:
|
||||
@@ -783,7 +983,15 @@ def supervise(argv=None):
|
||||
event = parent.recv()
|
||||
except EOFError:
|
||||
event = None
|
||||
if event and event['kind'] == 'look-pending':
|
||||
if event and event['kind'] == 'approach-pending':
|
||||
proposed = event.get('deadline_monotonic')
|
||||
current = time.monotonic()
|
||||
if type(proposed) not in (int, float) or not math.isfinite(proposed) or proposed > current + 35:
|
||||
proposed = current # malformed lease fails closed
|
||||
approach_deadline = proposed if approach_deadline is None else min(approach_deadline, proposed)
|
||||
elif event and event['kind'] == 'approach-finished':
|
||||
approach_finished = bool(event.get('successful'))
|
||||
elif event and event['kind'] == 'look-pending':
|
||||
look_at = time.monotonic()
|
||||
elif event and event['kind'] == 'look-finished':
|
||||
look_at = None
|
||||
@@ -798,7 +1006,7 @@ def supervise(argv=None):
|
||||
elif event and event['kind'] == 'proof-finished':
|
||||
proof_finished = event['successful']
|
||||
now = time.monotonic()
|
||||
if received or now >= deadline or (held_at is not None and now - held_at > 1.5) or (look_at is not None and now - look_at > 4):
|
||||
if received or now >= deadline or (approach_deadline is not None and not approach_finished and now >= approach_deadline) or (held_at is not None and now - held_at > 1.5) or (look_at is not None and now - look_at > 4):
|
||||
expired = True
|
||||
child.terminate() # asks worker finally to release if still provable
|
||||
child.join(1)
|
||||
|
||||
Reference in New Issue
Block a user