Problem
In src/services/config-service.ts inside initialize(), the timeoutId variable that holds the 10-second hard rejection timeout is overwritten on line 83 whenever the connection state is neither 0 nor 1 (e.g. state 2 = connecting, state 3 = disconnecting):
let timeoutId: ReturnType<typeof setTimeout>;
timeoutId = setTimeout(() => {
reject(new Error("MongoDB connection timeout"));
}, 10000); // ← outer 10s timer
const checkConnection = async (): Promise<void> => {
try {
if (mongoose.connection.readyState === 1) {
clearTimeout(timeoutId); // clears the 100ms poll, NOT the 10s timer
resolve();
} else if (mongoose.connection.readyState === 0) {
await mongoose.connect(...);
clearTimeout(timeoutId); // same problem
resolve();
} else {
timeoutId = setTimeout(checkConnection, 100); // ← OVERWRITES the reference to the 10s timer
}
} catch (error) { reject(error); }
};
After the first poll cycle where state is 2 or 3, timeoutId points to the 100ms poll timer instead of the 10s rejection timer. When the connection finally succeeds and clearTimeout(timeoutId) is called, it clears the already-fired 100ms timer and the original 10s timer is leaked. That leaked timer will call reject() ~10 seconds later — after resolve() was already called — which is silently swallowed by the Promise machinery but can produce confusing debug output and wastes a timer slot.
Suggested fix
Use a separate variable for the poll timer so the original rejection timer reference is never overwritten:
const rejectTimeoutId = setTimeout(() => {
reject(new Error("MongoDB connection timeout"));
}, 10000);
let pollTimeoutId: ReturnType<typeof setTimeout> | null = null;
const checkConnection = async (): Promise<void> => {
try {
if (mongoose.connection.readyState === 1) {
clearTimeout(rejectTimeoutId);
if (pollTimeoutId) clearTimeout(pollTimeoutId);
resolve();
} else if (mongoose.connection.readyState === 0) {
await mongoose.connect(process.env.MONGODB_URI || "mongodb://mongodb:27017/koolbot");
clearTimeout(rejectTimeoutId);
if (pollTimeoutId) clearTimeout(pollTimeoutId);
resolve();
} else {
pollTimeoutId = setTimeout(checkConnection, 100);
}
} catch (error) {
clearTimeout(rejectTimeoutId);
reject(error);
}
};
Affected file
src/services/config-service.ts lines 64–88 (initialize())
Problem
In
src/services/config-service.tsinsideinitialize(), thetimeoutIdvariable that holds the 10-second hard rejection timeout is overwritten on line 83 whenever the connection state is neither 0 nor 1 (e.g. state 2 = connecting, state 3 = disconnecting):After the first poll cycle where state is 2 or 3,
timeoutIdpoints to the 100ms poll timer instead of the 10s rejection timer. When the connection finally succeeds andclearTimeout(timeoutId)is called, it clears the already-fired 100ms timer and the original 10s timer is leaked. That leaked timer will callreject()~10 seconds later — afterresolve()was already called — which is silently swallowed by the Promise machinery but can produce confusing debug output and wastes a timer slot.Suggested fix
Use a separate variable for the poll timer so the original rejection timer reference is never overwritten:
Affected file
src/services/config-service.tslines 64–88 (initialize())