All files / src/plugin refresh-guard.ts

0% Statements 0/33
0% Branches 0/10
0% Functions 0/7
0% Lines 0/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63                                                                                                                             
import { DatabaseSync } from 'node:sqlite';
import { openSync, closeSync } from 'node:fs';
import { setTimeout } from 'node:timers/promises';
import { performance } from 'node:perf_hooks';
import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
 
const SQLITE_BUSY = 5;
 
/** An empty database supplies an OS-owned mutex, independently of connection state writes. */
export class RefreshGuard {
  private readonly db: DatabaseSync;
  private tail: Promise<void> = Promise.resolve();
  private closing = false;
 
  constructor(path: string) {
    closeSync(openSync(path, 'a', 0o600));
    this.db = new DatabaseSync(path, { timeout: 0 });
  }
 
  run<T>(operation: () => Promise<T>): Promise<T> {
    const result = this.tail.then(async () => {
      await this.acquire();
      try {
        return await operation();
      } finally {
        if (!this.closing) this.db.exec('ROLLBACK');
      }
    });
    this.tail = result.then(
      () => {},
      () => {},
    );
    return result;
  }
 
  private async acquire(): Promise<void> {
    const deadline = performance.now() + DEFAULT_REQUEST_TIMEOUT_MSEC;
    while (!this.closing) {
      try {
        this.db.exec('BEGIN IMMEDIATE');
        return;
      } catch (error) {
        if (
          !(error instanceof Error) ||
          !('errcode' in error) ||
          error.errcode !== SQLITE_BUSY ||
          performance.now() >= deadline
        )
          throw new Error('Could not acquire the shared OAuth refresh lock.', { cause: error });
        await setTimeout(10);
      }
    }
    throw new Error('OAuth connection closed.');
  }
 
  async close(): Promise<void> {
    if (this.closing) return;
    this.closing = true;
    this.db.close();
    await this.tail;
  }
}