File

FILE: projects/scheduler_v2.py
SIZE: 7715 bytes
SHA256: 27e46fea8abac6806657daf9ef194c83d77c52b3eda68ca889aed48595f02fcc
Lines: 207

Use path as FILE | Patch Context | Read first 100 | Read prev 100 | Read next 100 | Git diff this file | Code Buffers | Active Patch Context

Blocks / Symbols (python)

LineTextActions
1
from __future__ import annotations
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
2
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
3
import argparse
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
4
import datetime as dt
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
5
import hashlib
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
6
import json
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
7
import time
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
8
from dataclasses import dataclass, field
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
9
from typing import Dict, List, Tuple
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
10
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
11
REVISION = "20260902-01-SCHEDV2-SHADOW"
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
12
TERMINAL = {"done", "error", "cancelled", "expired", "superseded"}
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
13
COALESCABLE = {"health", "status", "job_status", "jobs", "recent", "git_status"}
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
14
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
15
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
16
def utc_now() -> dt.datetime:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
17
    return dt.datetime.now(dt.timezone.utc)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
18
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
19
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
20
@dataclass
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
21
class Client:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
22
    client_id: str
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
23
    weight: int = 1
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
24
    max_inflight: int = 1
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
25
    inflight: int = 0
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
26
    last_served_tick: int = 0
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
27
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
28
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
29
@dataclass
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
30
class Command:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
31
    command_id: str
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
32
    client_id: str
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
33
    seq: int
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
34
    created_at: dt.datetime
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
35
    priority: int = 50
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
36
    resource_keys: Tuple[str, ...] = field(default_factory=tuple)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
37
    action: str = "health"
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
38
    payload_json: str = "{}"
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
39
    status: str = "pending"
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
40
    ttl_seconds: int = 300
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
41
    coalesce_key: str = ""
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
42
    supersedes_command_id: str = ""
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
43
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
44
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
45
@dataclass
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
46
class Lock:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
47
    resource_key: str
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
48
    command_id: str
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
49
    client_id: str
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
50
    fence_token: str
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
51
    lease_until: dt.datetime
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
52
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
53
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
54
class Scheduler:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
55
    def __init__(self, clients: Dict[str, Client], lease_seconds: int = 60, aging_seconds: int = 30):
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
56
        self.clients = clients
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
57
        self.lease_seconds = lease_seconds
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
58
        self.aging_seconds = aging_seconds
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
59
        self.tick = 0
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
60
        self.locks: Dict[str, Lock] = {}
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
61
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
62
    def expire_and_coalesce(self, cmds: List[Command], now: dt.datetime | None = None) -> None:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
63
        now = now or utc_now()
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
64
        by_id = {c.command_id: c for c in cmds}
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
65
        latest: Dict[Tuple[str, str], Command] = {}
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
66
        for c in cmds:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
67
            if c.status in TERMINAL:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
68
                continue
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
69
            if c.ttl_seconds and (now - c.created_at).total_seconds() > c.ttl_seconds:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
70
                c.status = "expired"
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
71
                continue
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
72
            if c.supersedes_command_id:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
73
                superseded = by_id.get(c.supersedes_command_id)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
74
                if superseded and superseded.status == "pending":
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
75
                    superseded.status = "superseded"
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
76
            if c.action in COALESCABLE and c.coalesce_key:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
77
                key = (c.client_id, c.coalesce_key)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
78
                prev = latest.get(key)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
79
                if prev is None:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
80
                    latest[key] = c
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
81
                elif (c.seq, c.created_at) > (prev.seq, prev.created_at):
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
82
                    prev.status = "superseded"
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
83
                    latest[key] = c
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
84
                else:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
85
                    c.status = "superseded"
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
86
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
87
    def resources_free(self, c: Command, now: dt.datetime) -> bool:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
88
        for resource in c.resource_keys:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
89
            lock = self.locks.get(resource)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
90
            if lock and lock.lease_until > now and lock.command_id != c.command_id:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
91
                return False
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
92
        return True
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
93
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
94
    def effective_priority(self, c: Command, now: dt.datetime) -> int:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
95
        age = max(0.0, (now - c.created_at).total_seconds())
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
96
        return c.priority + int(age / self.aging_seconds)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
97
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
98
    def choose(self, cmds: List[Command], now: dt.datetime | None = None) -> Command | None:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
99
        now = now or utc_now()
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
100
        self.tick += 1
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
101
        self.expire_and_coalesce(cmds, now)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
102
        by_client: Dict[str, List[Command]] = {}
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
103
        for c in cmds:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
104
            if c.status != "pending":
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
105
                continue
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
106
            client = self.clients.get(c.client_id)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
107
            if client is None or client.inflight >= client.max_inflight:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
108
                continue
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
109
            if not self.resources_free(c, now):
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
110
                continue
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
111
            by_client.setdefault(c.client_id, []).append(c)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
112
        if not by_client:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
113
            return None
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
114
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
115
        candidates = []
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
116
        for client_id, arr in by_client.items():
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
117
            client = self.clients[client_id]
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
118
            best = max(arr, key=lambda c: (self.effective_priority(c, now), -c.seq))
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
119
            fairness_age = self.tick - client.last_served_tick
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
120
            score = (self.effective_priority(best, now), fairness_age * max(1, client.weight))
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
121
            candidates.append((score, client_id, best))
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
122
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
123
        _, client_id, chosen = max(candidates, key=lambda item: (item[0][0], item[0][1]))
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
124
        self.clients[client_id].last_served_tick = self.tick
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
125
        return chosen
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
126
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
127
    def acquire(self, c: Command, now: dt.datetime | None = None) -> Tuple[str, dt.datetime]:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
128
        now = now or utc_now()
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
129
        if not self.resources_free(c, now):
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
130
            raise RuntimeError("resource_locked")
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
131
        token = hashlib.sha256(
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
132
            f"{c.command_id}|{c.client_id}|{time.time_ns()}".encode()
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
133
        ).hexdigest()[:16]
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
134
        lease_until = now + dt.timedelta(seconds=self.lease_seconds)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
135
        for resource in c.resource_keys:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
136
            self.locks[resource] = Lock(resource, c.command_id, c.client_id, token, lease_until)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
137
        self.clients[c.client_id].inflight += 1
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
138
        return token, lease_until
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
139
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
140
    def release(self, command_id: str, client_id: str, fence_token: str) -> None:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
141
        for resource, lock in list(self.locks.items()):
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
142
            if lock.command_id == command_id and lock.fence_token == fence_token:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
143
                self.locks.pop(resource, None)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
144
        client = self.clients.get(client_id)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
145
        if client:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
146
            client.inflight = max(0, client.inflight - 1)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
147
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
148
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
149
def self_test() -> None:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
150
    now = utc_now()
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
151
    clients = {
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
152
        "chatgpt-main": Client("chatgpt-main", weight=2, max_inflight=2),
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
153
        "chatgpt-work": Client("chatgpt-work", weight=1, max_inflight=1),
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
154
        "codex": Client("codex", weight=1, max_inflight=2),
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
155
    }
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
156
    scheduler = Scheduler(clients, lease_seconds=60, aging_seconds=10)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
157
    cmds = [
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
158
        Command("a1", "chatgpt-main", 1, now - dt.timedelta(seconds=5), 50, ("apps-script",), "health", coalesce_key="status"),
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
159
        Command("a2", "chatgpt-main", 2, now, 50, ("apps-script",), "health", coalesce_key="status"),
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
160
        Command("b1", "chatgpt-work", 1, now, 100, ("android-build",), "health"),
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
161
        Command("c1", "codex", 1, now, 20, ("apps-script",), "job_status", ttl_seconds=1),
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
162
        Command("c2", "codex", 2, now - dt.timedelta(seconds=20), 20, ("file:project/x",), "git_status", ttl_seconds=5),
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
163
    ]
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
164
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
165
    scheduler.expire_and_coalesce(cmds, now)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
166
    assert cmds[0].status == "superseded", cmds[0].status
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
167
    assert cmds[4].status == "expired", cmds[4].status
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
168
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
169
    first = scheduler.choose(cmds, now)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
170
    assert first and first.command_id == "b1", first
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
171
    token, _ = scheduler.acquire(first, now)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
172
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
173
    second = scheduler.choose(cmds, now)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
174
    assert second and second.command_id == "a2", second
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
175
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
176
    scheduler.release(first.command_id, first.client_id, token)
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
177
    assert clients["chatgpt-work"].inflight == 0
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
178
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
179
    print(json.dumps({
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
180
        "ok": True,
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
181
        "revision": REVISION,
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
182
        "first": first.command_id,
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
183
        "second": second.command_id,
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
184
        "states": {c.command_id: c.status for c in cmds},
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
185
        "execution_enabled": False,
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
186
    }, separators=(",", ":")))
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
187
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
188
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
189
def main() -> None:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
190
    parser = argparse.ArgumentParser()
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
191
    parser.add_argument("--self-test", action="store_true")
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
192
    parser.add_argument("--mode", default="shadow", choices=["shadow"])
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
193
    args = parser.parse_args()
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
194
    if args.self_test:
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
195
        self_test()
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
196
        return
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
197
    print(json.dumps({
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
198
        "ok": True,
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
199
        "revision": REVISION,
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
200
        "mode": args.mode,
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
201
        "execution_enabled": False,
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
202
        "note": "Engine only. Live Sheet adapter not enabled yet.",
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
203
    }, separators=(",", ":")))
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
204
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
205
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
206
if __name__ == "__main__":
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A
207
    main()
Use FILE Use as OLD Use as NEW Open Patch Composer +/-3 CONTEXT +/-10 CONTEXT +/-25 CONTEXT Append to Buffer A