• Non ci sono risultati.

Il meccanismo di invocazione remota

N/A
N/A
Protected

Academic year: 2021

Condividi "Il meccanismo di invocazione remota"

Copied!
24
0
0

Testo completo

(1)

Capitolo 4

Il meccanismo di

invocazione remota

Nei capitoli precedenti è stato spiegato cos’è RMI, come è architettato e sono state esaminate le classi e interfacce dei package che lo compongono.

In questo capitolo, invece, verrà esaminato cosa accade quando viene invocato un metodo remoto; vedremo, quindi, la sequenza con cui le varie classi di sistema entrano in gioco e come avviene l’interazione tra di esse. Inoltre verranno forniti maggiori dettagli su ciò che concerne il meccanismo della garbage collection distribuita.

(2)

4.1 Impostazione di RMI

L’esecuzione del più semplice esempio di oggetto remoto richiede qualche operazione di impostazione in più rispetto all’esecuzione di un programma o di un’applet indipendente. Occorre, infatti, lanciare due programmi, uno sul client e l’altro sul server.

Dato che un oggetto remoto deve esistere prima che possa essere richiamato, bisogna innanzitutto esaminare i passi necessari alla sua creazione e alla sua registrazione nel registro remoto (il cosiddetto registry).

Per poter implementare un oggetto remoto, si deve prima creare un’interfaccia per esso. Questa deve essere pubblica (cioè deve essere dichiarata con la parola chiave public) e deve estendere necessariamente l’interfaccia

java.rmi.Remote: quest’ultima conterrà i metodi remoti che potranno essere

richiamati dai client e tali metodi dovranno “lanciare” una RemoteException

(devono cioè contenere nella dichiarazione la clausola throws RemoteException), poiché le chiamate remote potrebbero non essere eseguite correttamente a causa di problemi dovuti alla connessione di rete.

Un semplicissimo esempio di interfaccia remota potrebbe essere il seguente:

public interface MyServer extends Remote { int getDatanum() throws RemoteException; String getData(int n) throws RemoteException; }

Dopo l’interfaccia, occorre creare una classe che la implementi. Questa classe estende tipicamente la classe UnicastRemoteObject, ma può anche estendere altre classi derivate da RemoteServer.

(3)

La classe di implementazione deve avere un costruttore che crea e inizializza l’oggetto remoto e deve implementare tutti i metodi definiti nell’interfaccia remota. Deve, inoltre, possedere un metodo main() affinché

possa essere eseguita come classe remota.

Il metodo main() deve utilizzare inizialmente, il metodo

setSecurityManager() della classe System[11] per attivare un oggetto

gestore della sicurezza. Successivamente, deve creare un’istanza della classe e, servendosi dei metodi bind o rebind della classe Naming, deve registrare nel

registry l’oggetto creato.

Una volta scritta l’interfaccia remota e la classe che la implementa, bisognerà compilarle usando il comando “javac <nomeclasse>.java”[12],

in modo da ottenere i relativi file “.class” contenenti il bytecode. Dopodiché, è necessario generare gli stub per le classi che implementano interfacce remote, usando il comando “rmic -v1.2 <nomeclasse>”[13]; dove nomeclasse va

scritto senza estensione, in quanto viene considerato per default il file “.class”

(ecco dunque che prima di eseguire il comando rmic vanno necessariamente compilati i file “.java”). L’opzione “-v1.2” serve per indicare che vanno creati solo i file con suffisso “_Stub” e non quelli con suffisso “_Skel”, conformemente a quanto richiesto dalle nuove versioni a partire dal JDK 1.2 .

A questo punto, la classe contenente il metodo main() può essere

eseguita, usando il comando “java <options> <nomeclasse>”[14], e ricordandosi di aver prima avviato il registro remoto tramite la riga di comando “start rmiregistry”.

Per ciò che concerne il lato client, bisogna scrivere la classe contenente il metodo main(), all’interno del quale è presente l’invocazione di uno o più metodi dell’oggetto remoto. A tal proposito, occorre tener presente che, il server o la connessione di rete possono diventare temporaneamente inutilizzabili e generare una RemoteException; pertanto, il linguaggio Java costringe ad

(4)

intercettare e gestire (mediante blocchi “try-catch”) tale eccezione ad ogni chiamata al metodo remoto.

Il client accede all’oggetto remoto attraverso uno stub locale, il quale viene recuperato dal registry, usando il metodo lookup() della classe Naming e specificando il nome del server e dell’oggetto stesso.

Il file “.java” contenente la classe col metodo main() dovrà essere dapprima

compilato (usando il comando “javac”) e poi eseguito col comando:

“java <option> <nomeclasse>”.

4.2 Invocazione di metodo remoto a

basso livello

Dopo aver spiegato come impostare e scrivere delle classi per usare il meccanismo di invocazione di metodo remoto a livello oggetto (cioè dal punto di vista del programmatore), ricapitoliamo cosa succede quando un client invoca il metodo di un oggetto remoto.

Innanzitutto, il client si rivolge all’oggetto registry (che svolge la funzione di registro remoto), posto sul server, per chiedere uno stub locale dell’oggetto remoto. Ottenuto lo stub, ogni volta che il client invoca un metodo di tale oggetto remoto, la chiamata viene effettuata localmente al metodo omonimo contenuto nello stub (infatti lo stub di un oggetto remoto contiene la definizione di tutti i metodi dichiarati nell’interfaccia remota dell’oggetto in questione).

Successivamente, lo stub si occuperà di creare un “pacchetto” contenente indicazioni circa l’individuazione dell’oggetto remoto (nome dell’oggetto e indirizzo dell’host server a cui appartiene), il metodo da invocare e i relativi parametri (sui quali viene effettuato il marshal) e di trasmetterlo (su uno stream di uscita lungo la connessione di rete) verso il server.

(5)

Dal lato server, un oggetto ricevitore leggerà tali informazioni e invocherà localmente il metodo in questione. Una volta ottenuto il risultato, ne effettuerà il marshal e lo restituirà (su uno stream lungo la connessione) allo stub del client, da dove verrà fornito al chiamante.

Figura 4.1 – Invocazione di metodo remoto.

Di seguito verrà esaminato in dettaglio ciò che accade e quali classi di sistema entrano in gioco durante l’invocazione di metodo remoto sia dal lato client che dal lato server.

4.2.1 Lato Client

Quando viene invocato un metodo di un oggetto remoto, si è visto che in realtà, l’invocazione viene fatta verso l’omonimo metodo della classe stub locale, ottenuta tramite il metodo lookup() della classe Naming.

Per comprendere meglio il meccanismo di comunicazione tra lo stub e il lato server, viene riportato di seguito, un semplicissimo esempio in cui un client invoca un metodo di un oggetto remoto e viene scritto anche il codice del relativo stub (tale codice viene prodotto dal sistema RMI e non dal programmatore).

(6)

Esempio

/* HelloWorld.java è l’interfaccia remota */ import java.rmi.*;

public interface HelloWorld extends Remote {

public String getMessage(int n) throws RemoteException; }

/* HelloWorldImpl.java è la classe che implementa l’interfaccia remota (entrambe sul server) */ import java.net.MalformedURLException;

import java.rmi.*; import java.rmi.server.*;

public class HelloWorldImpl extends UnicastRemoteObject

implements HelloWorld { public HelloWorldImpl() throws RemoteException

{ super(); }

public String getMessage(int n) throws RemoteException {

switch(n) {

case 0: return “Hello World zero!”; case 1: return “Hello World uno!”; case 2: return “Hello World due!”; }

return “Hello World!”; }

public static void main(String[] args) {

try {

HelloWorldImpl helloWorldObject = new HelloWorldImpl(); Naming.rebind( “HelloWorld”, helloWorldObject );

System.out.println(“HelloWorldImpl.main(): Server pronto”); } catch(MalformedURLException mue) {

System.err.println(“HelloWorldImpl.main(): nome dell’oggetto non valido: ” + mue); } catch(UnknownHostException uhe) { System.err.println(“HelloWorldImpl.main(): host sconosciuto: ” + uhe); } catch(RemoteException re) { System.err.println(“HelloWorldImpl.main(): errore di rete: ” + re); } } }

(7)

/* HelloWorldClient.java è la classe che implementa il lato client dell’esempio (cioè quella che invoca il metodo remoto */ import java.net.MalformedURLException;

import java.rmi.*;

public class HelloWorldClient {

public static void main(String[] args) {

HelloWorld helloWorldObject = null; String message = null;

try {

helloWorldObject = (HelloWorld)Naming.lookup(“HelloWorld”); } catch(NotBoundException nbe) {

System.err.println(“HelloWorldClient.main(): server non trovato: ” + nbe);

} catch(MalformedURLException mue) {

System.err.println(“HelloWorldClient.main(): nome dell’oggetto non valido: ” + mue); } catch(UnknownHostException uhe) { System.err.println(“HelloWorldClient.main(): host sconosciuto: ” + uhe); } catch(RemoteException re) { System.err.println(“HelloWorldClient.main(): errore di rete: ” + re); } try { message = helloWorldObject.getMessage(1); } catch(RemoteException re) { System.err.println(“HelloWorldClient.main(): errore di rete: ” + re); } if (message != null)

System.out.println(“Il messaggio è: “ + message); else

System.out.println(“Il client non è stato in grado di recuperare il messaggio dal server”); }

}

Di seguito viene presentata una porzione di codice dello stub generato per la versione 1.1 del JDK (cioè usando il comando rmic senza l’opzione “-v1.2”). Quanto esposto, comunque, non è da considerarsi obsoleto, in quanto, sebbene le recenti versioni di Java2 demandano molte operazioni al runtime di RMI, nel complesso le operazioni svolte sono le medesime (infatti nelle versioni 1.1 le

(8)

operazioni sono svolte tutte nello stub, mentre nelle versioni successive parte di esse viene svolta dal runtime di RMI). Inoltre, per completezza, viene presentato anche lo stub creato quando si usa l’opzione “-v1.2”, in modo da poter avere un

termine di paragone.

1: // parte dello stub relativa al metodo getMessage(int) 2: public java.lang.String getMessage(int $param_int_1) 3: throws java.rmi.RemoteException { 4: try{

5: if (useNewInvoke) 6: {

7: Object $result = ref.invoke(this,

$method_getMessage_0, new java.lang.Object[]{ new java.lang.Integer($param_int_1)},

-1610639450831037496L);

8: return ((java.lang.String) $result); 9: } else { 10: java.rmi.server.RemoteCall call = ref.newCall((java.rmi.server.RemoteObject)this, operations, 0, interfaceHash;) 11: try { 12: java.io.ObjectOutput out = call.getOutputStream(); 13: out.writeInt($param_int_1); 14: } catch (java.io.IOException e) { 15: throw new

java.rmi.MarshalException(“error marshalling arguments”, e); 16: } 17: ref.invoke(call); 18: java.lang.String $result; 19: try { 20: java.io.ObjectInput in = call.getInputStream(); 21: $result = (java.lang.String)in.readObject(); 22: } catch (java.io.IOException e) {

23: throw new java.rmi.UnmarshalException(“error unmarshalling return”, e); 24: } catch (java.lang.ClassNotFoundException e) { 25: throw new java.rmi.UnmarshalException(“error unmarshalling return”, e); 26: } finally { 27: ref.done(call); 28: } 29: return $result; 30: } 31: } catch (java.lang.RuntimeException e) { 32: throw e;

(9)

33: } catch (java.rmi.RemoteException e) { 34: throw e;

35: } catch (java.lang.Exception e) {

36: throw new java.rmi.UnexpectedException(“undeclared checked exception”, e); 37: }

38: }

Osservando nel listato la parte che va dalla riga 11 alla riga 16, si nota come viene ottenuto, dall’oggetto call, lo stream per la scrittura dei parametri e come viene serializzato il parametro intero (riga 13). Nella riga 17 viene “invocato” il metodo, mentre di seguito (fino alla riga 28) viene eseguita la lettura del risultato.

Il listato successivo mostra invece lo stub creato con la versione 1.4.1 del JDK:

// Stub class generated by rmic, do not edit. // Contents subject to change without notice. package unipi.server;

public final class HelloWorldImpl_Stub

extends java.rmi.server.RemoteStub

implements unipi.server.HelloWorld, java.rmi.Remote {

private static final long serialVersionUID = 2;

private static java.lang.reflect.Method $method_getMessage_0; static{

try {

$method_getMessage_0 =

unipi.server.HelloWorld.class.getMethod("getMessage", new java.lang.Class[] {int.class}); } catch (java.lang.NoSuchMethodException e) {

throw new java.lang.NoSuchMethodError("stub class initialization failed"); }

}

// constructors

public HelloWorldImpl_Stub(java.rmi.server.RemoteRef ref) { super(ref);

}

// methods from remote interfaces // implementation of getMessage(int)

(10)

public java.lang.String getMessage(int $param_int_1) throws java.rmi.RemoteException {

try {

Object $result = ref.invoke(this, $method_getMessage_0, new java.lang.Object[] {new java.lang.Integer($param_int_1)}, -1610639450831037496L);

return ((java.lang.String) $result); } catch (java.lang.RuntimeException e) {

throw e;

} catch (java.rmi.RemoteException e) { throw e;

} catch (java.lang.Exception e) {

throw new java.rmi.UnexpectedException("undeclared checked exception", e); }

} }

Come si può notare il corpo del metodo adesso è notevolmente ridotto rispetto al caso precedente; ciò è dovuto al fatto che le versioni successive di Java2 demandano molte operazioni al runtime di RMI, anziché contenerle nello stub (complessivamente, però, le operazioni svolte sono le medesime in ambo i casi).

Dal precedente listato si può osservare che, le classi stub estendono la classe base java.rmi.server.RemoteStub, la quale costituisce la superclasse più comune per gli stub dei client. Quando viene creato un oggetto di tale classe, il costruttore presenta un parametro ref di tipo

java.rmi.server.RemoteRef; quest’ultima è un’interfaccia che descrive un riferimento remoto.

Dunque, un oggetto RemoteStub usa un riferimento (ref) per fare una

invocazione di metodo su un oggetto remoto.

L’implementazione dei metodi presenti in RemoteRef è fornita dalla classe sun.rmi.server.UnicastRef; quest’ultima, in particolare, contiene

l’implementazione del metodo invoke(), che viene usato per invocare un

(11)

di una chiamata a invoke() nella prima riga successiva al blocco try dell’implementazione del metodo getMessage(int). I parametri passati al metodo invoke() sono rispettivamente, l’oggetto che contiene il riferimento all’oggetto remoto (quindi l’oggetto RemoteStub), il metodo che deve essere

invocato sull’oggetto remoto, la lista dei parametri di tale metodo e un numero hash che può essere usato per rappresentare il metodo.

Quando viene effettuata una chiamata a invoke(), le azioni intraprese sono le seguenti:

• Si apre una nuova connessione di rete, sfruttando le informazioni contenute in ref. Quest’ultimo rappresenta il riferimento di rete vero e proprio all’oggetto che implementa il servizio sul lato server. Più precisamente, l’informazione contenuta in ref è divisa in due parti: un indirizzo IP dell’host server insieme col numero di porta su cui questo accetta le richieste e un identificatore dell’oggetto remoto, il quale è specificato da un’istanza della classe java.rmi.server.ObjID. La

nuova connessione di rete è gestita mediante la classe

sun.rmi.transport.tcp.TCPConnection, che viene usata dalla classe sun.rmi.transport.StreamRemoteCall, la quale provvede a

fornire gli stream (di input e di output) necessari ad effettuare la comunicazione con il server. Queste ultime due classi appartengono ovviamente al “livello Trasporto” dell’architettura;

• Si esegue il marshal dei parametri del metodo da eseguire, utilizzando il metodo marshalValue(), quindi si pongono sullo stream di uscita

associato alla connessione.

• Si invoca il metodo executeCall di StreamRemoteCall per leggere

un byte di ritorno che indica se il metodo remoto è stato eseguito correttamente (valore del byte:TransportConstant.NormalReturn) oppure se è stata sollevata un’eccezione (valore del byte:

(12)

• Si esegue l’unmarshal sul risultato ricevuto, utilizzando il metodo

unmarshalValue(); • Si chiude la connessione.

Durante l’esecuzione del metodo invoke() viene quindi usato un oggetto (call) della classe StreamRemoteCall, la quale funge in pratica da

interfaccia tra il livello “Riferimento Remoto” e il livello ”Trasporto”. In particolare, quando si invoca il costruttore di tale classe, viene creato uno header da inviare al server, ponendolo sullo stream di uscita. L’header è costituito dalla costante TransportConstants.Call, che indica l’inizio di un’invocazione di metodo remoto, da un identificatore (id), che identifica l’oggetto remoto da invocare, da un numero intero (op), che identifica univocamente il metodo remoto da invocare e da un long integer (hash) usato per effettuare ricerche hash relativamente allo stub.

Gli stream, attraverso i quali vengono inviati i parametri e viene letto il risultato, sono ottenuti da oggetti appartenenti alle classi

ConnectionInputStream e ConnectionOutputStream del package

sun.rmi.transport, che a loro volta estendono le classi MarshalInputStream e MarshalOutputStream (del package sun.rmi.server), derivate anch’esse dalle classi ObjectInputStream e ObjectOutputStream del package java.io.

Nella figura 4.2 seguente vengono rappresentate schematicamente le principali classi coinvolte nell’esecuzione di un metodo remoto, relativamente al lato client, mentre nella successiva figura 4.3 viene fornito un “sequence diagram” (in notazione UML) che mostra l’interazione tra di esse.

(13)

Figura 4.2 – Lato client dell’architettura di RMI: classi e oggetti coinvolti nell’esecuzione di un metodo remoto

(14)

Figura 4.3 – Sequence Diagram delle principali classi (lato client) coinvolte nell’invocazione di metodo remoto.

4.2.2 Lato Server

Quando un oggetto remoto viene esportato, cioè viene reso disponibile a ricevere chiamate da parte dei client, esso si mette in attesa su una determinata porta; questa attesa viene eseguita da un thread che è creato da un’istanza della classe sun.rmi.transport.tcp.TCPTransport (ovviamente siamo al livello ”Trasporto”), mediante il metodo exportObject().

All’arrivo di una richiesta di connessione da un client, viene creato un ulteriore thread per gestire la connessione: questo thread è implementato con la classe ConnectionHandler, che è a sua volta una classe interna di

TCPTransport. Le azioni eseguite dal thread sono le seguenti:

• Crea gli oggetti TCPConnection e StreamRemoteCall mediante i quali viene effettuata la comunicazione con il client;

(15)

• Legge le informazioni preliminari inviate dal client, ovvero lo header e l’ObjID dell’oggetto locale che definisce i metodi;

• Attraverso la chiave costituita dall’ObjID, preleva da una tabella locale

degli oggetti (la ObjectTable) il riferimento all’oggetto UnicastServerRef che costituisce il livello “Riferimento Remoto” del lato server. La ObjectTable è condivisa fra tutte le possibili istanze del thread;

• Demanda ogni successiva operazione al livello superiore, invocando il metodo dispatch() della classe UnicastServerRef posta nel

package sun.rmi.server.

Il metodo dispatch() riceve come parametri di ingresso il riferimento

all’oggetto remoto interessato dalla chiamata (riferimento che è stato prelevato dalla ObjectTable al livello “Trasporto”) e lo stream attraverso il quale continuare la comunicazione con il client. In particolare, tale metodo si occupa di leggere l’header ricevuto sullo stream di ingresso per ricavare da esso la chiave per individuare il metodo da invocare all’interno della methodTable. Successivamente legge i parametri dallo stream ottenuto dalla classe

MarshalInputStream e ne esegue l’unmarshal, prima di invocare il metodo remoto. Il risultato del metodo invocato dovrà poi essere restituito al client tramite un nuovo stream ottenuto con una chiamata al metodo

getResultStream() di StreamRemoteCall. In particolare, se il metodo è stato eseguito correttamente il risultato verrà preceduto dal byte

TransportConstant.NormalReturn, altrimenti verrà preceduto dal byte TransportConstant.ExceptionalReturn.

Nella figura 4.4 seguente vengono rappresentate schematicamente le principali classi coinvolte nell’esecuzione di un metodo remoto, relativamente al

(16)

lato server, mentre nella successiva figura 4.5 viene fornito un “sequence diagram” (in notazione UML) che mostra l’interazione tra di esse.

Figura 4.4 - Lato server dell’architettura di RMI: classi e oggetti coinvolti nell’esecuzione di un metodo remoto

(17)

Figura 4.5 – Sequence Diagram delle principali classi (lato server) coinvolte nell’invocazione di metodo remoto.

4.3 Garbage Collection di oggetti

remoti

Il meccanismo per la garbage collection distribuita permette l’eliminazione degli oggetti remoti che non sono più riferiti da alcun client e si basa sul reference counting : ogni riferimento remoto che entra in una JVM viene registrato in opportune strutture dati, e si tiene traccia della sua utilizzazione. Sul lato server di ogni oggetto remoto viene mantenuta una lista dei client che vi fanno riferimento; l’oggetto remoto può essere eliminato solo quando la lista diventa vuota. Più precisamente, in tale situazione RMI passa a utilizzare una

weak-reference (riferimento debole) per individuare l’oggetto. Questo autorizza il

sistema locale (non quello distribuito) di garbage collection a eliminare l’oggetto nel momento in cui esso non venga più riferito neppure localmente.

(18)

4.3.1 Lato client

Il sistema di garbage collection distribuita prevede un lato client (che agisce sulla JVM che possiede un riferimento a un oggetto remoto) e un lato server (che agisce sulla JVM dove l’oggetto remoto risiede). Il lato client è implementato con la classe DGCClient, le cui caratteristiche principali sono

riportate nella seguente porzione di codice:

final class DGCClient {

private static long nextSequenceNumber = Long.MIN_VALUE;

private static VMID vmid = new VMID();

//...

static void registerRefs(Endpoint ep, List refs) { //...

}

private static synchronized long getNextSequenceNum() { //...

}

private static long computeRenewTime(long grantTime, long duration) { //...

}

private static class EndpointEntry { private Endpoint ep;

private Map refTable = new HashMap(); private Map invalidRefs = new HashSet(); private Thread renewCleanThread;

private Set pendingCleans = new HashSet(); //...

private static Map endpointTable = new HashMap(); //...

public static EndpointEntry lookup(Endpoint ep) {

//... }

(19)

private void makeDirtyCall(Set refEntries, long sequenceNum) {

//... }

private class RenewCleanThread implements Runnable {

//... }

private void makeCleanCalls() {

//... }

} }

Il campo VMID contiene un identificatore associato univocamente ad ogni

lato client del DGC (Distributed Garbage Collector). Quando un riferimento remoto, ovvero un oggetto della classe LiveRef, entra nella JVM, tale riferimento viene registrato presso il DGCClient attraverso il metodo registerRefs(). Questo metodo ha come parametri l’Endpoint1 del

riferimento remoto e una lista comprendente, oltre al riferimento stesso, tutti i riferimenti remoti che sono entrati nella JVM insieme ad esso e che sono relativi allo stesso Endpoint.

La registrazione di un riferimento remoto presso il DGCClient comporta

il suo inserimento in una lista che è relativa a un particolare Endpoint, ed è implementata con il campo refTable della classe interna EndpointEntry; il

DGCClient mantiene un’istanza di questa classe interna per ciascun Endpoint

in relazione con il client locale. Nel campo statico endpointTable è

memorizzata infatti una tabella che associa ogni Endpoint alle rispettive EndpointEntry.

1 Si ricorda che l’Endpoint, insieme all’ObjID, è una delle informazioni contenute in un LiveRef. Se gli

Endpoint (che sono istanze dell’interfaccia così denominata) sono implementati con la classe

TCPEndpoint, come accade in JavaRMI, le informazioni che essi comprendono sono l’indirizzo IP di un host e un numero di porta.

(20)

Quando un nuovo riferimento remoto è stato registrato, si deve effettuare una chiamata al metodo remoto dirty() sul lato server del DGC. Il valore di ritorno della chiamata rappresenta un intervallo temporale, detto lease time, durante il quale è garantito che il server non eliminerà l’oggetto remoto riferito. Da questo momento in poi, il client dovrà ripetere le chiamate al metodo

dirty() prima di ogni scadenza del lease time, fino a che non avrà più bisogno

di quel riferimento remoto. Se il client non rinnova il lease time prima della scadenza, il DGC assume che l’oggetto remoto non sia più riferito dal client in questione. Le chiamate periodiche al metodo dirty() vengono effettuate dal

thread implementato con il campo renewCleanThread, che fa parte della EndpointEntry e viene inizializzato con un’istanza della classe omonima RenewCleanThread.

Può accadere che per qualche ragione una chiamata a dirty() fallisca. In tal caso tutti i riferimenti contenuti nella refTable vengono duplicati nella lista

invalidRefs in modo tale che vengano inclusi nella prima chiamata utile al

metodo dirty().

Il metodo makeDirtyCall() permette di effettuare una chiamata al

metodo dirty() sul server, specificando nel parametro refEntries gli oggetti remoti interessati e precisando un determinato numero di sequenza. Questo numero viene associato a ogni chiamata per consentire al lato server di identificare e scartare le eventuali chiamate a dirty() giunte in ritardo.

Quando un client localmente non ha più riferimenti a un dato oggetto remoto, deve effettuare una chiamata al metodo remoto clean() sul lato server,

segnalando così che il DGC può eliminare il client dalla lista di quelli che riferiscono l’oggetto remoto in questione. Anche le invocazioni del metodo

clean() vengono compiute dal renewCleanThread. In tali occasioni, il thread si comporta nella seguente maniera:

(21)

• Controlla se ci sono riferimenti remoti non più utilizzati e, se ne trova, li inserisce nella lista pendingCleans;

• Invoca il metodo dirty() per tutti i riferimenti remoti relativi alla EndpointEntry alla quale appartiene;

• Invoca il metodo clean() per tutti i riferimenti remoti inseriti nella lista pendingCleans.

(22)

1.4.2 Lato server

L’implementazione del lato server del DGC è affidata alla classe

DGCImpl. Come accade anche per RegistryImpl, lo stub relativo a questa classe è presente in ogni JVM, mentre l’ObjID è fisso e pari a una costante nota (DGC_ID).

final class DGCImpl implements DGC{ //...

private static final long leaseValue =

getLong(“sun.rmi.dgc.leaseValue”, 600000); //...

private Hashtable leaseTable = new Hashtable(); private Thread checker;

//...

public Lease dirty(ObjID[] ids,long sequenceNum, Lease lease){ //...

}

public void clean(ObjID[] ids, long sequenceNum, VMID vmid, boolean strong) { //...

}

void registerTarget(VMID vmid, Target target) { //...

}

void unregisterTarget(VMID vmid, Target target) { //...

}

private boolean checkLeases() { //...

} //... }

(23)

La costante leaseValue fornisce il lease time che viene concesso al client in seguito a una chiamata a dirty(). Il valore di default è di 10 minuti, ma può essere modificato al momento di lanciare la JVM, aggiungendo alla riga di comando la seguente opzione:

java -Djava.rmi.dgc.leaseValue=10000 ...

In questo esempio il valore del lease time viene portato a 10 secondi.

Il metodo dirty() ha tre parametri di ingresso. Il parametro ids può rappresentare gli ObjID degli oggetti remoti per i quali si sta eseguendo il metodo; il parametro contiene effettivamente valori solo nel caso in cui la chiamata a dirty() venga fatta per un riferimento remoto appena registrato, oppure nel caso in cui ci siano chiamate fallite in precedenza; sequenceNum è il numero di sequenza che serve a scartare le chiamate in ritardo. Il terzo parametro

lease contiene il VMID del client e un certo valore per il lease time. Questo

valore però viene ignorato dal server, che in uscita fornisce il proprio valore (che eventualmente può essere stato modificato dalla linea di comando, come si è detto sopra). Le operazioni che compie il metodo dirty() sono le seguenti:

• Aggiunta del VMID del client (se è nullo, ne viene creato uno appositamente) alla leaseTable, assieme al riferimento al Target

dell’oggetto remoto riferito dal client.

• Determinazione del lease time (praticamente viene posto uguale a

leaseValue) e sua eventuale memorizzazione nella leaseTable;

• Avvio del thread checker, che controlla la scadenza del lease time.

Quando un lease viene trovato scaduto, il VMID del client viene rimosso dalla leaseTable e dalla lista dei client che riferiscono l’oggetto remoto; • Aggiunta del VMID del client alla lista refSet (che fa parte dell’oggetto

(24)

Figura 4.7 – Struttura delle classi componenti il lato server del DGC

Per quanto riguarda il metodo clean(), i parametri di ingresso sono

quattro: ids che contiene gli ObjID degli oggetti remoti non più riferiti dal client, il quale è specificato tramite il parametro vmid; sequenceNum che permette di identificare chiamate ritardate e, infine, strong che informa il lato server del DGC che questa chiamata a clean() è conseguenza di una chiamata a

dirty() fallita precedentemente, per cui il sequenceNum del client vmid deve

essere mantenuto all’interno della lista sequenceTable contenuta nell’oggetto Target, in modo da poter riconoscere la futura (ed eventuale) chiamata in ritardo a dirty().

Come conseguenza di una chiamata a clean(), il VMID specificato viene rimosso dalla leaseTable e dalla lista refSet del Target corrispondente.

Figura

Figura 4.1 – Invocazione di metodo remoto.
Figura 4.2 – Lato client dell’architettura di RMI: classi e oggetti coinvolti   nell’esecuzione di un metodo remoto
Figura 4.3 – Sequence Diagram delle principali classi (lato client) coinvolte   nell’invocazione di metodo remoto
Figura 4.4 - Lato server dell’architettura di RMI: classi e oggetti coinvolti   nell’esecuzione di un metodo remoto
+4

Riferimenti

Documenti correlati

• Lo stato del metodo, cioè la documentazione di attivazione (activation record), viene allocato sulla pila di esecuzione (run-time stack) e un puntatore (stack pointer)

Trieste, 7 settembre 2009 1 Metodi Matematici per

Un file di testo contiene i dati sulle precipitazioni (in millimetri) relativi a ciascun mese di svariati anni..

[r]

[r]

Questo approccio favorisce la portabilità degli elementi - che come abbiamo visto è un elemento delle specifiche alla base di Projectmoon System - la leggerezza, la manteni- bilità e

30 Illustra, con estrema chiarezza, i due approcci, L. PRATI, Il mancato impedimento di illeciti ambientali e la responsabilità per omissione, cit., p. Puccini, in

Molti studi hanno già dimostrato che l’attuale pandemia COVID-19 sia attribuibile al cosiddetto salto di specie o spillover, ossia il passaggio di un virus da una specie