opusdev/vector-similarity-api
1
1import type { ChildProcess } from 'child_process';2 3import { MongoNetworkTimeoutError } from '../error';4import { type AutoEncryptionExtraOptions } from './auto_encrypter';5 6/**7 * @internal8 * An internal class that handles spawning a mongocryptd.9 */10export class MongocryptdManager {11 static DEFAULT_MONGOCRYPTD_URI = 'mongodb://localhost:27020';12 13 uri: string;14 bypassSpawn: boolean;15 spawnPath = '';16 spawnArgs: Array<string> = [];17 _child?: ChildProcess;18 19 constructor(extraOptions: AutoEncryptionExtraOptions = {}) {20 this.uri =21 typeof extraOptions.mongocryptdURI === 'string' && extraOptions.mongocryptdURI.length > 022 ? extraOptions.mongocryptdURI23 : MongocryptdManager.DEFAULT_MONGOCRYPTD_URI;24 25 this.bypassSpawn = !!extraOptions.mongocryptdBypassSpawn;26 27 if (Object.hasOwn(extraOptions, 'mongocryptdSpawnPath') && extraOptions.mongocryptdSpawnPath) {28 this.spawnPath = extraOptions.mongocryptdSpawnPath;29 }30 if (31 Object.hasOwn(extraOptions, 'mongocryptdSpawnArgs') &&32 Array.isArray(extraOptions.mongocryptdSpawnArgs)33 ) {34 this.spawnArgs = this.spawnArgs.concat(extraOptions.mongocryptdSpawnArgs);35 }36 if (37 this.spawnArgs38 .filter(arg => typeof arg === 'string')39 .every(arg => arg.indexOf('--idleShutdownTimeoutSecs') < 0)40 ) {41 this.spawnArgs.push('--idleShutdownTimeoutSecs', '60');42 }43 }44 45 /**46 * Will check to see if a mongocryptd is up. If it is not up, it will attempt47 * to spawn a mongocryptd in a detached process, and then wait for it to be up.48 */49 async spawn(): Promise<void> {50 const cmdName = this.spawnPath || 'mongocryptd';51 52 // eslint-disable-next-line @typescript-eslint/no-require-imports53 const { spawn } = require('child_process') as typeof import('child_process');54 55 // Spawned with stdio: ignore and detached: true56 // to ensure child can outlive parent.57 this._child = spawn(cmdName, this.spawnArgs, {58 stdio: 'ignore',59 detached: true60 });61 62 this._child.on('error', () => {63 // From the FLE spec:64 // "The stdout and stderr of the spawned process MUST not be exposed in the driver65 // (e.g. redirect to /dev/null). Users can pass the argument --logpath to66 // extraOptions.mongocryptdSpawnArgs if they need to inspect mongocryptd logs.67 // If spawning is necessary, the driver MUST spawn mongocryptd whenever server68 // selection on the MongoClient to mongocryptd fails. If the MongoClient fails to69 // connect after spawning, the server selection error is propagated to the user."70 // The AutoEncrypter and MongoCryptdManager should work together to spawn71 // mongocryptd whenever necessary. Additionally, the `mongocryptd` intentionally72 // shuts down after 60s and gets respawned when necessary. We rely on server73 // selection timeouts when connecting to the `mongocryptd` to inform users that something74 // has been configured incorrectly. For those reasons, we suppress stderr from75 // the `mongocryptd` process and immediately unref the process.76 });77 78 // unref child to remove handle from event loop79 this._child.unref();80 }81 82 /**83 * @returns the result of `fn` or rejects with an error.84 */85 async withRespawn<T>(fn: () => Promise<T>): ReturnType<typeof fn> {86 try {87 const result = await fn();88 return result;89 } catch (err) {90 // If we are not bypassing spawning, then we should retry once on a MongoTimeoutError (server selection error)91 const shouldSpawn = err instanceof MongoNetworkTimeoutError && !this.bypassSpawn;92 if (!shouldSpawn) {93 throw err;94 }95 }96 await this.spawn();97 const result = await fn();98 return result;99 }100}101 