Repository: compiler-explorer/compiler-explorer
Pull request #6610 (2024-06-16 00:57:53)

Attempt to detect faulty compiler/language population
Would maybe fix super rare issue which causes 404's when compiling and when CE failed to detect the error at startup.


Patch:
diff --git a/app.ts b/app.ts
index 92ba44195..a7837100c 100755
--- a/app.ts
+++ b/app.ts
@@ -538,10 +538,16 @@ async function main() {
     if (opts.prediscovered) {
         const prediscoveredCompilersJson = await fs.readFile(opts.prediscovered, 'utf8');
         initialCompilers = JSON.parse(prediscoveredCompilersJson);
-        await compilerFinder.loadPrediscovered(initialCompilers);
+        const prediscResult = await compilerFinder.loadPrediscovered(initialCompilers);
+        if (prediscResult.length === 0) {
+            throw new Error('Unexpected failure, no compilers found!');
+        }
     } else {
         const initialFindResults = await compilerFinder.find();
         initialCompilers = initialFindResults.compilers;
+        if (initialCompilers.length === 0) {
+            throw new Error('Unexpected failure, no compilers found!');
+        }
         if (defArgs.ensureNoCompilerClash) {
             logger.warn('Ensuring no compiler ids clash');
             if (initialFindResults.foundClash) {
@@ -642,7 +648,10 @@ async function main() {
             }),
         )
         // Handle healthchecks at the root, as they're not expected from the outside world
-        .use('/healthcheck', new healthCheck.HealthCheckHandler(compilationQueue, healthCheckFilePath).handle)
+        .use(
+            '/healthcheck',
+            new healthCheck.HealthCheckHandler(compilationQueue, healthCheckFilePath, compileHandler).handle,
+        )
         .use(httpRoot, router)
         .use((req, res, next) => {
             next({status: 404, message: `page "${req.path}" could not be found`});
diff --git a/lib/handlers/compile.interfaces.ts b/lib/handlers/compile.interfaces.ts
index 464f0c647..ec214dcab 100644
--- a/lib/handlers/compile.interfaces.ts
+++ b/lib/handlers/compile.interfaces.ts
@@ -62,3 +62,7 @@ export type CompileRequestTextBody = {
     skipAsm: string;
     filterAnsi?: string;
 };
+
+export interface ICompileHandler {
+    hasLanguages(): boolean;
+}
diff --git a/lib/handlers/compile.ts b/lib/handlers/compile.ts
index 0efda1b7f..445d24203 100644
--- a/lib/handlers/compile.ts
+++ b/lib/handlers/compile.ts
@@ -41,7 +41,7 @@ import {
     FiledataPair,
 } from '../../types/compilation/compilation.interfaces.js';
 import {CompilerOverrideOptions} from '../../types/compilation/compiler-overrides.interfaces.js';
-import {ICompiler, PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js';
+import {CompilerInfo, ICompiler, PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js';
 import {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
 import {ResultLine} from '../../types/resultline/resultline.interfaces.js';
 import {BaseCompiler} from '../base-compiler.js';
@@ -54,7 +54,12 @@ import {SentryCapture} from '../sentry.js';
 import {KnownBuildMethod} from '../stats.js';
 import * as utils from '../utils.js';
 
-import {CompileRequestJsonBody, CompileRequestQueryArgs, CompileRequestTextBody} from './compile.interfaces.js';
+import {
+    CompileRequestJsonBody,
+    CompileRequestQueryArgs,
+    CompileRequestTextBody,
+    ICompileHandler,
+} from './compile.interfaces.js';
 
 temp.track();
 
@@ -97,7 +102,7 @@ export type ParsedRequest = {
     libraries: CompileChildLibraries[];
 };
 
-export class CompileHandler {
+export class CompileHandler implements ICompileHandler {
     private compilersById: Record<string, Record<string, BaseCompiler>> = {};
     private readonly compilerEnv: CompilationEnvironment;
     private readonly textBanner: string;
@@ -200,6 +205,14 @@ export class CompileHandler {
         });
     }
 
+    hasLanguages(): boolean {
+        try {
+            return Object.keys(this.compilersById).length > 0;
+        } catch {
+            return false;
+        }
+    }
+
     async create(compiler: PreliminaryCompilerInfo): Promise<ICompiler | null> {
         const isPrediscovered = !!compiler.version;
 
@@ -253,7 +266,10 @@ export class CompileHandler {
         }
     }
 
-    async setCompilers(compilers: PreliminaryCompilerInfo[], clientOptions: ClientOptionsType) {
+    async setCompilers(
+        compilers: PreliminaryCompilerInfo[],
+        clientOptions: ClientOptionsType,
+    ): Promise<CompilerInfo[]> {
         // Be careful not to update this.compilersById until we can replace it entirely.
         const compilersById = {};
         try {
@@ -278,6 +294,7 @@ export class CompileHandler {
             return createdCompilers.map(compiler => compiler.getInfo());
         } catch (err) {
             logger.error('Exception while processing compilers:', err);
+            return [];
         }
     }
 
diff --git a/lib/handlers/health-check.ts b/lib/handlers/health-check.ts
index eade683dc..cad3f0cba 100644
--- a/lib/handlers/health-check.ts
+++ b/lib/handlers/health-check.ts
@@ -29,12 +29,15 @@ import {CompilationQueue} from '../compilation-queue.js';
 import {logger} from '../logger.js';
 import {SentryCapture} from '../sentry.js';
 
+import {ICompileHandler} from './compile.interfaces.js';
+
 export class HealthCheckHandler {
     public readonly handle: (req: any, res: any) => Promise<void>;
 
     constructor(
         private readonly compilationQueue: CompilationQueue,
         private readonly filePath: any,
+        private readonly compileHandler: ICompileHandler,
     ) {
         this.handle = this._handle.bind(this);
     }
@@ -52,6 +55,12 @@ export class HealthCheckHandler {
          */
         await this.compilationQueue.enqueue(async () => {}, {highPriority: true});
 
+        if (!this.compileHandler.hasLanguages()) {
+            logger.error(`*** HEALTH CHECK FAILURE: no languages/compilers detected`);
+            res.status(500).end();
+            return;
+        }
+
         if (!this.filePath) {
             res.send('Everything is awesome');
             return;


Test patch:
diff --git a/test/handlers/health-check-tests.ts b/test/handlers/health-check-tests.ts
index 8e9a3b0a5..da7889d51 100644
--- a/test/handlers/health-check-tests.ts
+++ b/test/handlers/health-check-tests.ts
@@ -35,9 +35,12 @@ describe('Health checks', () => {
     let compilationQueue;
 
     beforeEach(() => {
+        const compileHandlerMock = {
+            hasLanguages: () => true,
+        };
         compilationQueue = new CompilationQueue(1, 0, 0);
         app = express();
-        app.use('/hc', new HealthCheckHandler(compilationQueue, '').handle);
+        app.use('/hc', new HealthCheckHandler(compilationQueue, '', compileHandlerMock).handle);
     });
 
     it('should respond with OK', async () => {
@@ -54,15 +57,36 @@ describe('Health checks', () => {
     });
 });
 
+describe('Health checks without lang/comp', () => {
+    let app;
+    let compilationQueue;
+
+    beforeEach(() => {
+        const compileHandlerMock = {
+            hasLanguages: () => false,
+        };
+        compilationQueue = new CompilationQueue(1, 0, 0);
+        app = express();
+        app.use('/hc', new HealthCheckHandler(compilationQueue, '', compileHandlerMock).handle);
+    });
+
+    it('should respond with error', async () => {
+        await request(app).get('/hc').expect(500);
+    });
+});
+
 describe('Health checks on disk', () => {
     let app;
 
     beforeAll(() => {
+        const compileHandlerMock = {
+            hasLanguages: () => true,
+        };
         const compilationQueue = new CompilationQueue(1, 0, 0);
 
         app = express();
-        app.use('/hc', new HealthCheckHandler(compilationQueue, '/fake/.nonexist').handle);
-        app.use('/hc2', new HealthCheckHandler(compilationQueue, '/fake/.health').handle);
+        app.use('/hc', new HealthCheckHandler(compilationQueue, '/fake/.nonexist', compileHandlerMock).handle);
+        app.use('/hc2', new HealthCheckHandler(compilationQueue, '/fake/.health', compileHandlerMock).handle);
 
         mockfs({
             '/fake': {