55 lines · 1.7 KB
import { env } from "cloudflare:workers";
import type { Doorbell } from "./job-doorbell-do";

type Scoped = { repo_id: string | null; owner_id: string | null };

function bell(key: string) {
  return env.JOB_DOORBELL.get(env.JOB_DOORBELL.idFromName(key));
}

function scopeKeys(runner: Scoped): string[] {
  return [
    ...(runner.repo_id ? [`repo:${runner.repo_id}`] : []),
    ...(runner.owner_id ? [`owner:${runner.owner_id}`] : []),
  ];
}

/** After a job becomes claimable: wakes runners registered to its repo and to its owner. */
export async function ringJobDoorbells(repoId: string, ownerId: string): Promise<void> {
  await Promise.all(
    [`repo:${repoId}`, `owner:${ownerId}`].map(async (key) => {
      try {
        await bell(key).ring();
      } catch (error) {
        console.error("Job doorbell ring failed", key, error);
      }
    }),
  );
}

export type RunnerBells = { ringing: boolean; silence(): Promise<void> };

/** Whether this runner's scopes may have queued work; failures count as ringing so jobs are never stranded. */
export async function checkRunnerBells(runner: Scoped): Promise<RunnerBells> {
  const keys = scopeKeys(runner);
  let states: Array<{ key: string; state: Doorbell }>;
  try {
    states = await Promise.all(keys.map(async (key) => ({ key, state: await bell(key).check() })));
  } catch (error) {
    console.error("Job doorbell check failed", error);
    return { ringing: true, silence: async () => {} };