Newsgroup: it.comp.www.php
Titolo: Dubbio su mutua esclusione in PHP

Ciao a tutti!
Mi hanno proposto di realizzare un sistema che prevede la prenotazione di 
alcune stanze d'albergo.
Mi � venuto in mente un dubbio banalissimo.
Come fate voi a evitare che un utente prenoti una stanza che nel mentre � 
gi� stata prenotata da un altro utente?
Cio�, ipotizziamo che il codice ultra-semplificato sia questo:
if (stanzaLibera()) {
    impostaStanzaOccupata();
    riceviPagamento();
}
Come fate per evitare che due client in esecuzione contemporaneamente 
eseguano il codice e quindi trovino entrambi la stanza libera e provino a 
occuparla? Esiste una istruzione eseguita "atomicamente" del tipo 
"test-and-set"?
I miei ricordi di programmazione concorrente mi dicono che in Assembler 
c'era e serviva proprio a questo.
O senn� servirebbe un semafoto mutex o qualcosa di simile ma in PHP 
esistono?
-- 
Elminster
-- 
Elminster 

 � gi� stata prenotata da un altro utente?
Io di solito mi affido al DBMS per evitare di trovarmi in situazioni
logicamente incongruenti.
Y.

 esistono?
è una cosa che ti conviene a livello di database, guarda ad esempio la 
direttiva lock table di mysql

RedWiz ha scritto:

Ma con la lock table non si rischia di bloccare la prenotazione di altre stanze eventualmente libere?
Non potrebbe essere funzionale un
"UPDATE tabella SET prenotazione = true WHERE stanza = $stanza"
quando un potenziale cliente inizia la procedura di prenotazione (e lo sbloccaggio nel caso
non concluda la procedura con il pagamento) e verificare se 'prenotazione' è settato per visualizzare
le stanze libere?

"Elminster" <elminst...@QUESTOtiscali.it> ha scritto nel messaggio 
news:1u%wm.2124$Rt2....@tornado.fastwebnet.it...

Quando una stanza � prenotata viene aggiunto un record ad una tabella.
Questo record deve contenere dei dati che identificano quella stanza 
rispettoa tutte le altre.
Prima di presentare il form per la prenotazione controlla se quella stanza � 
libera.
Ciao
Leo

 Elminster
http://martinfowler.com/eaaCatalog/pessimisticOfflineLock.html
http://martinfowler.com/eaaCatalog/optimisticOfflineLock.html
Ciao, Leo

 stanza � libera.
Allora, ipotizziamo che le stanza sia libera: a questo punto due utenti 
aprono la pagian web della stanza.
La stanza � libera quindi giustamente entrambi vedono che � libera.
A questo punto entrambi contemporaneamente premono il tasto prenota. A 
questo punto non si pu� fare un ulteriore controllo strutturato in questo 
modo:
if (stanzaAncoraLibera)
    impostaStanzaOccupata();
perch� se la funzione impostaStanzaOccupata fosse un UPDATE stanze SET 
occupata=1 WHERE idstanza = 384; entrambi gli utenti eseguirebbero la query 
e a entrambi sembrerebbe di averla prenotata a nome loso.
Invece ci vorrebbe un'istruzione che atomicamente (cio� senza possibilit� di 
essere interrotta) verifica se � libera e solo in quel caso la prenota.
Forse potrebbe andar bene cos�?
UPDATE stanze SET occupata = 1 WHERE (idstanza = 384 AND occupata = 0);
In questo modo la query verrebbe eseguita correttamente solo da un utente 
mentre l'altro avrebbe 0 affected_rows.
-- 
Elminster 

 'prenotazione' è settato per visualizzare le stanze libere?
La fai un po' troppo semplice: la procedura di prenotazione potrebbe non 
essere una procedura lunga ma bensì una funzione single-step.
Per esempio, mettiamo il caso di due utenti già loggati che guardano il sito 
di un albergo e visitano le pagine di varie stanze, in fondo a ognuan delle 
quali è presente un tasto "prenota".
A un certo punto si trovano tutti nella stessa pagina e entrambi cliccano 
"prenota": dovrebbero averla prenotata entrambi? In questo caso non esiste 
una procedura di prenotazione iniziata nè può esserci un successivo 
sbloccaggio. Ma comunque il problema si ripresenta sempre nel caso in cui i 
due utenti iniziassero la procedura di prenotazione contemporaneamente: fino 
a che momento dovrebbero entrambi vedere la stanza libera? C'è un bug 
evidente, ti rendi conto?
-- 
Elminster 

 query e a entrambi sembrerebbe di averla prenotata a nome loso.
Classico problema di concorrenza.
lock table stanze;
select * from stanze where idstanza=384;
if stanza not occupata {
  update stanze set occupata=true where idstanza=384;
}
unlock table stanze;
Blocchi la tabella per qualche decimo di secondo, ma non penso che tu 
prenoti qualche milione di stanze nello stesso albergo al giorno, quindi 
tutto il resto dovrebbe rimanere abbastanza responsivo.
Bye.

 unlock table stanze;
In mysql esiste anche la sintassi SELECT ... FOR UPDATE che credo faccia
"gratis" il lock della tabella, dentro la stessa transazione.
Mi ricordo di aver usato questo costrutto per un sito con aggiornamenti
on-line molto molto frequenti e devo dire la verità... .... avevo avuto
qualche problema a riguardo :-(
saluti,
M.

 "gratis" il lock della tabella, dentro la stessa transazione.
Non l'ho mai usata, a dire il vero. :)
Leggo dal manuale di MySQL che funziona solo con le tabelle InnoDB e solo 
all'interno di una transazione (o disattivando l'autocommit a livello 
globale). E, a dire il vero, me l'aspettavo, visto che fa un lock a 
livello di riga, cosa che MyISAM non supporta.
Bye.

 
c'e' da considerare la cosa princiapale
l'appliazione _crepa_ sempre
bello stanza libera
faccio la prenotazione
	i $$ sulla CC non li ho
	.....
	.....
un altro ha gia finito
la prenotazione deve aver effetto solo con il pagamento o l'acconto

 }
Non mi sembra che questa soluzione risolva il problema.
Presumo che impostaStanzaOccupata sia una funziona che imposta a occupata la 
stanza ma solo se � libera, giusto?
Altrimenti non capisco perch� scrivi !impostaStanzaOccupata, come se la 
funzione dovesse restituire true o false.
In quel caso per� stai dando ragione a me: dai per scontato l'esistenza di 
un'istruzione atomica test-and-set (come in Assembler). Se infatti la 
funzione impostaStanzaOccupata non fosse atomica ma fosse una funzione di 
questo tipo:
if stanzalibera() { occupata = true; return true; }
else return false;
in quel caso ci sarebbe ancora il problema dei due utenti che entrano 
nell'IF allo stesso momento trovandolo valido e poi effettuano entrambi il 
codice contenuto nel corpo dell'IF.
Cio�:
ISTANTE 1
La stanza � ancora libera. L'utente A entra nell'IF.
ISTANTE 2
Anche l'utente B entra nell'IF.
ISTANTE 3
Entrambi settano occupata a true.
ISTANTE 4
Entrambi ottengono true come valore di ritorno.
-- 
Elminster 

 livello di riga, cosa che MyISAM non supporta.
Quindi il lock a livello di riga è logicamente più efficace, perché di
riga ce n'è una sola (c'è una sola stanza) e non si fa il lock su tutta
la tabella.
L'update andrebbe fatta possibilmente dentro uno statement atomico,
preferibilmente una stored. Io avevo avuto problemi per il semplice
fatto che il mio provider, ho scoperto, mi fornisce MySQL /senza/ le
stored :-( e quindi non avevo più approfondito la questione.
Sarei in ogni caso curioso di vedere esempi qui di qualcuno che abbia
provato qualcosa di simile con le multiple query.
saluti,
M.

 Entrambi settano occupata a true.
A livello di db, questo non � possibile. Le istruzioni vengono in ogni
caso seguite sequenzialmente e facendo il lock della tabella (o della
riga, come si diceva in un altro subthread) uno arriva prima dell'altro.
Io per esempio, ottenuto l'ID della riga da aggiornare, ipotizzo
un'operazione del genere
UPDATE SET flag=1 WHERE flag = 0 AND ID = id
Uno dei due utenti fa l'update per primo (e fa il lock per primo).
L'altro, dopo che la tabella torna unlocked, tenter� di fare l'update ma
essendo il flag a 1 l'update non aggiorner� una fava ed � possibile
controllarlo con msqli_affected_row.
saluti,
M.

 caso seguite sequenzialmente
E il concetto di transazione, dove lo mettiamo?

 E il concetto di transazione, dove lo mettiamo?
Non ho capito bnne la domanda ... :-/
Alla resa dei conti, ad esempio, fai due INSERT nel db con la stessa
chiave primaria. Per quanto ti sforzi, una vince l'altra perde.
La transazione ti permette di lavorare con uno snapshot consistente dei
dati e gestire "atomicamente" pi� operazioni in sequenza, con la
possibilit� di fare commit e/o rollback alla fine della transazione.
Sbaglio qualcosa ? :-|
M.

 Sbaglio qualcosa ? :-|
No, � perfetto. Allora perch� in un altro post scrivi che "L'update
andrebbe fatta possibilmente dentro uno statement atomico,
preferibilmente una stored." Puoi usare anche pi� statement, basta che
siano compresi in un'unica transazione. O no?

 siano compresi in un'unica transazione. O no?
S�.
La stored � una funzionalit� aggiuntiva che semplifica il lavoro e rende
 pi� agevole il debug e la manutenzione del codice.
Lato PHP � sufficiente fare una banale prepared statement che chiama la
stored; massima pulizia e separazione fra la parte di codice PHP e database.
Ma se questa funzionalit� non � applicabile (come nel mio caso, a causa
del mio isp) ben venga l'approccio classico.
saluti,
M.

 del mio isp) ben venga l'approccio classico.
Non posso che sottoscrivere. Allora avevo frainteso quello che scrivevi.

 tutto il resto dovrebbe rimanere abbastanza responsivo.
Non prenotando qualche milione di stanze al giorno per me
non vale nemmeno la pena perdere la testa su come gestire
le prenotazioni atomicamente.
Sarebbe una bella sfiga prenotare, nello stesso millisecondo
la stessa stanza dello stesso albergo per lo stesso giorno.
Inoltre, basterebbe verificare il numero di affected rows.
Nel caso ci fossero due utenti contemporanei, per forza di cose
uno dei due avrà affected rows pari a 0, mentre l'altro lo riceverà
pari ad 1.
Chi lo riceve pari ad 1 ha prenotato.
-- 
Non tocca a noi dominare tutte le maree del mondo,
il nostro compito è di fare il possibile per la
salvezza degli anni nei quali viviamo,
sradicando il male dai campi che conosciamo.