• Non ci sono risultati.

Capitolo 1

N/A
N/A
Protected

Academic year: 2021

Condividi "Capitolo 1"

Copied!
81
0
0

Testo completo

(1)

Capitolo 1

1.1) ORC

Molte applicazioni distribuite eseguono un pattern che consiste nell'acquisire dati da un servizio remoto, eseguire dei calcoli su questi dati e invocare un altro servizio remoto con i risultati ottenuti. Inoltre è spesso richiesto di invocare dei servizi alternativi per la stessa computazione.

In ORC il temine generico per definire un sevizio è sito (site); un sito può eseguire qualunque tipo di computazione: acquisizione e invio dati, elaborazione, chiamate di altri servizi, ecc...

L'integrazione tra i vari siti viene chiamata orchestrazione ed il linguaggio ORC modella l'orchestrazione tra siti; lavorare con le orchestrazioni implica l'utilizzo dei concetti dominanti che riguardano la programmazione di rete: il ritardo associato alle comunicazioni, l'indisponibilità dei server e la condivisione di risorse da parte di più client.

Pur non essendo un linguaggio molto ricco di costrutti, ORC permette sia di descrivere moltissimi modelli di applicazioni distribuite (dai servizi di news all'invio di mail) che di fare reverse engineering di sistemi distribuiti esistenti [2]. Questa potenza descrittiva, unita alla capacità di isolare la struttura dell'applicazione dalla sua effettiva implementazione, è stata il motivo principale della scelta di ORC come framework per la generazione di skeleton per applicazioni distribuite.

(2)

La sintassi del linguaggio ORC è definita formalmente in [1] come segue:

Dalla sintassi è possibile notare come il modello proposto è abbastanza minimale ma questa apparente limitazione permette sia lo studio di orchestrazioni in modo isolato che la possibiltà di combinare siti di complessità arbitraria in computazioni, senza fare assunzioni sulla loro implementazione.

Di seguito verranno illustrati i principali operatori ORC, mostrando per ognuno semplici esempi di utilizzo.

E  Nome Espressione

x,z  Variabili M  Sito c  Costante

::=z: E(P) Valuta E(P) e lo assegna a z

::=E(Q) Δ f Definizione dell'espressione E M(P) Chiamata di Sito

E(P) Chiamata di Espressione

f|g Composizione parallela simetrica f>x>g Composizione sequenziale

(3)

1.2) Sito

Un sito è la più semplice espressione ORC, può prendere o meno dei parametri. La valutazione di un'espressione di questo tipo produce una chiamata di sito (cioè l'esecuzione del sito come se fosse una procedura).

A seguito di una chiamata, un sito produce uno o più valori (chiamato

pubblicazione); se un sito/espressione non produce alcun valore si dice che è silente.

Un sito si differenzia per due motivi da una funzione: il primo è che la valutazione di un sito può avere effetti laterali (se per esempio la chiamata del sito provoca un update su di un DB), il secondo è che può in momenti diversi pubblicare valori diversi (se per esempio la chiamata di un sito provoca una query si di un DB).

Un esempio di sito è l'espressione Email(a,m), che spedisce il messaggio m all'indirizzo a. La valutazione dell'espressione causa il cambiamento di stato della mailbox e la pubblicazione di un segnale che denota il completamento dell'operazione.

Oppure l'espressione Ticket(p,d,g) che permette di comprare un biglietto aereo con luogo di partenza p, destinazione d nel giorno g. La valutazione dell'espressione potrebbe produrre valori diversi a seconda che il biglietto aereo sia disponibile o meno.

(4)

1.3) L'operatore “>>”

L'operatore “>>” ( nella sua forma più generale “>x>” ) permette l'esecuzione sequenziale di chiamate di siti.

Nel caso più semplice l'espressione A>>B (on A e B espressioni ORC) viene valutata nel seguente modo: viene effettuata la chiamata dell'espressione A. Quando A termina viene effettuata la chiamata dell'espressione B che pubblicherà un valore; quest'ultimo valore è la pubblicazione dell'espressione corrente.

Se la chiamata di B è silente allora l'espressione corrente è silente.

Se la chiamata di A è silente allora l'espressione corrente è silente (visto che B non verrà mai chiamato).

ORC prevede che un'espressione possa effettuare chiamate di siti in parallelo quindi l'espressione A potrebbe pubblicare più di un valore. In questo caso dopo la pubblicazione di ogni valore verrà effettuata la chiamata di B; ogni pubblicazione di B sarà pubblicazione dell'espressione corrente.

L'operatore “>>” è associativo quindi le espressioni (A>>B)>>C e A>>(B>>C) sono equivalenti.

Visto che le espressioni possono avere dei parametri l'operatore “>>” può anche essere usato per descrivere la pubblicazione e l'utilizzo di un valore; se per esempio B prendesse come parametro la pubblicazione di A allora avremmo potuto scrivere l'espressione precedente nel modo seguente : A>x>B(x); indicando con x la pubblicazione di A.

(5)

Per esempio potrebbe essere di memorizzare su un data base il risultato di una query; in ORC potremmo scrivere:

Query(q,d)>x>Update(x,d2)

In un primo momento verrà effettuata una query q sul data base d; questa query produrra un result set x (la pubblicazione della chiamata di sito Query()) che successivamente verrà memorizzato nel data base d2 dalla chiamata del sito

Update().

In un altro esempio potremmo voler spedire in successione due mail a due persone diverse ma con lo stesso testo; la situazione modellata in ORC sarebbe la seguente:

Mail(a,m)>>Mail(a2,m)

In questo caso dopo che sarà stata inviata la mail all'indirizzo a verrà pubblicato un valore (che non sarà utilizzato) e successivamente verrà spedita una mail all'indirizzo a2.

(6)

1.4) L'operatore “|”

Questo operatore permette la composizione parallela simmetrica.

Nel caso più semplice l'espressione A|B (on A e B espressioni ORC) viene valutata nel seguente modo: viene effettuata la chiamata dell'espressione A che pubblica uno o più valori e contemporaneamente viene effettuata la chiamata dell'espressione B che pubblicherà uno o più valori; ogni pubblicazione di una delle due espressioni è considerata pubblicazione dell'espressione corrente.

Se A e B fossero due chiamate di sito allora l'espressione potrebbe produrre zero, una o due pubblicazioni visto che A e/o B potrebbero essere anche silenti.

In generale (se A e B fossero espressioni ORC) l'espressione corrente potrebbe produrre un numero arbitrario di pubblicazioni.

L'operatore “|” è sie associativo, infatti (A|B)|C che A|(B|C) sono equivalenti, che commutativo, infatti A|B equivale a B|A.

E' importante notare che A|A è differente dall'espressione A perchè la prima esegue due chiamate parallele del sito A mentre la seconda una sola; inoltre anche A>>(B|

C) è differente da A>B|A>C (con C espressione ORC) perchè nella prima

l'espressione A viene chiamata una volta sola nella seconda invece due volte contemporaneamente.

Se per esempio volessimo spedire una mail con delle news si potrebbe scrivere in ORC :

(7)

contemporaneamente; il primo sito a rispondere produce la pubblicazione m che successivamente verrà spedita all'indirizzo a. Quando avrà risposto anche il secondo sito verrà pubblicato un nuovo valore che sarà assegnato ad a e successivamente inviato all'indirizzo a.

E' importante notare che il secondo sito potrebbe anche non rispondere (per esempio per problemi al server), in questo caso verrebbe inviata una sola mail; nel caso peggiore potrebbe succedere che nessuno dei due siti di news sia funzionante, in questo caso non verrebbe spedita nessuna mail all'indirizzo a; inoltre non può essere fatta alcuna assunzione sull'ordine di invio delle pubblicazioni dei due siti: il sito BBC potrebbe pubblicare per primo il valore (e viceversa).

La potenza descrittiva di ORC risiede proprio nel fatto che tutte queste possibili situazioni possano essere modellate in una singola espressione senza che ci sia il bisogno di fare assunzioni sull'implementazione dei servizi stessi.

(8)

1.5) L'operatore“where”

Questo operatore permette la composizione parallela asimmetrica.

Nel caso più semplice l'espressione A where xB (on A e B espressioni ORC) viene valutata nel seguente modo: viene effettuata la chiamata dell'espressione A che pubblica un valore e contemporaneamente viene effettuata la chiamata dell'espressione B che pubblicherà un valore; dopo che B avrà pubblicato il suo primo valore questo verrà assegnato ad x e la computazione di B verrà terminata immediatamente. Durante la valutazione in parallelo di A rispetto a B ogni chiamata di sito che non utilizza x come parametro proseguirà mentre quelle che lo utilizzano aspetteranno che B renda disponibile il valore x.

Tutte le pubblicazioni di A saranno le pubblicazioni dell'espressione corrente. Per esempio se volessimo spedire via mail una pagina di news potremmo scrivere:

Mail(x,a) where x (BBC|CNN)

In questo caso verrebbero contattati contemporaneamente sia i siti della BBC che quello della CNN; il primo sito a rispondere pubblicherà un valore che sarà assegnato ad x e verrà inviata una mail all'indirizzo a.

E' importante notare che dopo la pubblicazione del primo valore da parte dell'espressione (BBC|CNN) le successive pubblicazioni saranno ignorate; quindi l'espressione corrente potrà al massimo inviare una mail (al minimo nessuna nel

(9)

1.6) Definizione di espressione

Per strutturare le orchestrazioni è necessario permettere di definire le espressioni; un'espressione è definita come una procedura, con un nome e possibilmente dei parametri.

Per esempio se volessimo dare un nome all'espressione utilizzata nell'esempio precedente potremmo scrivere:

MailOnce(a) Δ Mail(x,a) where x (BBC|CNN)

Un'espressione così definita sarà utilizzabile come se fosse una chiamata di sito:

MailOnce(a)>>MailOnce(b)

Grazie alla possibilità di poter definire espressioni e possibile utilizzare meccanismi importanti come la ricorsione.

Per esempio se volessimo mandare una mail infinite volte allo stesso indirizzo potremmo definire l'espressione seguente:

MailForever(a) Δ MailOnce(a)>>MailForever(a)

Questo meccanismo è molto importante perché nelle applicazioni distribuite molti processi eseguono più volte le stesse operazioni e quindi la ricorsione sarà una proprietà fortemente sfruttata nella creazione di modelli.

(10)

1.7) Siti speciali

Anche se i siti sono totalmente definibili dall'utente esistono alcuni tipi di sito che, a causa del loro uso frequente, vengono dati come predefiniti; in particolare verranno utilizzati Rtimer(t) che pubblica un segnale dopo t millisecondi (usato per i time-out) e if(b) che esegue un test sul parametro b: in caso sia true pubblica un segnale altrimenti rimane silente.

Per esempio se vogliamo che una mail venga spedita infinite volte ad intervalli regolari possiamo scrivere:

MailForever(a) Δ Rtimer(t)>>MailOnce(a)>>MailForever(a)

E' importante notare che gli intervalli di tempo tra l'invio di una mail ed un altro non sono tutti uguali: infatti, oltre al tempo di attesa t va aggiunto il ritardo dovuto alla comunicazione con in server delle news che può variare per diversi motivi (banda a disposizione, numero di connessioni al server, ecc...).

Un altro esempio potrebbe essere quello di spedire una news solo se il suo contenuto è maggiore di cento righe:

CNN>x>ContaRighe(x)>z>if(z>100)>>Mail(x,a)

Nel caso in cui le righe della mail siano minori di cento allora la computazione si blocca.

Verranno utilizzati inoltre dei canali condivisi dalle espressioni sui quali sarà possibile effettuare operazioni di lettura e scrittura di valori. In particolare sarà

(11)

possibile, dato un canale c, prelevare un valore con c.get() ( se il canale è vuoto si mette in attesa che arrivi almeno un elemento), spedire un valore con c.put() (non si fanno assunzioni sulla capacità del canale).

I canali vengono utilizzati soprattutto per la definizione di processi; un processo è un'espressione che tipicamente lavora su canali che sono condivisi con altri processi.

Per esempio se volessimo definire un processo che preleva un valore da un canale

c, effettua su questo dato dei calcoli (attraverso la chiamata del sito Compute()) e

lo spedisce su di un altro canale e potremmo scrivere:

Proc(c,e) Δ c.get()>x>Compute(x)>z>e.put(z)>>Proc(c,e)

Inoltre è spesso utilizzata la notazione (i|:1<i<n:wi) come abbreviazione per

(12)

1.8) Strumenti

Il linguaggio di programmazione utilizzato per realizzare il prototipo di ambiente per la generazione degli skeleton è Java (JDK6) [3].

I motivi che hanno portato alla scelta di questo linguaggio sono molteplici: l'indipendenza dalla piattaforma utilizzata (il codice generato infatti può essere eseguito sia sotto ambiente Linux che Windows), è un linguaggio ormai standardizzato e molto diffuso nell'ambiente della programmazione (rendendo quindi più semplice per gli utenti scrivere le parti di codice mancanti negli skeleton), la presenza della libreria java.net che offre un supporto semplice e completo per quanto riguarda la creazione e la comunicazione tra socket TCP/IP , [4] una facile gestione dei threads [5] e delle primitive di sincronizzazione.

Le conoscenze necessarie allo sviluppo del tool sono state principalmente quelle inerenti la programmazione di rete (creazione di socket TCP/IP per la comunicazione tra processi e l'utilizzo di stream di comunicazione) e programmazione concorrente (per quanto riguarda l'utilizzo di threads, la sincronizzazione tra processi e l'accesso a risorse condivise).

(13)

Capitolo 2:

2.1) Il progetto logico

Il prototipo consiste in una libreria che implementa le principali funzionalità del linguaggio ORC.

Il lavoro consiste in una ibreria chiamata jORC che (unitamente ad una cartella contenete dei template per la generazione di codice) mette a disposizione tutti gli strumenti che permettono di generare il codice che implementa in modo semi automatico un programma ORC su un insieme di macchine con Java Virtual Machines distribuite.

Partendo da un programma ORC scritto con la sintassi della libreria sviluppata viene creata una cartella contenente delle classi Java che rappresentano gli skeleton dei processi che formano l'espressione e alcune classi che forniscono il suppo rto a tempo di esecuzione.

La sintassi utilizzata per scrivere le espressioni è molto semplice in modo che in futuro sia possible creare un parser che legga direttamente file con la sintassi originale di ORC e produca gli skeleton; per esempio se vogliamo che due siti

(14)

Successivamente verranno descritti in modo più dettagliato la sintassi e gli operatori della libreria anche attraverso l'uso di semplici esempi di orchestrazioni. Per ogni sito presente nell'espressione ORC viene generato un processo wrapper; questo processo si occuperà di eseguire la parte di codice scritta dall'utente relativa alla chiamata del sito; il processo wrapper riceve una pubblicazione, esegue la chiamata di sito e pubblica un valore.

Il codice del wrapper è in parte creato a tempo di compilazione ed in parte ricopiato da una cartella chiamata stub che contiene dei template dei vari files che dovranno essere generati.

Tra i files generati, particolare importanza riveste una classe Launcher che si occuperà di ricevere dai vari processi i loro indirizzi e quindi di mandare in esecuzione i vari processi che compongono l'orchestrazione.

La fase di valutazione dell'espressione (che si traduce nell'esecuzione dei processi wrapper) è suddivisa in due parti: la prima è una fase di setup dei processi, la seconda è quella di esecuzione vera e propria.

Sintassi ORC:

CNN>>BBC

Sintassi libreria jORC :

CNN cnn=new Site(); BBC bbc=new Site();

(15)

Nella fase di setup viene stabilito per ogni processo wrapper quale sia il processo dal quale deve ricevere la pubblicazione e quale sia il processo al quale spedire la pubblicazione. Questo compito viene svolto da un server centralizzato chiamato

manager, anche lui generato dalla libreria, che si occupa di spedire ai vari wrapper

gli indirizzi dei processi ai quali mandare la pubblicazione.

Per ogni espressione elementare (cioè che non contiene sotto espressioni al suo interno) esiste un manager; conclusa la fase di setup il manager non interviene più nella comunicazione tra i processi.

Nella fase di esecuzione i processi wrapper ricevono le pubblicazioni da altri processi wrapper, eseguono la chiamata del loro sito e pubblicano un valore (ovvero spediscono la loro pubblicazione ad un altro processo wrapper):

(16)

Per semplificare al massimo l'implementazione dei siti, le pubblicazioni che viaggiano da un processo wrapper ad un altro sono di tipo String.

(17)

2.2) Il processo wrapper

Il processo wrapper rappresenta un nodo dell'architettura peer to peer di supporto ai processi che compongono l'espressione. Il processo si occupa di eseguire una chiamata di sito e può essere eseguito su di una delle macchine del cluster a disposizione. Il suo funzionamento è il seguente: nella fase è di setup il processo manda al manager la sua porta e il suo indirizzo ip e riceve dal manager gli indirizzi ai quali spedire la pubblicazione.

Nella fase di esecuzione il wrapper attende che arrivi un messaggio (la pubblicazione di un altro processo wrapper); quando il messaggio è giunto viene eseguita la chiamata del sito (che può utilizzare o meno il messaggio appena giunto). L'esecuzione della chiamata del sito produrrà una pubblicazione, cioè una stringa che consiste nel messaggio da spedire al processo successivo. Infine il processo spedisce la pubblicazione al processo (o processi) successivo e si rimette in attesa di ricevere una nuova pubblicazione.

(18)

2.3) Funzionamento manager

Per ogni espressione viene creato un processo manager. Questo processo viene eseguito sulla macchina dell'utente.

Lo scopo del processo manager è di fornire a ciascun processo wrapper la lista dei processi ai quai spedire la pubblicazione/i . Questo avviene nella fase di setup dell'espressione, dopo la quale il processo manager non interviene nello scambio di messaggi tra i processi wrapper.

Nel caso di un espressione complessa il manager si occupa anche di contattare il manager di una (o più ) sottoespressione per il setup degli indirizzi; per esempio si consideri:

Sintassi ORC:

CNN>>CNN>>CNN

Sintassi libreria jORC :

CNN cnn=new Site(); CNN cnn2=new Site(); CNN cnn3=new Site();

OrcSeQ os=new OrcSeq(cnn,cnn2); OrcSeQ os2=new OrcSeq(cnn3,os);

(19)

In questo caso il manager di os2 contatterà in manager di os per avere l'indirizzo del processo wrapper di cnn al quale il processo wrapper di cnn3 dovra spedire la pubblicazione:

(20)

2.4) Start ,End e Run

Se si pensa all'espressione ORC come ad un grafo dove i processi wrapper rappresentano i nodi allora i processi Start ed End rappresentano rispettivamente il nodo di partenza e quello di arrivo.

Queste due classi vengono generate insieme ai wrapper ed al manager.

Per eseguire un' espressione è necessario eseguire la classe Run; ogni cartella (e quindi ogni espressione) ne ha uno. Questa classe prende a tempo di esecuzione un valore che sarà la pubblicazione spedita al primo (o primi) processo wrapper. Quindi si occupa di mandare in esecuzione tutti i processi facendo in modo che i vari wrapper e manager si sincronizzino tra di loro.

Il processo Start crea anche un file di testo chiamato StartLog.txt che contiene da data e ora di invio dei messaggi da parte del processo Start e un altro file chiamato EndLog.txt che contiene la data e l'ora della ricezione delle pubblicazioni da parte del processo End.

I processi Start ed End essendo eseguiti sulla stessa macchina rendono possibile, confrontando i tempi di invio della pubblicazione e quelli di arrivo, l'esecuzione di test per verificare le performance dell' applicazione distribuita.

(21)

2.5) Esempio di compilazione ed esecuzione di un'espressione

Questo esempio illustrerà come sia possibile, attraverso la libreria, creare ed eseguire dei processi che implementano una semplice espressione ORC.

Come sito verrà utilizzato il sito write (definito nel package TestingPackage) che semplicemente crea un file di testo che ha per contenuto la pubblicazione ricevuta. L'espressione ORC che vogliamo implementare è la seguente:

os (z)Δ write(z)>x>write(x);

Questa espressione prende un parametro iniziale z, esegue una chiamata di sito

write (che scrive un file di testo contenente il valore di z) che pubblica un valore

che viene assegnato ad x e successivamente esegue la chiamata di write con x come parametro.

Per implementare il programma creiamo un file Java chiamato Prova e scriviamo il seguente codice:

(22)

Nella seconda riga viene importata la libreria jORC che contiene le classi per la generazione degli skeleton.

Nelle quinta e nella sesta riga vengono create due istanze della classe write; è importante notare che fino a questo momento non è stato generato ancora alcun tipo di file.

Nella settima riga viene utilizzato il costruttore per le espressioni sequenziali; questo costruttore (contenuto nella libreria jORC) a tempo di esecuzione creerà una catella chiamata “expr_ [hash code dell'oggetto]”ed inizierà a copiare dalla cartella stub i template dei processi wrapper e del manager.

I processi wrapper verranno riempiti in alcune parti con delle informazioni che potranno essere conosciute solo a tempo di compilazione (come il nome dell'espressione corrente e il tipo di sito del quale deve effettuare la chiamata).

1- package TestingPackage; 2- import jORC.*;

3- public class Prova {

4- public static void main(String[] args) throws IOException{

5- write w=new write(); 6- write w2=new write();

7- OrcSeq os=new OrcSeq(w,w2);}

(23)

Per generare il codice è sufficiente recarsi nella cartella dove si trova il package che contiene il file Prova.java e digitare “javac Prova.java” per compilare il file e successivamente “java Prova” per esegure il programma.

L'esecuzione produrrà la cartella “expr_ [hash code dell'oggetto]” contenente files java come i processi wrapper, il manager,le classi per il supporto a tempo di esecuzione, il Launcher (che si occupa di eseguire e sincronizzare i wrapper ed il manager), la classi Start, End e Run.

Dopo aver compilato la cartella come package, sarà sufficente digitare il comando

”java Run [valore]” dove valore rappresenta la pubblicazione che il processo Start

invierà al primo processo wrapper.

Per esegure i processi su macchine diverse il Launcher istanzia per ogni wrapper una Java Virtual Machine che (utilizzando il protocollo SSH) esegue in remoto la classe passata come argomento.

I processi saranno eseguiti sulle macchine delle quali il nome è presente nel files “machines”.

Verranno di seguito descritte le principali classi che compongono la libreria jORC e verrà spiegato, anche attraverso l'uso di esempi, come sia possibile scrivere espressioni ORC con la sintassi della libreria jORC.

(24)

2.6) Site

Site è un’interfaccia che deve essere implementata per creare un sito qualunque. Il metodo più importante da ridefinire è “start()” che dovrà contenere il codice che poi effettivamente sarà eseguito al momento della chiamata del sito.

Sebbene l’interfaccia sia stata definita con i tipi generici tutti gli esempi ed i test sono stati effettuati tenendo conto che le implementazioni dei siti hanno sempre avuto come tipo String.

Il metodo “start ()” prende come argomento una stringa che rappresenta la pubblicazione arrivata al processo wrapper che esegue la chiamata del sito; in questo modo è possibile operare anche sul valore della pubblicazione. Nel caso in cui la chiamata del sito non operi sulla pubblicazione a lui giunta questo valore può semplicemente essere ignorato nella scrittura del metodo.

Il secondo metodo fondamentale è “getPublication()”: questo metodo, che restituisce una stringa, verrà richiamato dal processo wrapper per ottenere la pubblicazione; quindi durante la scrittura del metodo start è necessario settare il valore di una stringa (la stringa “pub”) . Questa stringa rappresenterà la pubblicazione della chiamata del sito e verrà inviata al processo wrapper successivo .

Nel package di test è presente il sito write che implementa l’interfaccia site e che verrà usato in numerosi esempi. Il suo metodo “start()” semplicemente crea un file di testo che ha per nome il nome della macchina sulla quale gira il processo wrapper che esegue la chiamata del sito e che contiene tutte le pubblicazioni che sono giunte al processo.

(25)

Esempio creazione oggetto di tipo site :

write w=new write();

L'istanziazione di un sito non genera nessun tipo di skeleton; ma verrà utilizzato il metodo “start()” del sito definito al momento della sua chiamata.

2.7) Localsite

La classe localSite implementa site ma, a differenza di un sito semplice, funge da classe astratta per la definizione di siti locali.

La classe astratta viene estesa da due siti: ifSite ed Rtimer.

I siti locali non vengono eseguiti da un vero e proprio processo wrapper ma vengono chiamati nel processo wrapper della chiamata di sito successiva nell’espressione. Per esempio, volendo creare una espressione sequenziale (usando il costruttore OrcSeq()) si potrebbe scrivere:

Normalmente verrebbero creati due processi wrapper , uno per ogni sito definito, invece in questo caso verrà creato un solo processo wrapper che prima eseguirà il corpo di Rtimer e poi la chiamata del sito write; questo è il motivo per cui questi

write w=new write();

Rtimer rt=ner Rtimer(5000); OrcSeq os=new OrcSeq(rt,w);

(26)

siti vengono definiti locali.

-Rtimer : il suo costruttore prende un long che rappresenta i millisecondi di pausa. Il suo significato è banalmente quello di attendere un intervallo di tempo prima di poter eseguire la chiamata di sito.

-ifSite:il suo costruttore prende come primo parametro una stringa che può essere una delle seguenti: “<”, ”>”, ”<=”, ”>=” e come secondo parametro un intero. Il significato della chiamate del suddetto sito è la seguente: la pubblicazione giunta viene trasformata in un intero e viene confrontata (utilizzando uno dei possibili quattro valori ) con l’intero dato nel costruttore; se il confronto restituisce il valore true allora viene eseguita anche la chiamata del sito altrimenti la computazione si blocca.

I localSite sono supportati solo dai costruttori OrcSeq() e OrcSeqRec() (che verrà illustrato successivamente).

2.8) Channel

Un oggetto di tipo Chanel rappresenta un canale di comunicazione condiviso sul quale sarà possibile effettuare operazioni di lettura e scrittura di valori. In particolare sara possibile, dato un canale c, prelevare un valore con c.get() ( se il canale è vuoto si mette in attesa che arrivi almeno un elemento), spedire un valore con c.put(), sommare un valore ad una particolare variabile condivisa con c.Add() e leggere il valore di questa variabile con c.GetValue().

La creazione di un canale avrà come effettola creazione di una cartella che conterrà tra l'altro il file procChannel[hashCode oggetto]; prima di mandare in esecuzione istruzioni che utilizzano il canale è necessario avviare questo processo

(27)

spediti o richiesti dai processi che eseguono le chiamate di sito. Esempio creazione di un canale:

Channel c=new Channel();

-Put: Il procChannel ha un buffer attraverso il quale è possibile memorizzare dei valori; una chiamata del metodo Put equivale ad una chiamata di sito; per Esempio:

OrcSeq(c.Put(),w);

Il valore che sarà memorizzato sul canale “c” sarà la pubblicazione giunta al processo wrapper che implementa la chiamata del sito c.Put();

-Get: Il procChannel ha un buffer attraverso il quale è possibile memorizzare dei valori; una chiamata del metodo Get restituisce il primo dei valori (di tipo String) memorizzati nel buffer, nel caso in cui il canale sia vuoto mette in attesa che il canale riceva una Put da qualche altro processo.

Esempio:

OrcSeq(w,c.Get());

Il valore preso dal canale sarà la pubblicazione del processo wrapper che implementa la chiamata del sito c.Get();

Inoltre sono stati introdotti altri due metodi non standard che saranno utili per sviluppare alcuni modelli che saranno descritti nel Capitolo 5.

(28)

-GetValue:il procChannel ha una variabile globale (di tipo intero) che ha valore di default uguale a zero. Questo metodo permette di ricevere il valore di questa variabile (convertito in stringa).

Esempio:

OrcSeq(w,c.GetValue());

Il valore preso dal canale sarà la pubblicazione del processo wrapper che implementa la chiamata del sito c.GetValue();

-Add:il procChannel ha una variabile globale (di tipo intero) che ha valore di default uguale a zero. Questo metodo permette di sommare il valore della pubblicazione ricevuta al valore di questa variabile.

Esempio:

OrcSeq(w,c.Add());

Il valore spedito al canale sarà la pubblicazione del processo wrapper che implementa la chiamata del sito c.Add() mentre la pubblicazione di questa chiamata di sito sarà la stringa “messaggio ricevuto”.

(29)

2.9) OrcExpr

Interfaccia che rappresenta un'espresione generica ORC; tutte le classi successive implementano OrcExpr. La presenza di questa interfaccia ha reso più facile la scrittura dei costruttori delle classi che implementano gli operatori; per esempio: al posto di creare due costruttori distinti per OrcSeq(site a, OrcSeq o) e

OrcSeq(site a, OrcPar p) (OrcPar verrà illustrato successivamnete) è stato creato

un unico costruttore OrcSeq(site a, OrcExpr oe).

2.9.1) OrcSeq

Uno dei principali operatori del linguaggio ORC è la composizione sequenziale (“>>”).

Questo operatore è implementato dalla classe OrcSeq. Un'istanza della classe OrcSeq rappresenta una espressione a due argomenti, dove ogni argomento può essere un'espressione oppure una chiamata di sito.

I costruttori della classe sono:

-OrcSeq(site a, site b): equivale all'espressione ORC a>x>b(x); E' importante notare che anche l' espressione ORC a>>b è rappresentata dal costruttore precedente; infatti che il sito b faccia uso o meno della pubblicazione x del sito a è solamente a descrizione di chi implementa il sito b (in particolare del suo metodo “start()”). Compilatore creerà

dei processi wrapper che eseguiranno le chiamate dei siti a e b e l'esecuzione della chiamata del sito b inizierà solamente quando

(30)

giungerà al processo wrapper che la esegue la pubblicazione del processo che implementa la chiamata del sito a.

Esempio di scrittura dell'espressione write>>write:

- OrcSeq(site a, OrcExpr e);

- OrcSeq(OrcExpr e, site b);

- OrcSeq(OrcExpr e, OrcExpr e2);

Il significato di questi costruttori è analogo a quello del costruttore precedente tranne per il fatto che uno o entrambi gli argomenti sono espressioni ORC precedentemente dichiarate. Il compilatore in questo caso provvederà in modo del tutto trasparente a collegare tra di loro tutti i processi wrapper coinvolti .

Esempio di scrittura dell'espressione ORC write>>write>>write>>write: write w=new write();

write w2=new write(); OrcSeq os=new OrcSeq(w,w2);

write w=new write(); write w2=new write();

OrcSeq os=new OrcSeq(w,w2); write w3=new write();

write w4=new write();

OrcSeq os2=new OrcSeq(w3,w4); OrcSeq os=new OrcSeq(os,os2);

(31)

- OrcSeq(localSite ls,site a);

- OrcSeq(site a, localSite ls);

Questi costruttori utilizzano un'istanza di localSite al posto dei site; anche in questo caso, in modo del tutto trasparente sia l'esecuzione del localSite che del sito a vengono garantite da un solo processo wrapper che esegue (in entrambi i casi) prima il localSite e poi la “start()” del sito a.

Esempio di scrittura dell'espressione Rtimer(500)>>a:

Rtimer rt=new Rtimer(500); write q =new write();

(32)

2.9.2) OrcSeqRec

Questo costruttore è una variante della composizione sequenziale (“>>”).

Un'istanza della classe OrcSeqReq rappresenta una espressione a due argomenti, dove ogni argomento può essere un'espressione oppure una chiamata di sito.

La differenza sostanziale con il costruttore OrcSeq è rappresentata dal fatto che le pubblicazioni del secondo argomento vengono rispedite anche al processo che esegue la chiamata di sito del primo argomento.

I costruttori della classe sono:

-OrcSeqRec(site a, site b): quindi se noi avessimo una espressione del tipo “OrcSeqRec osr =new OrcSeqRec( a, b);” il corrispettivo significato in ORC sarebbe il seguente: osr:= a>x>b(x)>>osr; E' importante notare che anche l' espressione ORC “a>>b” è rappresentata dal costruttore precedente; infatti che il sito b faccia uso o meno della pubblicazione x del sito a è solamente a descrizione di chi implementa il sito b (in particolare del suo metodo “start()”). Il compilatore creerà dei processi wrapper che eseguiranno le chiamate dei siti a e b e l'esecuzione della chiamata del sito b inizierà solamente quando giungerà al processo wrapper che la esegue la pubblicazione del processo che implementa la chiamata del sito a.

Esempio dell'espressione ORC osr(z) Δ write(z)>x>write(x)>>osr:

write w=new write(); write w2=new write();

(33)

Altri costruttori:

- OrcSeqRec(site a, OrcExpr e);

-OrcSeqRec(OrcExpr e, site b); OrcSeqRec(OrcExpr e, OrcExpr e2);

Il significato di questi costruttori è analogo a quello del costruttore precedente tranne per il fatto che uno o entrambi gli argomenti sono espressioni ORC precedentemente dichiarate. Il compilatore in questo caso provvederà in modo del tutto trasparente all'utente a collegare tra di loro tutti i processi wrapper coinvolti .

(34)

2.9.3) OrcPar

Un altro operatore è la composizione parallela (in ORC “|”).

Un' espressione di tipo parallelo è composta da una o più espressioni (oppure siti ) che vengono eseguiti in parallelo su macchine diverse. Il compilatore genererà wrapper per ogni chiamata di sito e un manager che si occuperà dello scambio di pubblicazioni tra le varie espressioni ( e quindi tra i vari processi wrapper che le compongono).

I costruttori sono:

- OrcPar(site[]siti): questo costruttore prende un array di siti e per ogni sito viene creato un processo wrapper che eseguirà il metodo “start()”.

Esempio dell'espressione ORC (write|write)>>write:

write w =ner write(); wrie w2 = new write(); write w3=new write(); site [] sitelist={w,w2};

OrcPar op=new OrcPar(sitelist); OrcSeq os=new OrcSeq(op,w3);

(35)

- OrcPar(Object[]expr): è simile al costruttore precedente tranne per il fatto che l'array passato come parametro è di oggetti; il motivo di questa scelta risiede nel fatto che nell'array possono essere inseriti sia oggetti di tipo site che di tipo OrcExpr; a tempo di compilazione viene eseguito un controllo sui tipi degli elementi dell'array e nel caso in cui un elemento non corrisponda viene ignorato.

Esempio dell'espressione ORC (write|(write>>write)):

write w =ner write();

write w2 = new write(); write w3=new write();

OrcSeq os=new OrcSeq(w2,w3); Object [] sitelist={os,w};

(36)

2.10) where

Questo operatore si occupa della composizione parallela asimmetrica.

Un' espressione di tipo parallelo è composta da due espressioni (oppure siti ) che vengono eseguiti in parallelo su macchine diverse. Il compilatore genererà wrapper per ogni chiamata di sito e un manager che si occuperà dello scambio di pubblicazioni tra le varie espressioni ( e quindi tra i vari processi wrapper che le compongono).

La generazione avverrà in modo del tutto simile a quella del costrutto OrcSeq. I costruttori sono:

-OrcWhere(site a, ste b): vengono creati due processi wrapper, uno per ogni sito che saranno eseguiti in parallelo.

La differenza principale consiste con l'operatore OrcSeq nel fatto che viene generato un altro processo locale che funge da filtro per le pubblicazioni; infatti il wrapper che esegue la chiamata di sito b dovrà ricevere solamente la prima pubblicazione del processo wrapper di a: per questo motivo il wrapper di a spedirà la sua pubblicazione al processo intermedio che la inoltrerà al processo wrapper di b. Successivamente il processo intermedio cesserà di funzionare.

-OrcWhere(OrcExpr oe, site b): simile al precedente; al processo wrapper del

sito b viene sperdita solamente la prima pubblicazione (in ordine di tempo) dell'esperssione oe.

2.11) Il file machines

I processi wrapper vengono eseguiti su diverse macchine. Il files machines è un semplice file di testo che contiene la la lista di tutti i nomi delle macchine del cluster. Il compilatore si occuperà in modo trasparente all'utente di cablare nella classe che lancia i processi wrapper i nomi delle macchine sulle quali eseguirli.

(37)

Capitolo 3:

L'implementazione

Questo capitolo tratterà in modo più approfondito dell'implementazione della libreria jORC (anche attraverso l'uso di codice e pseudocodice), in particolar modo delle classi che contengono i costruttori che si occupano della generazione del codice; saranno inoltre analizzate la classi generate e verrà illustrato il loro funzionamento.

3.1) La libreria jORC

Questa libreria contiene le classi che si occupano della generazione del codice. In ogni classe sono presenti uno o più costruttori, per ogni tipo di espressione ORC; quando, a tempo di esecuzione, verrà istanziato un oggetto di una delle classi della libreria verranno generati dei files java che implementano l'espressione. Per essere utilizzata la libreria deve essere inclusa nel classpath come un qualunque file di estensione jar .

I files generati non verranno integralmente creati dai costruttori delle classi della libreria ma verranno in parte ricopiati dalla cartella stub che contiene alcuni template per le classi da generare.

Le classi contenute nella libreria sono:

1) OrcExpr : rappresenta una generica espressione ORC che viene implementata dalle classi OrcSeq, OrcSeqRec, OrcPar .

(38)

2) OrcSeq : che si occupa della compilazione delle espressioni sequenziali. 3) OrcSeqRec : che si occupa della compilazione delle espressioni sequenziali ricorsive.

4) OrcPar : che si occupa della compilazione delle espressioni parallele. 5) site : l’interfaccia generica di un sito.

6) siti locali ifsite e rtimer.

7) Channel :compilatore di canali.

8) Deployer, launchercreator e runcreator necessari alla generazione di codice.

Successivamente verrà discussa ogni singola classe della libreria illustrando il funzionamento generico e l'implementazione.

3.2) La cartella stub

Questa cartella contiene un insieme di file di estensione java che vengono letti dalla libreria jORC per creare gli skeleton delle espressioni ORC.

Infatti sia le classi wrapper che i manager che si occupano dello scambio di porte non vengono creati da zero ma vengono copiati dai files contenuti in stub; solamente le parti che sono definite dall'utente (come le chiamate di siti oppure i parametri dei localSite) verranno inserite a tempo di compilazione.

(39)

3.3) La cartella TestPackage

Questo package è stato utilizzato per contenere sia i file di prova che le varie definizioni di sito usate negli esempi (write, echo, ecc..). Nulla vieta di creare ed utilizzare un altro package per i propri test, l'importante è che i nuovi siti definiti e le classi relative alle istruzioni che li utilizzano debbano essere nella stessa cartella. File che contiene la lista delle macchine del cluster sulle quali verranno eseguiti i processi wrapper che eseguono le chiamate di sito.

3.4) Espressione sequenziale

Verranno di seguito analizzate le varie fasi della creazione di skeleton per ogni costruttore dell'espressione sequenziale.

(40)

3.4.1) OrcSeq(site a,site b)

Questo costruttore appartiene alla classe OrcSeq che implementa OrcExpr. Implementa l'operatore sequenziale “>>”; i parametri sono due siti a e b. Il costruttore implementa sia l'espressione ORC “a>>b” che “a>x>b(x)”.

Le prime istruzioni eseguite si occupano di scoprire quale sia la classe reale dei due parametri:

String s1class=s1.getClass().toString();

s1class=s1class.substring(5, s1class.length()).trim(); String channelDir=””

Successivamente vengono effettuati dei controlli per verificare se i parametri dell' espressione sono dei siti semplici oppure dei metodi di un canale:

if((s1class.equals("procChannel.Get"))|| (s1class.equals("procChannel.Put"))|| (s1class.equals("procChannel.Add"))|| (s1class.equals("procChannel.GetValue"))){ channelDir=s1.GetDir(); }

Poi viene creata una cartella (che sarà quella contenente tutti i files necessari all'esecuzione dell'espressione); il nome della cartella è la stringa “expr_” seguita dall hashCode dell'oggetto:

(41)

Dir="expr_"+Dir;

boolean success = (new File(Dir)).mkdir(); if (success)

{

System.out.println("Ho creato: " + Dir); }else{

System.out.println("La cartella già esiste!"); }

In seguito vengono create due istanze delle classi launcherCreator e runCreator: //creo il launcher

launcherCreator lc=new launcherCreator(Dir); //creo run.java

runCreator rc =new runCreator(Dir); rc.addProcessLauncher();

Il launcherCreator è una classe che si occupa di creare un file (chiamato appunto “Launcher.java”) che ha il compito di mandare in esecuzione i processi java che compongono l'espressione.

Al momento della istaziazione di un oggetto di tipo launcherCreator viene creato il file “Launcher.java” nella cartella dell'espressione corrente; successivamente nel costruttore dell 'espressione sequenziale verranno invocati i metodi della classe

launcherCreator che hanno il compito di scrivere nel file “Launcher.java” le

righe di codice per l'esecuzione di ogni particolare processo (i metodi sono

AddWrapper per l'esecuzione del wrapper, AddManager per l'esecuzione del

manager, ecc...).

Il file Launcher.java, per mandare in esecuzione i processi crea un'istanza di JVM attraverso la classe Runtime ed esegue un processo su di essa attraverso il comando

exec:

Process p;

Runtime rt =Runtime.getRuntime();

(42)

Il runCreator è una classe analoga al launcherCreator; la differenza risiede nel fatto che crea una classe java che esegue i file “Launcher” di ogni espressione. Infatti un'espressione ORC può essere composta da più sottoespressioni, perciò per eseguire un'espressione complessa è necessario che vengano eseguiti tutti i

Launcher.java delle sottoespressioni prima del Launcher.java dell'espressione

corrente.

Il runCreator genera un file chiamato Run.java che si occupa di eseguire in modo sequenziale tutti i Launcher.java delle sottoespressioni.

Quando l'utente vorrà eseguire un espressione dovrà eseguire proprio il processo

Run.

Successivamente, con istruzioni simili tra di loro, vengono copiati dalla cartella

stub (in questo caso dalla sua sottocartella seqSiteSite) i vari template che

serviranno alla creazione delle classi per i processi wrapper, del manager e delle classi che forniranno il supporto a tempo di esecuzione.

Durante la fase di copiatura i template vengono modificati in alcuni punti con informazioni che possono essere conosciute solo a tempo di compilazione (come il nome del sito del quale deve essere eseguita la chiamata ed il nome delle sotto espressioni).

Alla fine del costruttore dell'espressione sequenziale vengono chiamati i seguenti metodi:

//chiude launcher lc.closeLauncher(); //chiude Run

rc.closeRun();

(43)

Il seguente schema riassume le varie fasi della generazione di un'espressione:

Al termine dell'esecuzione del costruttore la cartella deve essere compilata come package.

FUNZIONAMENTO DI UN GENERICO COSTRUTTORE:

1. Creazione cartella espressione corrente. 2. Creazione Launcher.java e Run.java.

3. Copia + modifica template di file1 in cartella espressione corrente.

4. Aggiunta dell'esecuzione di file1 in Launcher.java.

5. Copia + modifica template di file2 in cartella espressione corrente.

6. Aggiunta dell'esecuzione di file2 in Launcher.java.

7. ....

(44)

Adesso verranno analizzate le principali classi generate dal costruttore OrcSeq al momento dell'istanziazione di un oggetto :

3.4.1.1) server.java

Questa classe implementa il processo manager che si occupa, nella fase di setup dell'esecuzione, dello scambio di indirizzi tra i vari processi wrapper.

In una prima fase crea un serverSocket sul quale attenderà le richieste dei vari processi; successivamente verrà creato un file (Settings.txt)che conterrà l'indirizzo IP e la porta di questo manager.

Quando il processo launcher manderà in esecuzione i wrapper lo farà passando come parametro l'IP e la porta del manager prese dal file Settings.txt (in questo modo ogni processo wrapper potrà subito connettersi al proprio manager):

Successivamente vengono create delle istanze di alcune classi (Monitor,

MonitorForIn,MonitorForOut) che servono per la memorizzazione degli indirizzi

dei processi. Queste classi servono principalmente per la memorizzazione degli indirizzi per la comunicazione: tra i processi wrapper della chiamata di sito a e b,

tra il wrapper di b e i processi successivi e tra il wrapper di a e i processi precedenti.

Queste classi garantiscono la mutua esclusione attraverso metodi synchronized.

1. Pubblica un server socket su porta libera 2. Crea un file Settings.txt

3. Scrive nel file indirizzo IP e porta del server socket

(45)

Infine viene eseguito un ciclo che accetta le richieste di connessione al serverSocket e, per ogni richiesta, genera un thread che si occupa della comunicazione tra i processi:

while(true) {

Socket socket = serverSocket.accept(); SequentialServerThread serverThread = new

SequentialServerThread (socket,Monitor,M2,M3); serverThread.start();}

3.4.1.2)

SequentialServerThread.java

E' un thread che si occupa della comunicazione tra il manager ed i processi wrapper; il suo scopo principale è quello di fornire ai wrapper gli indirizzi dei processi ai quali spedire lo pubblicazioni.

Inizialmente vengono istanziati degli stream in lettura e scrittura sul socket (passato come parametro dal manager).

Monitor=new monitor(dir); M2=new MonitorForOut(); M3=new MonitorForIn(dir);

(46)

Quindi, all'arrivo del primo messaggio, viene eseguito un controllo su chi sia il mittente; se il messaggio è la stringa “Sender” il mittente è il processo wrapper relativo alla chiamata del sito a.

Quindi il thread memorizza il messaggio successivo (che sarebbe l'indirizzo del serverSocket del processo wrapper) e poi manda al processo stesso gli indirizzi e le porte dei processi ai quali spedire le pubblicazioni (in questo caso l'indirizzo e l'IP del wrapper che esegue la chiamata di sito b).

if(msg.equals("Sender")){ String port=br.readLine(); M2.addOutPort(port); pw.println("Start"); String porta=Monitor.getPort(); pw.println(porta); msg=br.readLine(); }

Altri possibili valori della stringa possono essere “SendMePort” e “HereYouPort”: in questo caso il processo che si connette al server è il processo manager di un'altra espressione; più precisamente sarà il manager di un'espressione che usa l'espressione corrente come sottoespressione.

Nel caso di “SendMePort” il manager esterno richiede la lista delle porte e degli indirizzi IP dei processi ai quali spedire le proprie pubblicazioni.

Messaggi ricevuti dal thread: “Sender”da wrapper sito a

“HereYouPorts”da manager sottoespressione o processo End “GiveMePorts” da manager sottoespressione o processo Start “ip_processo@porta_processo” da wrapper sito b

(47)

if(msg.equals("SendMePort")){

String por =M2.getOutPort(); pw.println(por);

}

Nel caso di “HereYouPort” il manager esterno invia la lista delle porte e degli indirizzi IP dei processi che devono ricevere le pubblicazioni.

if(msg.equals("HereYouPort")){

String porte=br.readLine(); M3.addInPort(porte);

}

Ultimo caso possibile è quello della comunicazione con il processo wrapper che esegue la chiamata del sito b; in questo caso vengono memorizzati la porta e l'IP del wrapper (che poi verranno spedite dal thread che comunica col wrapper del sito

a) e poi inviate le porte e gli indirizzi IP dei processi ai quali il processo wrapper

deve spedire la pubblicazione (che erano state memorizzate con la “HereYouPort”). else{ Monitor.setPort(msg); String porte=M3.getInPort(); pw.println(porte); pw.println("Wait"); M3.createFile(); }

(48)

Il seguente schema riassume il funzionamento del thread:

3.4.1.3) wrapper sito a

Questo processo viene mandato in esecuzione dal processo Launcher ricevendo come parametri l'IP e la porta del manager al quale spedire le proprie porte e dal quale ricevere le porte e gli IP dei processi wrapper ai quali spedire le pubblicazioni.

In una prima fase si occupa di aprire degli stream di input ed output con il manager e successivamente apre un serverSocket che verrà utilizzato per ricevere le

FUNZIONAMENTO DEL THREAD:

1. Inizializza stream di comunicazione 2. Riceve stringa

3. Analizza stringa e identifica client 4. Esegue codice relativo al client 5. Muore

(49)

Quindi invia al manager la stringa “Sender” in modo che il manager lo identifichi; successivamente spedisce l'indirizzo IP e la porta del proprio serverSocket in modo che il manager possa fornirli al processo Start oppure al manager di un'altra espressione.

Quindi riceve dal manager la lista dei processi ai quali spedire la pubblicazione (sotto forma di stringhe). Queste verranno parsate ed inserite in un array:

String porte[]; int numporte=1;

StringTokenizer t = new StringTokenizer(letti,"|"); String stringa=t.nextToken(); int ii=0; while(true){ porte[ii]=stringa; if (t.hasMoreElements()){ stringa=t.nextToken(); numporte++; ii++; }else break; }

Infine il processo si mette in attesa (tramite il suo serverSocket) di ricevere pubblicazioni da altri processi wrapper; per ogni messaggio arrivato viene creato

(50)

un thread che si occupa della gestione delle pubblicazioni.

Il seguente schema riassume il funzionamento del wrapper:

3.4.1.4) Thread del wrapper del sito a

Questo thread (indicato nella cartella col nome di Proc0Thread) si occupa di gestire la pubblicazione giunta al processo wrapper; una volta giunta la pubblicazione si occupa di eseguire la chiamata del sito (in particolare crea una nuova istanza del sito a e ne esegue il metodo “start()”).

FUNZIONAMENTO DEL WRAPPER:

1. Si connette al server del manager 2. Inizializza stream di comunicazione 3. invia indirizzo del suo serverSocket 4. invia “Sender”

5. Riceve lista porte dei processi ai quali mandare le pubblicazioni

6. si mette in attesa sul serverSocket 7. per ogni connessione crea un thread

(51)

TestingPackage.a wc=new TestingPackage.a(); wc.start(lett);

Questa è la parte dove viene riempito lo skeleton con la creazione di un oggetto di tipo a e l'esecuzione del metodo start() che esegue le istruzioni della chiamata del sito.

Una volta eseguito la chiamata di sito viene aperto un socket per ogni elemento nel l'array degli indirizzi ai quali spedire le pubblicazioni e viene spedita loro la pubblicazione (con il metodo “GetPublication()”):

for(int z=0;z<numporte;z++){ Socket socket2 =null;

String ippieporta=porte[z];

int indice=ippieporta.lastIndexOf("@"); String ip=ippieporta.substring(0, indice); int indice2=ip.lastIndexOf("/");

ip=ip.substring(indice2+1, ip.length()); System.out.println("Ip parsato: "+ip);

String porta=ippieporta.substring(indice+1, ippieporta.length());

PrintWriter pw2=null; socket2 = new

(52)

pw2 = new PrintWriter(socket2.getOutputStream(),true); pw2.println(wc.getPublication()); pw2.close(); socket2.close(); }

3.4.1.5) wrapper sito b

Il wrapper si comporta in modo analogo al wrapper del sito a; l'unica differenza risiede nel fatto che, dopo aver spedito la porta e l'ip del proprio serverSocket al manager, si mette in attesa che il manager spedisca la lista dei processi ai quali mandare la pubblicazione (che possono essere sia altri processi wrapper che il processo end).

Infine il processo si mette in attesa (tramite il suo serverSocket) di ricevere pubblicazioni da altri processi wrapper; per ogni messaggio arrivato viene creato un thread che si occupa della gestione delle pubblicazioni.

(53)

3.4.1.6) thread del wrapper del sito b

Il thread si comporta come il thread del wrapper del processo a.

3.4.1.7) il processo Start

I processi descritti fino a questo momento vengono lanciati (e fatti sincronizzare) dal Launcher .

La computazione dell'espressione però non parte perché il processo wrapper iniziale (quello che esegue la chiamata del sito a) è in attesa di ricevere una pubblicazione; inoltre il processo wrapper finale si blocca in attesa degli indirizzi ai quali spedire la propria pubblicazione.

Per questo motivo sono stati progettati altri due processi (Start ed end) che hanno rispettivamente il compito di spedire una pubblicazione iniziale al processo wrapper iniziale e di ricevere le pubblicazioni finali; inoltre (essendo entrambi processi eseguiti sulla macchina che lancia l'espressione) hanno anche lo scopo di creare dei file di log (StartLog.txt ed EndLog.txt) attraverso i quali è possibile calcolare il tempo passato tra l'invio della pubblicazione iniziale e l'arrivo della pubblicazione (o pubblicazioni) finale.

La struttura della classe Start è la seguente: legge nella cartella corrente il file “Settings.txt” dove prende l'indirizzo IP e la porta del processo manager. Poi si connette al manager e manda la stringa “SendMePort”; una volta ricevuta la lista

(54)

dei processi wrapper ai quali spedire la pubblicazione iniziale apre un socket per ogni indirizzo nella lista ed invia loro il messaggio (segnando il tempo dell'invio nel file di StartLog.txt).

3.4.1.8) il processo end

Il processo end ha lo scopo di raccogliere tutte le pubblicazioni finali quindi, per garantire la ricezione di tutti i messaggi utilizza un serverSocket che genera un thread per ogni pubblicazione giunta.

Per connettersi al manager legge nella cartella corrente il file “Settings.txt” dal

FUNZIONAMENTO DI START:

1. Si connette al server del manager 2. Inizializza stream di comunicazione 3. Invia “GiveMePorts”

4. Riceve lista porte dei processi ai quali mandare le pubblicazioni

5. Per ogni elemento della lista apre un socket 6. Invia la pubblicazione iniziale ad ogni processo 7. Segna sul file “StartLog.txt” il momento

(55)

quale prende l'indirizzo IP e la porta del manager e quindi invia la stringa “HereYouPort” seguita da un'altra stringa che contiene l'indirizzo IP e la porta del suo serverSocket; il manager provvederà poi a spedire questi dati al processo wrapper del sito b, che quindi spedirà le sue pubblicazioni al processo end.

File f=new File(dir+"/Settings.txt"); FileInputStream fis=null; fis=new FileInputStream(f);

InputStreamReader is2=new InputStreamReader(fis); BufferedReader br2=new BufferedReader(is2);

String portamanager=br2.readLine(); String ipmanager=br2.readLine(); Socket s=new Socket(InetAddress.getByName(ipmanager),Integer.parseInt(portama nager)); PrintWriter pw = new PrintWriter(s.getOutputStream(),true); pw.println("HereYouPort"); String ippi= InetAddress.getLocalHost().getHostAddress(); String str=Integer.toString(ii); str=ippi+"@"+str; pw.println(str); while (true){ Socket sso=ss.accept();

endThread et=new endThread(sso); et.start();}

(56)

3.4.1.9) La sincronizzazione

La sincronizzazione tra i processi è garantita attraverso due meccanismi: il primo è l'utilizzo delle primitive di sincronizzazione wait() e notifyAll() nei metodi per accedere alle strutture dati che contengono le liste dei processi (le tre classi

monitor); il secondo è l'utilizzo di cookie che vengono creati dal manager.

Infatti il manager dopo aver inviato la lista degli indirizzi dei processi ad un processo wrapper (o ad un processo Start o end) crea un file di testo (StartEnd.txt,

StartStart.txt, StartWrapper.txt, ecc...).

Il launcher, prima di eseguire il secondo processo nella sua lista, controlla che il

FUNZIONAMENTO DI END:

1. Si connette al server del manager 2. Inizializza stream di comunicazione 3. Invia “HereYouPorts”

4. Invia l'indirizzo del suo server socket 5. Si mette in attesa di connessioni sul server 6. Per ogni connessione ricevuta esegue un thread

(57)

vale per l'esecuzione di qualunque processo nella lista del launcher.

Frammento di codice del launcher che si occupa dell'esecuzione dei processi:

Runtime rt =Runtime.getRuntime();

InputStreamReader is2=new InputStreamReader(fis); br2=new BufferedReader(is2);

portamanager=br2.readLine();} ipmanager=br2.readLine();

p=rt.exec("ssh user@ip java expr_27196165/wrapper_seq4660784 "+ipmanager+" "+portamanager+" ");

3.4.1.10) Run

La classe Run è quella che effettivamente deve essere eseguita per lanciare la dislocazione e l'esecuzione dei processi.

Questa classe prende da linea di comando un parametro, che sarà la prima pubblicazione spedita al processo wrapper iniziale.

A livello di implementazione si occupa di eseguire il processo launcher, lo Start e l'end. Il motivo per cui l'esecuzione di questi processi non si stata fatta nel processo launcher verrà spiegato nel prossimo paragrafo quando si analizzerà il costruttore che prende come parametro due o più espressioni.

(58)

3.4.2) OrcSeq(site a, OrcExpr oe)

Il funzionamento di questo costruttore e quasi del tutto simile a costruttore che prende come parametri due siti.

La differenza sostanziale risiede nel trattamento del secondo argomento: visto che si tratta di un espressione (che quindi dovrà essere già stata definita precedentemente) non viene generato alcun tipo di codice.

Infatti il manager di questa espressione contatterà il manager della sottoespressione e richiederà le porte dei processi ai quali spedire le pubblicazioni; per fare questo è necessario che la sottoespressione (e quindi i processi wrapper e il manager che la compongono) siano già in esecuzione.

Questo problema è risolto dal processo Run che nella sua lista di processi da eseguire ha come primi i processi Launcher delle sottoespressioni e poi il proprio

Launcher. La sincronizzazione è garantita dalla presenza di file cookie

(StartEnd.txt) che vengono generati dalle sottoespressioni e che fanno in modo che i processi delle sottoespressioni siano sempre in esecuzione prima dei processi dell'espressione corrente.

Il manager riesce a contattare i manager delle sottoespressioni perché a tempo di compilazione sono state cablate nel manager le cartelle che contengono le sottoespressioni e quindi viene letto il file Settings.txt (che ogni manager crea) che contiene il suo indirizzo IP e la sua porta.

(59)

3.4.3) OrcSeq(OrcExpr oe, site b)

Il funzionamento è analogo a quello del costruttore precedente.

3.4.4) OrcSeq(OrcExpr oe, OrcExpr oe2)

Anche questo costruttore utilizza lo stesso meccanismo dei costruttori precedenti per comunicare con le sottoespressioni. L'unica differenza è nel codice del

manager che, prima di lanciare i thread per la gestione delle connessioni al proprio serverSocket, si connette ai manager delle sottoespressioni:

(60)

3.5) Espressione sequenziale ricorsiva

Questo costruttore è presente nella classe OrcSeqRec che implementa OrcExpr. I costruttori sono: OrcSeqRec(site a, site b), OrcSeqRec(site a, OrcEpr b), OrcSeqRec(OrcExpr a, site b), OrcSeqRec(OrcExpr a, OrcExpr b).

Il funzionamento è simile a quello della classe OrcSeq; la differenza risiede solamente nel fatto che l'espressione creata è ricorsiva e quindi, ragionando in termini di processi che compongono l'espressione, che le pubblicazioni del

FUNZIONAMENTO DEL MANAGER:

1. Legge nel file “Settings.txt” della cartella di oe2 l'indirizzo del manager della

sottoespressione oe2

2. Apre un socket con questo indirizzo

3. Spedisce al manager della sottoespressione “GiveMeports”

4. Ricevuta la lista delle porte cerca l'indirizzo del manager nella cartella di oe

5. Apre un socket con questo indirizzo

6. Manda al manager di oe “HereYouPorts” 7. Manda al manager di oe la lista dei processi 8. Chiude i socket

(61)

secondo parametro vengono spedite anche al primo parametro.

La fase di creazione degli skeleton è la stessa di quella dei costruttori sequenziali; solo due file sono diversi: il manager (che differisce dal manager sequenziale solo nell'istruzione di lanciare thread) e il thread per la gestione dello scambio di indirizzi tra processi.

3.5.1) SequentialRecThreadServer

Questo thread differisce da quello del sequenziale nel momento in cui arrivano le porte e gli indirizzi IP del primo operando: vengono memorizzate in due diversi monitor (sia quello per ricevere pubblicazioni dall'esterno si a quello per spedire le pubblicazioni)

if(msg.equals("Sender")){

String port=br.readLine(); M2.addOutPort(port);

M3.setRecPort(port);

Quando il wrapper (o il manager) del secondo parametro richiederà la lista delgli indirizzi ai quali spedire la pubblicazioni, il manager invierà anche l'indirizzo del primo processo.

Riferimenti

Documenti correlati

[r]

Giuseppe Gigante dr.ssa

La sensibilità diagnostica della CTC, ancor più della colonscopia è condizionata dalla pulizia intestinale; residui solidi, fissi o mobili possono costituire un impor- tante

Eseguire le riprese utilizzando tecniche e scelte stilistiche per le inquadrature (es. angolo di ripresa, ecc.), coerentemente alla sceneggiatura (es. scena tensiva, climax, ecc.)

sediamento dell’organismo specificato. Nei casi in cui l’organismo ufficiale responsabile conclude che è possibile eradicare l’organismo specificato, tenendo conto delle

Esecuzioni di brani con acc.to del pianoforte Obiettivi minimi: Saper decodificare ed eseguire correttamente allo strumento gli elementi delle notazione musicale in funzione

Cliccando sul pulsante di richiesta dell’avviso di pagamento, il programma produce un documento contenente tutte le informazioni necessarie per poter effettuare il

Il sistema operativo, quando c’è bisogno di eseguire un nuovo servizio (e quindi di mandare in esecuzione un nuovo processo) decide di mandarlo in esecuzione sul processore che