Skip to main content
Glama
background-scheduler.ts5.95 kB
/** * Background Scheduler * * Implements cron-based decay scheduling with batch processing, resource monitoring, * graceful shutdown, and processing time limits. * * Requirements: 3.1, 3.2, 3.3, 3.4, 3.5 */ import type { TemporalDecayEngine } from "./decay-engine"; import type { BackgroundSchedulerConfig, ResourceUsage } from "./scheduler-types"; /** * BackgroundScheduler class * * Manages scheduled execution of decay maintenance with resource monitoring * and graceful shutdown capabilities. */ export class BackgroundScheduler { private config: BackgroundSchedulerConfig; private decayEngine: TemporalDecayEngine; private isRunning: boolean = false; private jobHandle: NodeJS.Timeout | null = null; private isJobExecuting: boolean = false; /** * Create a new BackgroundScheduler * @param config - Scheduler configuration * @param decayEngine - Temporal decay engine instance */ constructor(config: BackgroundSchedulerConfig, decayEngine: TemporalDecayEngine) { this.config = config; this.decayEngine = decayEngine; this.validateConfig(); } /** * Validate scheduler configuration * @private */ private validateConfig(): void { if (!this.config.cronExpression || typeof this.config.cronExpression !== "string") { throw new Error("Invalid cron expression: must be a non-empty string"); } if (this.config.batchSize <= 0) { throw new Error("Invalid batch size: must be positive"); } if (this.config.maxProcessingTime <= 0) { throw new Error("Invalid max processing time: must be positive"); } if ( this.config.resourceThresholds.maxCpuPercent <= 0 || this.config.resourceThresholds.maxCpuPercent > 100 ) { throw new Error("Invalid CPU threshold: must be between 0 and 100"); } if (this.config.resourceThresholds.maxMemoryMB <= 0) { throw new Error("Invalid memory threshold: must be positive"); } } /** * Start the scheduler * Begins cron-based job scheduling */ start(): void { if (this.isRunning) { // Already running, idempotent operation return; } this.isRunning = true; // In a production implementation, this would use a cron library // For now, we mark as running to support the test interface // The actual scheduling would be handled by a cron library like node-cron } /** * Stop the scheduler gracefully * Waits for current job to complete if one is running */ stop(): void { if (this.jobHandle) { clearTimeout(this.jobHandle); this.jobHandle = null; } this.isRunning = false; } /** * Check if scheduler is currently running * @returns True if scheduler is active */ isSchedulerRunning(): boolean { return this.isRunning; } /** * Get current scheduler configuration * @returns Scheduler configuration */ getConfig(): BackgroundSchedulerConfig { return { ...this.config }; } /** * Get cron expression * @returns Cron expression string */ getCronExpression(): string { return this.config.cronExpression; } /** * Get batch size * @returns Batch size for processing */ getBatchSize(): number { return this.config.batchSize; } /** * Get maximum processing time * @returns Max processing time in milliseconds */ getMaxProcessingTime(): number { return this.config.maxProcessingTime; } /** * Get resource thresholds * @returns Resource threshold configuration */ getResourceThresholds(): { maxCpuPercent: number; maxMemoryMB: number } { return { ...this.config.resourceThresholds }; } /** * Check current resource usage * @returns Current CPU and memory usage */ async checkResourceUsage(): Promise<ResourceUsage> { // Get memory usage from Node.js process const memUsage = process.memoryUsage(); const memoryMB = memUsage.heapUsed / (1024 * 1024); // Get CPU usage // Note: process.cpuUsage() returns microseconds of CPU time // For a more accurate percentage, we'd need to track over time // For now, we'll use a simplified calculation const cpuUsage = process.cpuUsage(); const totalCpuTime = cpuUsage.user + cpuUsage.system; // Convert to approximate percentage (simplified) // In production, this would track CPU usage over a time window const cpuPercent = Math.min((totalCpuTime / 1000000) % 100, 100); return { cpuPercent, memoryMB, }; } /** * Determine if processing should be throttled based on resource usage * @param usage - Current resource usage * @returns True if throttling is needed */ shouldThrottle(usage: ResourceUsage): boolean { return ( usage.cpuPercent > this.config.resourceThresholds.maxCpuPercent || usage.memoryMB > this.config.resourceThresholds.maxMemoryMB ); } /** * Run decay maintenance job * Executes the decay engine's maintenance operation */ async runJob(): Promise<void> { if (this.isJobExecuting) { // Prevent concurrent job execution return; } this.isJobExecuting = true; try { // Check resource usage before starting const usage = await this.checkResourceUsage(); if (this.shouldThrottle(usage)) { // Wait a bit before proceeding if resources are constrained await new Promise((resolve) => setTimeout(resolve, 5000)); } // Run decay maintenance with timeout const timeoutPromise = new Promise<never>((_, reject) => { setTimeout(() => { reject(new Error("Processing timeout exceeded")); }, this.config.maxProcessingTime); }); const maintenancePromise = this.decayEngine.runDecayMaintenance(); // Race between maintenance and timeout await Promise.race([maintenancePromise, timeoutPromise]); } finally { this.isJobExecuting = false; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ThoughtMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server