server: cache workspace shortstat and warm in background

This commit is contained in:
Mohamed Boudra
2026-04-05 22:28:07 +07:00
parent ce821afd27
commit 25213ce83a
3 changed files with 114 additions and 6 deletions
+84 -1
View File
@@ -20,11 +20,16 @@ const READ_ONLY_GIT_ENV: NodeJS.ProcessEnv = {
const SMALL_OUTPUT_MAX_BUFFER = 20 * 1024 * 1024; // 20MB
const DEFAULT_PULL_REQUEST_STATUS_CACHE_TTL_MS = 30_000;
const PULL_REQUEST_STATUS_CACHE_MAX = 1_000;
const DEFAULT_SHORTSTAT_CACHE_TTL_MS = 15_000;
const SHORTSTAT_CACHE_MAX = 1_000;
let pullRequestStatusCacheTtlMs = DEFAULT_PULL_REQUEST_STATUS_CACHE_TTL_MS;
let pullRequestStatusCache = createPullRequestStatusCache(pullRequestStatusCacheTtlMs);
const pullRequestStatusInFlight = new Map<string, Promise<PullRequestStatusResult>>();
let cachedGhPath: string | null | undefined = undefined;
let shortstatCacheTtlMs = DEFAULT_SHORTSTAT_CACHE_TTL_MS;
let shortstatCache = createShortstatCache(shortstatCacheTtlMs);
const shortstatInFlight = new Map<string, Promise<CheckoutShortstat | null>>();
function createPullRequestStatusCache(ttlMs: number) {
return new TTLCache<string, PullRequestStatusResult>({
@@ -34,10 +39,22 @@ function createPullRequestStatusCache(ttlMs: number) {
});
}
function createShortstatCache(ttlMs: number) {
return new TTLCache<string, CheckoutShortstat | null>({
ttl: ttlMs,
max: SHORTSTAT_CACHE_MAX,
checkAgeOnGet: true,
});
}
function getPullRequestStatusCacheKey(cwd: string): string {
return resolve(cwd);
}
function getShortstatCacheKey(cwd: string): string {
return resolve(cwd);
}
export function __resetPullRequestStatusCacheForTests(): void {
pullRequestStatusCache.clear();
pullRequestStatusCache.cancelTimer();
@@ -62,6 +79,22 @@ export function __setGhPathForTests(path: string | null): void {
cachedGhPath = path;
}
export function __resetCheckoutShortstatCacheForTests(): void {
shortstatCache.clear();
shortstatCache.cancelTimer();
shortstatCacheTtlMs = DEFAULT_SHORTSTAT_CACHE_TTL_MS;
shortstatCache = createShortstatCache(shortstatCacheTtlMs);
shortstatInFlight.clear();
}
export function __setCheckoutShortstatCacheTtlForTests(ttlMs: number): void {
shortstatCache.clear();
shortstatCache.cancelTimer();
shortstatCacheTtlMs = ttlMs;
shortstatCache = createShortstatCache(ttlMs);
shortstatInFlight.clear();
}
async function execGit(
command: string,
options: { cwd: string; env?: NodeJS.ProcessEnv },
@@ -1194,7 +1227,7 @@ export interface CheckoutShortstat {
deletions: number;
}
export async function getCheckoutShortstat(
async function getCheckoutShortstatUncached(
cwd: string,
context?: CheckoutContext,
): Promise<CheckoutShortstat | null> {
@@ -1273,6 +1306,56 @@ export async function getCheckoutShortstat(
}
}
function getOrLoadCheckoutShortstat(
cwd: string,
context?: CheckoutContext,
): Promise<CheckoutShortstat | null> {
const cacheKey = getShortstatCacheKey(cwd);
const cached = shortstatCache.get(cacheKey);
if (cached !== undefined) {
return Promise.resolve(cached);
}
const existing = shortstatInFlight.get(cacheKey);
if (existing) {
return existing;
}
const load = getCheckoutShortstatUncached(cwd, context)
.then((shortstat) => {
shortstatCache.set(cacheKey, shortstat);
return shortstat;
})
.finally(() => {
shortstatInFlight.delete(cacheKey);
});
shortstatInFlight.set(cacheKey, load);
return load;
}
export async function getCheckoutShortstat(
cwd: string,
context?: CheckoutContext,
): Promise<CheckoutShortstat | null> {
return getOrLoadCheckoutShortstat(cwd, context);
}
export function getCachedCheckoutShortstat(cwd: string): CheckoutShortstat | null | undefined {
return shortstatCache.get(getShortstatCacheKey(cwd));
}
export function warmCheckoutShortstatInBackground(cwd: string, context?: CheckoutContext): void {
const cacheKey = getShortstatCacheKey(cwd);
if (shortstatCache.get(cacheKey) !== undefined || shortstatInFlight.has(cacheKey)) {
return;
}
void getOrLoadCheckoutShortstat(cwd, context).catch(() => {
// Non-critical: keep listing path resilient even if git commands fail.
});
}
export async function getCheckoutDiff(
cwd: string,
compare: CheckoutDiffCompare,