All files / server/services compiler-with-fallback.ts

71.05% Statements 27/38
57.14% Branches 16/28
75% Functions 6/8
71.05% Lines 27/38

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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156                                                  10x             10x     10x   10x 3x 7x                   7x                             3x 1x 1x 1x                     2x 1x   1x                     1x   1x 1x 1x           1x                                     11x 1x   10x                           2x 1x               3x     10x 10x        
/**
 * CompilerWithFallback — Adapter that routes compile work to the worker pool
 * (production) or to the in-process ArduinoCompiler (development).
 *
 * NOTE: This is NOT the worker pool itself. The pool lives in
 *       `compilation-worker-pool.ts`. This class is the adapter that picks
 *       a backend at runtime and exposes a `compile()` method that matches
 *       ArduinoCompiler for drop-in compatibility.
 *
 * Mode selection:
 *   • production (server runs in docker)      → CompilationWorkerPool
 *   • development (tsx, worker @shared/* fail) → direct ArduinoCompiler fallback
 *
 * Renamed from `PooledCompiler` — the old name suggested this class WAS the
 * pool, which was misleading.
 */
 
import { CompilationWorkerPool, getCompilationPool } from "./compilation-worker-pool";
import { ArduinoCompiler } from "./arduino-compiler";
import type { CompilationResult, CompileRequestOptions } from "./arduino-compiler";
import type { CompileRequestPayload } from "@shared/worker-protocol";
import { config } from "../config";
import { compileMetricsTracker } from "./server-metrics";
 
export class CompilerWithFallback {
  readonly tracksCompileMetrics = true;
  private readonly pool: CompilationWorkerPool | null;
  private readonly directCompiler: ArduinoCompiler;
  private readonly usePool: boolean;
 
  constructor(pool?: CompilationWorkerPool) {
    // Always initialize direct compiler as fallback
    this.directCompiler = new ArduinoCompiler();
    
    // Try to use worker pool in production if available
    this.usePool = config.serverMode === "docker";
    
    if (this.usePool && pool) {
      this.pool = pool;
    I} else if (this.usePool) {
      try {
        this.pool = getCompilationPool();
      } catch {
        // Worker pool unavailable (e.g., worker files not found) - fall back to direct compiler
        // This is expected in development mode and is handled gracefully
        this.pool = null;
      }
    } else {
      // Development mode: use direct compiler (worker threads don't work with tsx/@shared/*)
      this.pool = null;
    }
  }
 
  /**
   * Compile code through the worker pool (production) or directly (development)
   * 
   * Signature matches ArduinoCompiler.compile() for drop-in compatibility
   */
  async compile(
    code: string,
    headers?: Array<{ name: string; content: string }>,
    tempRoot?: string,
    options?: CompileRequestOptions,
  ): Promise<CompilationResult> {
    if (this.usePool && this.pool) {
      try {
        const task: CompileRequestPayload = { code, headers, tempRoot, ...options };
        return await this.pool.compile(task);
      } catch (error) {
        // Pool failed to compile (e.g., workers not operational) - fall back to direct compiler
        // This is an expected fallback path when workers are unavailable
        if (!this.directCompiler) {
          throw new Error("Neither pool nor direct compiler available");
        }
        return await this.compileDirectWithMetrics(code, headers, tempRoot, options, error);
      }
    } else {
      // Fall back to direct compiler (always available)
      if (!this.directCompiler) {
        throw new Error("Neither pool nor direct compiler available");
      }
      return await this.compileDirectWithMetrics(code, headers, tempRoot, options);
    }
  }
 
  private async compileDirectWithMetrics(
    code: string,
    headers?: Array<{ name: string; content: string }>,
    tempRoot?: string,
    options?: CompileRequestOptions,
    poolError?: unknown,
  ): Promise<CompilationResult> {
    const compileStartTime = Date.now();
 
    try {
      const result = await this.directCompiler.compile(code, headers, tempRoot, options);
      compileMetricsTracker.recordCompileComplete(
        compileStartTime,
        0,
        result.success,
        !result.success && `${result.stderr ?? ""} ${result.errors.map((err) => err.message).join(" ")}`.toLowerCase().includes("timeout"),
      );
      return result;
    } catch (error) {
      const message = `${poolError instanceof Error ? poolError.message : ""} ${error instanceof Error ? error.message : String(error)}`;
      compileMetricsTracker.recordCompileComplete(compileStartTime, 0, false, message.toLowerCase().includes("timeout"));
      throw error;
    }
  }
 
  /**
   * Check if worker pool is operational
   */
  isOperational(): boolean {
    return this.usePool && this.pool !== null;
  }
 
  /**
   * Get current pool statistics (production only)
   */
  getStats() {
    if (this.pool) {
      return this.pool.getStats();
    }
    return {
      activeWorkers: 0,
      totalTasks: 0,
      completedTasks: 0,
      failedTasks: 0,
      avgCompileTimeMs: 0,
      queuedTasks: 0,
    };
  }
 
  /**
   * Gracefully shutdown the pool (production only)
   */
  async shutdown(): Promise<void> {
    if (this.pool) {
      await this.pool.shutdown();
    }
  }
}
 
/**
 * Singleton instance for application-wide use
 */
let compilerInstance: CompilerWithFallback | null = null;
 
export function getCompilerWithFallback(): CompilerWithFallback {
  compilerInstance ??= new CompilerWithFallback();
  return compilerInstance;
}
 
// setCompilerWithFallback removed; not needed