Accesso contemporaneo ai file e lock in PHP
Non � raro che una pagina di un sito internet scritta in PHP debba accedere in lettura o in scrittura a dei file, come un database SQLite, un'immagine generata o modificata dinamicamente con librerie GD, un documento pdf aggiornato con FPDF, un file compresso generato al volo per il download da parte dell'utente.
Immaginiamo che tale pagina PHP del nostro sito internet venga richiesta contemporaneamente da due utenti nella rete. Di conseguenza, avremo due esecuzioni quasi contemporanee dello script, che chiameremo, in ordine cronologico, Processo A e Processo B. In realt�, non � sempre vero che ogni esecuzione di uno script generi un
processo separato, ma per semplicit� assumeremo che sia cos�. I due processi cercheranno di leggere o scrivere contemporaneamente sul file
pippo.txt, generando dei problemi. In particolare, per un server Windows:
- Se il processo B, lanciato per secondo, cerca di aprire il file in scrittura col comando fopen('pippo.txt', 'w'), l'operazione viene completata correttamente anche se il file era gi� aperto in scrittura dal Processo A;
- Se entrambi i processi cercano di scrivere sul file, ad esempio con fwrite($fp, 'blablabla'), le due operazioni vanno a buon fine, ma i dati saranno un misto di quelli scritti da A e da B, facendo s� che il file venga corrotto.
123456
123456
123456
123456
|
+ |
abcdef
abcdef
abcdef
abcdef
abcdef
|
= |
123456
f4902jd922r
abcdef
abcdef
34
123456
|
Dati scritti dal Processo A |
|
Dati scritti dal Processo B |
|
Dati risultanti sul pippo.txt |
Possibili soluzioni
Non esiste una soluzione univoca alla questione; l'accesso condiviso alle risorse � un problema molto importante per i sistemi operativi multithreading, ovvero per tutti i moderni sistemi Windows, Linux e MacOS. In particolare:
- Windows: non esiste un modo semplice per verificare se un file � aperto; ci sono soluzioni basate su strumenti sviluppati da SysInternals di Mark Russinovich. Tuttavia tali soluzioni richiedono l'uso di eseguibili EXE, quasi mai possibile su server remoti.
- Linux: esiste il comando lsof, che permette di sapere se un file � utilizzato da un processo. Tuttavia, anche in questo caso � necessario che lo script PHP possa eseguire dei comandi della shell, ad esempio con shell_exec; tale opzione non � sempre disponibile, per motivi di sicurezza. Inoltre, con questo metodo � possibile sapere se un file � aperto in un dato istante, ma non � possibile mantenerne il controllo per tutta la durata dello script.
Il file lock
I file lock sono una soluzione completa e supportata da tutti i moderni sistemi operativi per risolvere il problema dell'accesso condiviso a un file. Possono essere interpretati come un attibuto temporaneo del file, leggibile da qualsiasi processo (incluso uno script PHP), che permette a tale processo di bloccare il file per scriverci, o di segnalare a altri processi che lo sta utilizzando. In particolare, esistono due tipi di blocco:
- Blocco condiviso (shared lock): il file � bloccato, ma altri processi possono ottenere ugualmente uno shared lock; uno exclusive lock non pu� essere invece ottenuto. Lo shared lock viene utilizzato da un processo che sta leggendo un file: permette a altri processi di leggerlo (visto che nessuna modifica � un corso), ma non di scriverlo.
- Blocco esclusivo (exclusive lock): il file � bloccato dal processo, e nessun altro processo pu� leggerlo, scriverlo o ottenere uno shared lock o un exclusive lock. E' il caso in cui il processo sta scrivendo sul file.
|
Il processo A possiede uno Shared lock sul file |
Il processo A possiede un Exclusive lock sul file |
Il processo B pu� acquisire uno Shared Lock | S� | No |
Il processo B pu� acquisire un Exclusive Lock | No | No |
Il processo B pu� leggere il file | S� | No |
Il processo B pu� scrivere il file | No | No |
Come interpretare i file lock
I lock sono solo un attributo del file, come il nome, la dimensione, la data di modifica, ecc. E' compito del processo che accede al file o del sistema operativo interpretare l'informazione che il lock fornisce riguardo alla condizione del file, e farne buon uso. In base all'uso che se ne pu� fare, esistono due tipi di lock:
- Advisory lock: il sistema operativo informa (consiglia, ovver advise) solamente i processi dei lock, ma non impedisce che tali processi agiscano sul file; � compito dei processi controllare il lock e eseguire o meno delle operazioni di scrittura / lettura. Dunque, affinch� l'uso dei lock permetta di gestire correttamente l'uso condiviso del file, � necessario che tutti i processi ne facciano uso; in altri termini, i processi devono cooperare. Windows implementa l'advisory lock.
Per fare un esempio: se state scaricando un file da internet, potete selezionarlo e copiarlo, nonostante questa operazione non dovrebbe essere permessa, visto che il file � ancora incompleto e in corso di scrittura. Tuttavia tale libert� pu� risultare comoda.
- Mandatory lock: il sistema operativo blocca un file sul quale un processo ha acquisito un lock, e non permette a altri processi di scrivervi (shared lock e exclusive lock) e eventualmente leggerne il contenuto (exclusive lock del file). In altri termini: i vari processi concorrenti non devono preoccuparsi di verificare i lock prima di interagire col file, ovvero non devono cooperare; il sistema operativo si incarica infatti di concedere o negare i permessi di lettura e scrittura del file al processo. Linux implementa il mandatory lock.
Uso del file lock in PHP
In PHP � possibile controllare il lock di un file tramite il comando
flock():
flock($file_pointer, LOCK_SH | LOCK_EX | LOCK_UN)
La funzione ritorna
TRUE nel caso in cui abbia avuto successo, oppure
FALSE nel caso in cui fallisca. Grazie a questo comportamento, � possibile verificare se un file ha gi� un lock con il seguente codice:
if (!flock($fp, LOCK_SH | LOCK_NB))
{
fai qualcosa...
}
Gli attributi sono:
- LOCK_SH: ottiene uno shared lock;
- LOCK_EX: ottiene un exclusive lock;
- LOCK_UN: rilascia (cancella) il lock.
Ulteriori link