|
5 | 5 | import io |
6 | 6 | import json |
7 | 7 | import math |
| 8 | +import os |
8 | 9 | import pprint |
| 10 | +import threading |
9 | 11 | import uuid |
10 | 12 | import zipfile |
11 | 13 | import zlib |
@@ -58,9 +60,35 @@ class GitHubLauncher(Launcher): |
58 | 60 | def __init__(self, repo: str, token: str, branch: str): |
59 | 61 | super().__init__(name="GitHub", gpus=GitHubGPU) |
60 | 62 | self.repo = repo |
61 | | - self.token = token |
| 63 | + self.tokens = self._load_github_tokens(token) |
| 64 | + self._token_lock = threading.Lock() |
| 65 | + self._token_idx = 0 |
62 | 66 | self.branch = branch |
63 | 67 |
|
| 68 | + @staticmethod |
| 69 | + def _load_github_tokens(fallback_token: str) -> list[str]: |
| 70 | + primary = (os.getenv("GITHUB_TOKEN") or fallback_token).strip() |
| 71 | + backup = (os.getenv("GITHUB_TOKEN_BACKUP") or "").strip() |
| 72 | + |
| 73 | + tokens: list[str] = [] |
| 74 | + for t in (primary, backup): |
| 75 | + if t and t not in tokens: |
| 76 | + tokens.append(t) |
| 77 | + |
| 78 | + if not tokens: |
| 79 | + raise KernelBotError( |
| 80 | + "No GitHub tokens configured. Set GITHUB_TOKEN " |
| 81 | + "(and optionally GITHUB_TOKEN_BACKUP)." |
| 82 | + ) |
| 83 | + |
| 84 | + return tokens |
| 85 | + |
| 86 | + def _next_token(self) -> str: |
| 87 | + with self._token_lock: |
| 88 | + token = self.tokens[self._token_idx] |
| 89 | + self._token_idx = (self._token_idx + 1) % len(self.tokens) |
| 90 | + return token |
| 91 | + |
64 | 92 | async def run_submission( # noqa: C901 |
65 | 93 | self, config: dict, gpu_type: GPU, status: RunProgressReporter |
66 | 94 | ) -> FullResult: |
@@ -89,7 +117,7 @@ async def run_submission( # noqa: C901 |
89 | 117 | lang_name = {"py": "Python", "cu": "CUDA"}[lang] |
90 | 118 |
|
91 | 119 | logger.info(f"Attempting to trigger GitHub action for {lang_name} on {selected_workflow}") |
92 | | - run = GitHubRun(self.repo, self.token, self.branch, selected_workflow) |
| 120 | + run = GitHubRun(self.repo, self._next_token(), self.branch, selected_workflow) |
93 | 121 | logger.info(f"Successfully created GitHub run: {run.run_id}") |
94 | 122 |
|
95 | 123 | payload = base64.b64encode(zlib.compress(json.dumps(config).encode("utf-8"))).decode( |
@@ -176,7 +204,7 @@ class GitHubArtifact: |
176 | 204 | public_download_url: str |
177 | 205 |
|
178 | 206 |
|
179 | | -_WORKFLOW_FILE_CACHE: dict[str, Workflow] = {} |
| 207 | +_WORKFLOW_FILE_CACHE: dict[tuple[str, str], Workflow] = {} |
180 | 208 |
|
181 | 209 |
|
182 | 210 | def patched_create_dispatch( |
@@ -248,12 +276,13 @@ def elapsed_time(self): |
248 | 276 | return datetime.datetime.now(datetime.timezone.utc) - self.start_time |
249 | 277 |
|
250 | 278 | async def get_workflow(self) -> Workflow: |
251 | | - if self.workflow_file in _WORKFLOW_FILE_CACHE: |
| 279 | + cache_key = (self.workflow_file, self.token) |
| 280 | + if cache_key in _WORKFLOW_FILE_CACHE: |
252 | 281 | logger.info(f"Returning cached workflow {self.workflow_file}") |
253 | | - return _WORKFLOW_FILE_CACHE[self.workflow_file] |
| 282 | + return _WORKFLOW_FILE_CACHE[cache_key] |
254 | 283 | logger.info(f"Fetching workflow {self.workflow_file} from GitHub") |
255 | 284 | workflow = self.repo.get_workflow(self.workflow_file) |
256 | | - _WORKFLOW_FILE_CACHE[self.workflow_file] = workflow |
| 285 | + _WORKFLOW_FILE_CACHE[cache_key] = workflow |
257 | 286 | return workflow |
258 | 287 |
|
259 | 288 | async def trigger(self, inputs: dict) -> bool: |
|
0 commit comments