LittlePlyResearch notebook

scheduler.py

python · 59 lines · Exact bytes

Line 1 ↗
Version & provenance · service-foundation-dc8989b92ba1

Recorded SHA-256 91051249b153ffbacac9b8cf48044203b1aad83ac7a362bf1e1ba59736e3832cRendered SHA-256 91051249b153ffbacac9b8cf48044203b1aad83ac7a362bf1e1ba59736e3832c

Service code retains Apache-2.0; the Rust helper component and its adapters are distributed with GPL-3.0-or-later notices and corresponding source. Dependencies retain their own licenses. License notes. Line links are stable within this snapshot. Exact allowlisted files from active release 857bc93bfffb-dc8989b92ba1, verified against its manifest. Service files originate under lichess_service/; helper adapter files at release root. No launcher, account state, credentials or private game logs. Complete GPL helper source is separately downloadable.

1"""Bounded per-game waiting set with earliest-deadline-first dispatch."""
2from dataclasses import dataclass
3import time
4
5def clock_window(state,color,age=0):
6    side='w' if color else 'b';remaining=max(0,state[side+'time']/1000-age)
7    increment=state.get(side+'inc',0)/1000
8    reserve=min(2.,max(.3,remaining*.1))
9    # Fast clocks get a usable subsecond budget; longer clocks retain room for
10    # a serialized burst. Queue time is included in the absolute deadline.
11    target=max(.6,remaining/10+increment*.6)
12    return max(.01,min(30.,target,remaining-reserve))
13
14def realtime_reason(tc):
15    if tc.get('type')!='clock':return 'timeControl'
16    limit,inc=tc.get('limit'),tc.get('increment')
17    if type(limit) is not int or type(inc) is not int or limit<0 or inc<0:return 'timeControl'
18    if limit+40*inc<30:return 'tooFast' # Lichess Bot API forbids UltraBullet.
19    return None
20
21def bullet_clock(limit,increment):
22    return limit+40*increment<180
23
24def expected_policy_seconds(tokens):
25    # Conservative envelope over short and near-context CPU load probes.
26    return .6+5.8*(min(tokens,1024)/1024)**2
27
28@dataclass
29class Request:
30    key:tuple
31    board:object
32    arrived:float
33    deadline:float
34    sequence:int
35    expired:bool=False
36
37class Scheduler:
38    def __init__(self,capacity):self.capacity=capacity;self.waiting={};self.sequence=0;self.running=None
39    def add(self,key,board,window,now=None):
40        now=time.monotonic() if now is None else now;gid=key[0]
41        if gid in self.waiting and self.waiting[gid].key==key:return
42        if gid not in self.waiting and len(self.waiting)>=self.capacity:raise OverflowError('per-game queue full')
43        self.sequence+=1;self.waiting[gid]=Request(key,board,now,now+window,self.sequence)
44    def remove(self,gid):self.waiting.pop(gid,None)
45    def next(self,valid,now=None):
46        if self.running:return None
47        for gid,request in list(self.waiting.items()):
48            if not valid(request.key):self.waiting.pop(gid)
49        if not self.waiting:return None
50        request=min(self.waiting.values(),key=lambda r:(r.deadline,r.sequence))
51        self.waiting.pop(request.key[0]);self.running=request;return request
52    def complete(self,key):
53        if self.running and self.running.key==key:
54            request=self.running;self.running=None;return request
55        return None
56
57def capacity_fits(windows,service_bound):
58    return bool(windows) and len(windows)*service_bound<=min(windows)
59