diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 778c1bc..0d2bdb0 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -156,7 +156,8 @@ backend and the game untouched, and takes no action when ownership, the environment or the stat fields cannot be read. The client UI is recognised by the name of its executable, not by the command line: `LeagueCrashHandler` processes carry the client UI name in their arguments only, so they are left alone. It -leaves by itself after ten minutes without a client or a match. Diagnose it in one +leaves by itself ten minutes after the last client or match is gone (the clock does +not run before the first client appears, so a long patch or login is fine). Diagnose it in one observation, without waiting for a match: ```sh diff --git a/tests/test_close_league_client_in_game.py b/tests/test_close_league_client_in_game.py index 0d774cb..aeddd38 100644 --- a/tests/test_close_league_client_in_game.py +++ b/tests/test_close_league_client_in_game.py @@ -194,6 +194,19 @@ class InGameCloseTests(unittest.TestCase): in_game.close_ui(self.proc, {'123': identity, '124': 1}) self.assertEqual(kill.call_args_list, []) + def test_idle_clock_starts_only_after_a_client_or_match(self): + empty = {'game': False, 'ui': {}} + client = {'game': False, 'ui': {'10': 5}} + match = {'game': True, 'ui': {}} + # Before any client (long patch or login): never idle, however long it takes. + self.assertEqual(in_game.update_idle(empty, False, None, 5000.0), (False, None)) + # A client or a match marks the session as seen and keeps the clock stopped. + self.assertEqual(in_game.update_idle(client, False, None, 10.0), (True, None)) + self.assertEqual(in_game.update_idle(match, True, 50.0, 60.0), (True, None)) + # Afterwards the clock starts once and keeps its start time. + self.assertEqual(in_game.update_idle(empty, True, None, 70.0), (True, 70.0)) + self.assertEqual(in_game.update_idle(empty, True, 70.0, 900.0), (True, 70.0)) + def test_close_decision_covers_limits_and_cooldown(self): state = {'game': True, 'ui': {'123': 1}} self.assertEqual(in_game.close_decision(state, 0, 0.0, 100.0), 'close') diff --git a/tools/close-league-client-in-game.py b/tools/close-league-client-in-game.py index bdf4508..3a7b008 100755 --- a/tools/close-league-client-in-game.py +++ b/tools/close-league-client-in-game.py @@ -49,7 +49,7 @@ KILL_GRACE = 2.0 POLL_SECONDS = 5.0 COOLDOWN_SECONDS = 30.0 # minimum between two closes in the same match MAX_CLOSES = 3 # give up on a client that keeps reopening during a match -IDLE_SECONDS = 600.0 # leave after this long without a client or a game +IDLE_SECONDS = 600.0 # leave after this long without a client or a game, once one was seen def target_prefix(): @@ -165,6 +165,20 @@ def close_decision(state, closes, last_close, now): return 'close' +def update_idle(state, seen, idle_since, now): + """Return (seen, idle_since) after one observation. + + The idle clock only starts once a client UI or a match has been seen: a long + patch or login can keep the League client closed for more than IDLE_SECONDS + after launch, and leaving then would skip the match that follows. + """ + active = state['game'] or bool(state['ui']) + seen = seen or active + if active or not seen: + return seen, None + return seen, now if idle_since is None else idle_since + + def supervise(proc_root=Path('/proc'), prefix=None, once=False, dry_run=False, out=sys.stdout): """Watch for a match and close the client UI while it runs.""" prefix = prefix or target_prefix() @@ -177,6 +191,7 @@ def supervise(proc_root=Path('/proc'), prefix=None, once=False, dry_run=False, o closes = 0 last_close = 0.0 limit_reported = False + seen = False idle_since = None while True: state = match_state(proc_root, prefix) @@ -215,11 +230,7 @@ def supervise(proc_root=Path('/proc'), prefix=None, once=False, dry_run=False, o 'running match (%d processes: %s)' % (len(closed), ','.join(sorted(closed, key=int))), file=out, flush=True) - idle = not state['game'] and not state['ui'] - if idle and idle_since is None: - idle_since = now - elif not idle: - idle_since = None + seen, idle_since = update_idle(state, seen, idle_since, now) if once: if state is not None: print('close-league-client-in-game: one observation: match=%s client_ui=%d'