Cache del Master
La cache consente al master di tenere traccia di tutte le richieste inoltrate da Client/Slave ancora da
soddisfare e di quelle gia' soddisfatte con i relativi files contenenti i risultati di tali ricerche.
Mediante i metodi di accesso forniti dalla cache il master e' in grado di capire se la richiesta arrivata
e' gia' stata soddisfatta, nel qual caso si ritornano i risultati raccolti, oppure se e' stata inoltrata piu'
volte da parte dello stesso Client/Slave o se e' stata inoltrata da qualcun altro.
Lo scopo di tali controlli e' quello di evitare di inoltrare agli slaves locali richieste gia' effettuate da altri
o effettuate piu' volte dallo stesso mittente e di restituire dei risultati qualora la richiesta sia gia' stata
soddisfatta.
Dettagli implementativi
La cache e' realizzata con una Hashtable le cui entry hanno la seguente struttura:
La chiave e' costituita da:
- Mittente: e' l'indirizzo e-mail di un client o e' uno slave.
- Richiesta e ambito: costituiscono una richiesta.
- TTL: indica la profondita' della ricerca da effettuare.
La parte dati, invece, e' costituita da:
- File risultati: e' il nome del file che contiene i risultati della ricerca.
- Expire time: e' il tempo di vita massimo della entry e del file risultati.
- Index e vivi: sono flag di servizio necessari per la rielezione di un master (si veda l'ultimo
punto del contattamento degli slave-Note).
Metodi di accesso
Mediante i seguenti metodi (tutti synchronized) e' possibile accedere e modificare i dati in cache:
- get(ric) : consente di vedere se la richiesta 'ric' e' gia' presente (in questo
caso il metodo ritorna la stringa "già fatta dallo stesso client" e, quindi, non deve essere considerata)
oppure se e' gia' stata fatta da un altro (la stringa di ritorno e' "fatta da un altro") con un TTL maggiore
o uguale a quello della richiesta corrente (anche in questo caso la richiesta non deve essere inoltrata
in quanto ce n'e' gia' un'altra piu' "profonda" che deve essere eseguita).
Nel caso in cui la richiesta non sia presente ritorna "entry non presente".
- getAll: ritorna tutte le entry della cache, raggruppando i dati in un array di oggetti CacheElem.
- put(ric,ind) : inserisce una richiesta 'ric' nella cache e inizializza il flag index corrispondente
con 'ind'.
- set(ric,fileRis): inserisce il nome del file 'fileRis' che contiene i risultati
di una ricerca 'ric' e setta l'expire time dell'entry corrispondente. Per fare questo viene preso il tempo
corrente in millisecondi e si aggiunge ad esso il tempo massimo per il mantenimento dei risultati di
una richiesta (contenuto nella costante Constants.dataExpireTime).
- setVivi(ric,v): inizializza il flag vivi della richiesta 'ric' con il valore 'v'.
- remove(ric): rimuove la richiesta 'ric' dalla cache.
- checkData(ric): controlla se il campo relativo al file risultati della richiesta 'ric' e' stato settato o meno.
Nel caso in cui il campo sia vuoto, il che significa che la richiesta non e' ancora stata soddisfatta, sospende
il chiamante fino a che non arrivano i risultati (metodo set). Questo e' reso possibile mediante i metodi wait e
notify/notifyAll degli oggetti Java.
Entita' attive della cache
All'interno della cache operano due thread:
- ExpireThread: questo thread ha il compito di rimuovere tutte le entry della cache "scadute", cioe' quelle il cui
Expire Time e' stato superato. Per fare questo il processo si risveglia periodicamente (il tempo di sleep e' contenuto
nella costante Constants.risveglioThreadCache), preleva il tempo
corrente in millisecondi e controlla tutte le entry il cui Expire Time e' diverso da -1. Se il tempo corrente e' maggiore
o uguale a quello della entry esaminata, questa viene eliminata dalla cache assieme al file che contiene i risultati
della richiesta corrispondente (ricordare che l'Expire Time viene settato quando viene inserito il nome del file dei risultati
mediante il metodo set(ric,fileRis)).
In questo modo si evita che i risultati delle ricerche vengano mantenuti troppo a lungo, occupando risorse del nodo
master.
- UpdateSlaveThread: questo thread ha il compito di diffondere agli slave del nodo locale
lo stato del master in modo che possa essere ripristinato in seguito a una rielezione dovuta a un guasto del master stesso.
Per stato del master si intendono solo le richieste pendenti. E' stata fatta questa scelta perche' la diffusione delle richieste
soddisfatte e dei relativi risultati impegnerebbe troppe risorse generando un traffico di rete troppo elevato. Riducendo le informazioni
da diffondere si e' potuto optare per un protocollo senza connessione, cioe' UDP.
Questo thread viene attivato ogni volta che viene chiamato uno dei metodi di accesso alla cache scritti in rosso e riportati poco sopra.
Questi metodi inseriscono un messaggio del tipo "set/unset richiesta+flag" in una coda realizzata dalla classe interna
MsgQueue. Questa ingloba un vector (la coda) e fornisce due metodi di accesso (synchronized) per inserire
(put) e prelevare (get) i messaggi della coda. UpdateSlaveThread una volta avviato chiama il metodo get della coda: se essa e' vuota,
viene sospeso (metodo wait) fino a che non vengono inseriti nuovi messaggi con put (che risveglia il processo sospeso con notify).
Una volta prelevato un messaggio (il primo della coda, cioe' il primo ad essere depositato) UpdateSlaveThread lo invia a tutti gli slave del
nodo locale. Per aumentare l'affidabilita' della trasmissione, dopo l'invio del messaggio lo thread attende un ack (vuoto) da ogni slave come
conferma della ricezione di cio' che e' stato inviato. Se uno slave non risponde, il messaggio viene inviato nuovamente a quello slave
per un massimo di 3 volte, dopodiche' lo slave viene ritenuto "morto" (per il mantenimento della lista degli slave "vivi" e di quelli "morti" si veda
la classe SlaveVivi). Per evitare attese infinite, viene settato un timeout per la socket usata pari al valore contenuto
in Constants.attesaRec e scaduto il quale si effettua una nuova ritrasmissione agli slave
che non hanno risposto.