Regola
Rilascia i lock anche nei percorsi di eccezione.
Ogni acquisizione di lock deve avere un rilascio
garantito, anche quando si verificano eccezioni.
Linguaggi supportati:** Java, C, C++, PHP, JavaScript,
TypeScript, Go, PythonIntroduzione
I lock non rilasciati rappresentano una delle cause più comuni di deadlock e blocchi di sistema nelle applicazioni Node.js in produzione. Quando si verifica un'eccezione tra l'acquisizione e il rilascio del lock, questo rimane occupato a tempo indeterminato. Altre operazioni asincrone in attesa di quel lock si bloccano per sempre, innescando guasti a catena in tutto il sistema. Un singolo mutex non rilasciato può compromettere un'intera API poiché il ciclo degli eventi viene bloccato e le richieste si accumulano. Questo problema si verifica con librerie come async-mutex, mutexify, o con qualsiasi implementazione di lock manuale in cui il rilascio non avviene automaticamente.
Perché è importante
Stabilità e disponibilità del sistema: I lock non rilasciati provocano deadlock che bloccano le operazioni asincrone in Node.js. Nei server Express o Fastify, questo esaurisce i worker disponibili, impedendo all'applicazione di gestire nuove richieste. L'unico modo per ripristinare il servizio è riavviare il processo, causando un periodo di downtime. Nelle architetture a microservizi, i lock non rilasciati in un servizio possono propagare guasti ai servizi dipendenti a causa del timeout durante l'attesa delle risposte.
Degradazione delle performance: Prima di arrivare a un deadlock totale, i lock non rilasciati causano gravi problemi di performance. Le operazioni asincrone competono per le risorse bloccate, creando una coda di promise pendenti che non verranno mai risolte. La contesa dei lock genera picchi di latenza imprevedibili che peggiorano l'esperienza utente. All'aumentare del numero di richieste concorrenti sotto carico, la contesa si intensifica in modo esponenziale.
Complessità di debugging: I deadlock causati da lock non rilasciati sono notoriamente difficili da debuggare nelle app Node.js in produzione. I sintomi si manifestano lontano dalla causa principale e i processi bloccati mostrano promise in sospeso, ma non specificano quale percorso di eccezione non sia riuscito a rilasciare il lock. Riprodurre l'esatta sequenza di eccezioni che ha attivato il deadlock risulta spesso impossibile negli ambienti di sviluppo.
Esaurimento delle risorse: Oltre ai lock stessi, la mancata liberazione delle risorse è spesso correlata all'incapacità di rilasciare altri elementi come connessioni al database, client Redis o file handle. Questo aggrava il problema, generando molteplici leak di risorse che fanno collassare i sistemi ancora più rapidamente sotto carico.
Esempi di codice
❌ Non conforme:
const { Mutex } = require('async-mutex');
const accountMutex = new Mutex();
async function transferFunds(from, to, amount) {
await accountMutex.acquire();
if (from.balance < amount) {
throw new Error('Insufficient funds');
}
from.balance -= amount;
to.balance += amount;
accountMutex.release();
}
Perché non è sicuro: Se viene generato l'errore per fondi insufficienti, accountMutex.release() non viene mai eseguito e il mutex rimane bloccato per sempre. Tutte le chiamate successive a transferFunds() rimarrà in attesa del mutex, bloccando l'intero sistema di pagamento.
✅ Conforme:
const { Mutex } = require('async-mutex');
const accountMutex = new Mutex();
async function transferFunds(from, to, amount) {
const release = await accountMutex.acquire();
try {
if (from.balance < amount) {
throw new Error('Insufficient funds');
}
from.balance -= amount;
to.balance += amount;
} catch (error) {
logger.error('Transfer failed', {
fromId: from.id,
toId: to.id,
amount,
error: error.message
});
throw error;
} finally {
release();
}
}Perché è sicuro: Il parametro catch il blocco registra l'errore con il relativo contesto prima di rilanciarlo, e il finally blocco garantisce che la funzione di rilascio del mutex venga eseguita indipendentemente dal fatto che l'operazione vada a buon fine, generi un errore o che l'errore venga rilanciato dal blocco catch. Il lock viene rilasciato in ogni caso, prevenendo i deadlock.
Conclusione
Il rilascio del lock deve essere garantito e non subordinato al successo dell'esecuzione. Utilizza try-finally blocchi in JavaScript o l'helper runExclusive() fornito da librerie come async-mutex. Ogni acquisizione di un lock dovrebbe prevedere un percorso di rilascio incondizionato visibile nello stesso blocco di codice. Una corretta gestione dei lock non è un'opzione, è ciò che fa la differenza tra un sistema stabile e uno che si blocca casualmente sotto carico.

