scheduler.py
python · 59 lines · Exact bytes
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