Newsgroup: it.comp.lang.delphi
Titolo: TCriticalSection e Sleep()

Non riesco a spiegarmi questo comportamento.
Solita applicazione con TIdTCPServer e numerosi TIdTCPClient che trasmettono 
e ricevono dati con il server.
Il server ha solo 3 TIdCommand
1) Uno manda i dati da elaborare
2) Uno riceve la conferma di fine elaborazione dati dai client
3) Uno riceve eventuali dati positivi
Ho racchiuso tutti e tre i Command nella medesima TCriticalSection che se 
non ho capito male, nel mio caso impedisce che due o più command vengano 
eseguiti contemporaneamente. Questo perchè le funzioni / procedure presenti 
nei tre Command accedono alle stesse tabelle e si potrebbero verificare 
conflitti.
Mi sapete spiegare come mai, se nel TIdCommand 2, che riceve conferma del 
pacchetto elaborato, metto uno Sleep(3000) il db va in tilt?
Mi spiego:
(taglio tutto il codice non necessario ed i controlli per miglior 
leggibilità)
procedure TFrmMain.IdTCPServerRequestIndexCommand(ASender: TIdCommand);
begin
  MyCriticalSection.Acquire;
  { Riceve richiesta pacchetto dal client
     lo consegna al client
     scrive nel db i dati del pacchetto assegnato }
    AppendRecord(Username, UserIP, NewTime, 'NE', '', Data);
    // sleep(3000);
  MyCriticalSection.Release;
end;
procedure TFrmMain.IdTCPServerResolvedIndexCommand(ASender: TIdCommand);
begin
  MyCriticalSection.Acquire;
    if locate('LastData', OldIndici, Opt) then
      DeleteRecords(arCurrent);
  MyCriticalSection.Release;
end;
procedure TFrmMain.IdTCPServerFindedIndexCommand(ASender: TIdCommand);
begin
  MyCriticalSection.Acquire;
    { salva nel db i dati positivi }
  MyCriticalSection.Release;
end;
Se mi collego con due soli client e nella procedura 
IdTCPServerRequestIndexCommand attivo lo sleep(3000), nella tabella degli 
utenti si sommano ad oltranza i due client e non si eliminano nonostante 
nella procedura IdTCPServerResolvedIndexCommand io esegua questa operazione 
ogni qualvolta essi restituiscono la conferma di dati processati.
Ovviamente commentando lo sleep tutto risulta corretto.
Se sono riuscito a spiegarmi qualcuno saprebbe dirmi dov'è l'errore?
Il mio dubbio è che non stia utilizzando correttamente la 
TCriticalSection...se io sospendo l'attività per 3/5 secondi in qualunque 
punto del codice ed in questo periodo di tempo un'altro client tenta 
l'accesso al server non dovrebbe essere messo in coda proprio dalla 
TCriticalìSection?
Grazie
V.

 end;
..alrimenti, se una qls delle operazioni va in errore, la cs non viene 
rilasciata...
Poi, non ho ben capito xche la usi x accodare l'accesso al db...
E cmq mancano info, tipo se gli oggetti di accesso ai dati sono uno x 
ogni evento e nel caso se usano la stessa connessione al db oppure n 
connessioni dedicate.
Se si tratta di connessioni dedicate come è gestita la visibilità tra le 
varie transazioni?
Quale db è?
Con quale tecnologia? sui oggetti tabellari o query?
A.

 E cmq mancano info [..]
Il db è Access, l'accesso con TADOConnection e TADOTable
I tre TIdCommand accedono a sole quattro TADOTable, in alcuni casi come già
detto condividono le stesse.
Non utilizzo query ma i soliti metodi del TDataSource (first, post ecc...).
Sostanzialmente il progetto è nella sua globalità abbastanza semplice e
proprio a due passi dal termine mi sta facendo sudare.
Mi stupisce come mai tu non mi abbia chiesto cosa ci metto a fare uno
sleep() dopo un AppendRecord...
Questa 'scoperta' di utenti che si sommano ad oltranza è spuntata mentre
cercavo di ovviare ad un inconveniente per il quale non sono riuscito a
trovare una soluzione:
nella struttura della mia applicazione c/s che spero ti sia abbastanza
chiara mi capita che, se utilizzo dei TDataSource e relative TDBGrid, pur se
gli accessi al db sono protetti dal cs, se si verificano degli accessi di
client trppo ravvicinati (vedi immediatamente successivi) la grid va in
panne dichiarando che *si è tentato di accedere ad una riga cancellata o in
via di eliminazione* e qualche volta random uno dei campi risulta vuoto
incasinandomi tutto...ho scaricato anche una DBGrid freeware ma nessuna
differenza...così ho sostituito la DBGrid con una TStringGrid e stavo
lavorando a caricare le grid manualmente...la ragione dello Sleep() era per
verificare passo per passo se la griglia riportava correttamente i dati
presenti nella tabella...
è inutile dirti che con la DBGrid ho provato di tutto - per quanto a me
possibile - e scandagliato Google ma la cosa resta un mistero.
Aggiungo che utilizzando la TStringGrid e sparando sul server client a
raffica non ho alcun problema segno che il codice dovrebbe essere
affidabile.
Dopo quanto lungamente esposto se sei ancora qui  :-)  ti chiederei:
preferirei di gran lunga usare la TDBGrid - ti spieghi l'errore?
ritornando all'oggetto del mio post - hai idea del perchè uno sleep alteri
così inaspettatamente il db?
Ciao
V.

Se sono riuscito a spiegarmi qualcuno saprebbe dirmi dov'è l'errore?
Quello che io non capisco, è perché hai bisogno di implementare un
lock manuale per l'accesso al db... Ogni DBMS serio prevede le
transazioni che ti assicurano il corretto livello di isolamento...

Il db è Access, l'accesso con TADOConnection e TADOTable
Usa un DBMS come Firebird e i driver dbExpress distribuiti con i
componenti UIB.

 transazioni che ti assicurano il corretto livello di isolamento...
ok, Access non si distingue di certo...mi sforzo di farlo funzionare lo
stesso, in fin dei conti dipende dal codice che si scrive...

 affidabile.
Non è detto che del codice "funzionnate" sia anche "corretto....
A.

Non è detto che del codice "funzionnate" sia anche "corretto....
ri-condivido
Ciao
V

 stesso, in fin dei conti dipende dal codice che si scrive...
No, dipende dalle funzionalità che ti offre il DB.  Perché implementare a
mano l'isolamento tra accessi al DB quando esistono DB che li offrono
gratuitamente mediante le transazioni ? perché reinventarsi le ruote ?
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it


Sono impreparato sull'argomento ma non so se intendiamo la stessa cosa...
io non devo impedire semplicemente la contemporaneità della modifica di una 
o più tabelle...
ho tre procedure, le TIdCommand - ognuna di esse elabora dei dati, accede al 
db, modifica le tabelle, comunica con il client
dal momento in cui una delle tre procedure viene chiamata in causa nessuna 
delle altre due deve essere eseguita.
Per questo pensavo che, a prescindere dal db, se io racchiudo le tre 
procedure nella stessa TCriticalSection dovrei avere la sicurezza che una 
esclude le altre due...quando ho questa certezza qualunque cosa accada 
all'interno della procedura è corretto...
non so di cosa parliamo con le transazioni, lo ammetto, ma ad istinto 
potrebbe essere più laborioso intervenire su chi e quando può modificare il 
db...
Non la vedi più semplice dire: se la procedura 1 è attiva le altre due 
devono aspettare, la seconda che arriva passa e la terza e la prima 
aspettano...e così via?

 aspettano...e così via?
No.  Non la vedo più semplice, perché non ho ben capito qual è il problema
di base.  Le corse critiche, in un contesto multi-thread, puzzano sempre,
a priori.  Perché vuoi rendere esclusive le elaborazioni ?


 a priori.  Perché vuoi rendere esclusive le elaborazioni ?
Questa è la struttura, non vedo altro modo:
il client si connette e invia una stringa command al server
1) una per richiedere il pacchetto da elaborare
2) una per restituire conferma di fine elaborazione
3) una per restituire i risultati positivi - questi vengono restituiti 
immediatamente, non aspetto la fine dell'elaborazione dell'intero pacchetto 
in quanto molto rari e se l'utente spegne il pc perdo l'elaborazione (anche 
se il pacchetto verrà poi riconsegnato ad un altro client alla sua 
scadenza). In ogni pacchetto potrebbero esserci uno o più risultati positivi 
o nessuno (più probabile) -
Al punto 1 il server procede in questo modo:
scansiona tutta la tabella per verificare se qualche pacchetto risulta 
scaduto - cioè se il client che lo possiede ha sforato il tempo a sua 
disposizione ed in caso positivo lo/li marca come scaduto
Se risultano pacchetti scaduti assegna il primo della lista al client che si 
è appena connesso ed elimina dal db il client scaduto, altrimenti crea ed 
assegna il nuovo
Marca nella tabella i dati del client, l'ora ed il relativo pacchetto 
assegnato
Al punto 2 il server, che riceve dal client la conferma di fine 
elaborazione, accede alla tabella del punto 1 e verifica che il pacchetto 
elaborato sia presente tra i dati. Se c'è elimina il record relativo perchè 
completato, se non c'è significa che il pacchetto è gia stato elaborato da 
qualche altro client (in conseguenza del suo ritardo era stato marcato come 
scaduto e quindi riconsegnato ad altro client - tieni presente che do 
l'oppurtunità all'utente di sospendere l'elaborazione e di riprenderla a suo 
piacere quindi non sempre un pacchetto risultato scaduto non torna più causa 
spegnimento del pc)
Già in questo contesto non vedo molto bene una sovraposizione di richieste e 
cancellazioni di pacchetti...
Il punto 3 si occupa di salvare i risultati positivi in un'altra tabella ma 
nel contempo deve rilevare nella tabella del punto 1 e 2 gli utenti connessi 
nello specifico momento nel quale ricevo un risultato positivo. Questo 
perchè tutti gli utenti presenti nel db riceveranno in seguito il risultato 
positivo (una sorta di premiazione alla partecipazione alla 
ricerca...insomma chi in quel dato momento c'è beneficerà del risultato 
trovato). Salvo gli utenti ed il risultato positivo in un'altra tabella ed 
al loro primo seguente accesso riceveranno i dati in questione. Man mano che 
li distribuisco li elimino dalla tabella e questa eventuale distribuzione la 
attuo al punto 1, prima di consegnare il pacchetto da elaborare. Cosa 
potrebbe succedere se mentre leggo o cancello in questa tabella dal punto 1 
si sovrappone il punto 3 o viceversa?
Che fatica, e non so nemmeno se sono stato chiaro!
Ma nella apparente confusione dovrebbe esserti evidente che ognuna delle tre 
procedure deve per forza completarsi altrimenti otterrei risultati sfalsati.
Riferiscimi eventuali obiezioni...
V.

nello specifico momento nel quale ricevo un risultato positivo.
Non devi spostare, non devi cancellare, devi lavorare su quello che
hai.  E' qui il problema: sposti e cancelli i dati.  Lavorando sui
dati che hai, invece, le transazioni ti bastano e avanzano.

 dati che hai, invece, le transazioni ti bastano e avanzano.
è che questo non lo vedo possibile...a me non serve mantenere traccia dei
pacchetti elaborati perchè per capire a che punto sono mi basta leggere il
valore dell'ultimo pacchetto consegnato dal server...ma sopratutto non posso
lasciare quelli elaborati nel db perchè sono milioni...è davvero necessario
cancellarli dalla tabella o al limite sovrascrivere il record con nuovi
dati...in questo modo nel db ci saranno solo tanti record quanti sono i
client che li stanno elaborando + eventuali pacchetti scaduti
In quest'ottica cosa ne pensi?

 sugli stati, *NON* cancellare record.
Aspetta, forse ho capito...
posso non eliminare il record e cambiarne lo stato a EL (elaborato)
quindi ad ogni richiesta di un client verificare se sono presenti record in
EL...se si lo sovrascrivo, altrimenti ne inserisco uno nuovo
in sostanza avrò solo qualche record in + in stato EL ma pronto ad essere
sostituito
E' (quasi) corretto?
V

 In quest'ottica cosa ne pensi?
Io penso una cosa molto semplice: a prescindere da qualsiasi sia l'ambito
del tuo problema, l'isolamento *manuale* delle operazioni da parte
dell'applicazione è sospetto, quando esistono sistemi, come appunto le
transazioni, lato DB, studiati per garantire atomicità, consistenza,
isolamento e durabilità.


 isolamento e durabilità.
Temo non si possa però prescindere dal tipo di problema.
E' necessario, per il tipo di lavoro che il server deve svolgere nel db in 
ognuna delle tre procedure, l'isolamento di una procedura dalle altre due. 
Capisco che in un contesto multi-thread suona un pò atipico, ma se una 
qualunque delle tre procedure, per lavorare correttamente, deve accedere ad 
un db che deve presentare i suoi dati - in più tabelle - completamente 
elaborati da una qualsiasi delle procedure stesse come posso fare 
altrimenti?
Approfondisco comunque il discorso sulle transazioni perchè non conoscendole 
non posso sapere se sono in grado di sostituire l'isolamento predetto.
Un'idea potrebbe essere quella di Alberto che suggerisce di salvare su 
tabella le richieste dei client e processarle cronologicamente - quindi 
permettere un accesso contemporaneo al server - ma comunque gli accessi al 
db dovranno sempre avere una caratteristica di unicità...
non so, ora guardo un pò
Grazie
Valter

 isolamento e durabilità.
Temo non si possa però prescindere dal tipo di problema.


 altrimenti?
Già detto.  Sfruttando le transazioni.
