Prep esame · Assignment 4

Assignment 4 — Alarm distribuito + RMI TTT

~20 min di letturapagina di preparazione all'orale

In questa pagina

  1. Cosa ho costruito
  2. Scelte concorrenti
  3. Domande probabili
  4. Punti deboli

1. Cosa ho costruito

Stato reale

Al momento la soluzione non esiste. Nel repository pcd-2025-2026 la cartella assignments/assignment-04/ contiene solo il README.md del docente (v1.0.0-20260517): nessuno dei tre esercizi è stato implementato, non c'è una cartella src né la relazione doc/report.pdf. Questa pagina descrive quindi cosa va costruito e da quale materiale di laboratorio partire, non un'architettura già realizzata. Va aggiornata appena il codice esiste.

L'assignment riguarda la programmazione distribuita e chiede tre esercizi (il terzo opzionale, ma obbligatorio per puntare al 30L), più il consueto deliverable Assignment-04 con src e doc/report.pdf (analisi del problema dal punto di vista concorrente + strategia adottata).

1.1 Esercizio 1 — Distributed Smart Home Alarm (Pekko Cluster) — assente

Non implementato. La specifica non è nel README del corso ma nel repository del tutor N. Farabegoli (seminar-pcd-actor-pekko-code, file assignment_4_smart_home_alarm_cluster.md), lo stesso repository del seminario su Pekko distribuito presentato nella lab activity 12 (lab-activities/lab-activity-12/README.md rimanda a slide e codice esterni; non ci sono sorgenti locali). L'idea: un sistema d'allarme domotico i cui componenti (sensori, unità di controllo, interfaccia) sono attori distribuiti su più nodi di un Pekko Cluster. La base concettuale è Cap. 14 — Attori avanzati (cluster, membership, location transparency) sopra il modello ad attori di Cap. 13.

1.2 Esercizio 2 — Distributed Tic-Tac-Toe con Java RMI — assente

Non implementato. Requisiti: un giocatore può creare una partita con un nome e attendere avversari, oppure fare join a una partita esistente dato il nome; progettazione secondo i principi del distributed object computing con Java RMI come meccanismo RPC. La base di partenza locale è la lab activity 13, package pcd.lab13.rmi in lab-activities/lab-activity-13/pcd-lab-13/:

Il README del lab ricorda anche il dettaglio operativo: lanciare rmiregistry dalla directory del classpath (target/classes) perché il registry deve vedere le classi degli oggetti remoti.

1.3 Esercizio 3 (opzionale, 30L) — Sezioni critiche distribuite su MOM — assente

Non implementato. Richiede un piccolo middleware che offra sezioni critiche a processi distribuiti, tale che un processo lo usi senza sapere nulla degli altri processi coinvolti, costruito sopra un MOM come RabbitMQ. La base locale è il package pcd.lab13.rabbitmq della lab activity 13: Test0_Send.java/Test0_Recv.java (coda semplice), Test1_MultipleSend.java/Test1_Receive.java (work queue con più consumatori), Test2_Publisher.java/Test2_Subscriber.java (fanout), Test3_* e Test4_* (routing direct e topic). Il broker si avvia via Docker (rabbitmq:4-management, come da README del lab). La teoria di riferimento è Cap. 16 — Algoritmi distribuiti (mutua esclusione distribuita).

1.4 Relazione — assente

Manca anche doc/report.pdf, parte del deliverable e tipicamente la prima cosa che il docente apre all'orale.

2. Scelte concorrenti

Non essendoci ancora codice, queste sono le scelte di progetto che andranno compiute — e che all'orale andranno difese. Per ciascuna è indicato il capitolo di teoria che la copre.

2.1 Alarm su cluster: topologia di attori e membership

Le decisioni chiave: quali componenti diventano attori (un attore per sensore? un attore di zona? un controller unico?), su quali nodi girano, e come il sistema reagisce a nodi che entrano ed escono dal cluster. Un controller centrale è naturale ma è un single point of failure: in Pekko Cluster lo si gestisce con membership e failure detection (gossip, nodi unreachable) e, se serve unicità, con un singleton di cluster. Da difendere anche il protocollo di messaggi (immutabili, serializzabili — nel distribuito la serializzazione non è più un dettaglio) e il fatto che tra nodi non c'è memoria condivisa né clock globale: valgono i limiti dei sistemi distribuiti di Cap. 15. Teoria: Cap. 14 e Cap. 13.

2.2 TTT su RMI: chi protegge lo stato della partita?

Il punto concorrente meno visibile dell'esercizio 2: il runtime RMI può servire più invocazioni remote in parallelo su thread diversi — non c'è alcuna garanzia di single-thread sull'oggetto remoto. Lo stato di una partita (griglia, turno, esito) è quindi stato condiviso a tutti gli effetti: l'oggetto remoto che lo incapsula va progettato come monitor (metodi synchronized o lock espliciti, invarianti su turno e terminazione), riportando dentro un esercizio "distribuito" i temi di Cap. 4 e Cap. 7. È l'analisi del flusso di controllo che il README del lab 13 chiede esplicitamente per example_01 ("Which control architecture? Concurrency? Races?"). Altre scelte: un oggetto remoto "lobby" per create/join per nome (sopra il naming del registry) che restituisce l'oggetto remoto partita; notifica delle mosse via listener remoti (pattern di example_02) invece del polling; gestione di RemoteException come parte del contratto. Teoria: Cap. 17.

2.3 Il pattern Proxy nei middleware distribuiti (indicazioni del docente)

Nella lezione conclusiva del modulo il professore è tornato sull'Assignment 4 con indicazioni pratiche. Il pattern proxy è centrale in entrambi i paradigmi dell'assignment: negli attori (ogni attore ha un indirizzo e comunica solo tramite messaggi asincroni, con un proxy che gestisce l'invio — l'ActorRef) e in RMI (lo stub è un proxy che serializza le chiamate a metodo in messaggi di rete). Il proxy è responsabile di:

Utile anche la progressione storica richiamata dal professore: si parte dalle Remote Procedure Call (RPC), dove chiamiamo funzioni su nodi remoti come se fossero locali; con Java RMI si passa alle Remote Method Invocation, che aggiungono la dimensione object-oriented — non chiamiamo più funzioni ma invochiamo metodi su oggetti che risiedono in JVM remote. È la visione del distributed object computing classico (Cap. 17), su cui va modellato il TTT.

Per l'esame

Il professore raccomanda di pensare in modo critico alla progettazione: "nel pensare a come progettare lo sviluppo ci sono dei principi che abbiamo visto nel corso importanti e che vengono messi da una descrizione come questa. Se noi diciamo componenti attivi, passivi, incapsulamento del flusso di controllo, sono tutte questioni che non è detto che l'AI subito attacchi, anzi." Il consiglio è di programmare toccando con mano e fare scelte consapevoli — all'orale si difendono scelte proprie, non codice generato.

2.4 Sezioni critiche su MOM: dove sta il coordinatore?

Il requisito "un processo non deve sapere nulla degli altri" orienta la scelta dell'algoritmo di Cap. 16: Ricart-Agrawala richiede che ogni processo conosca (e contatti) tutti gli altri — 2(N−1) messaggi per accesso — quindi mal si adatta; l'algoritmo centralizzato invece si mappa naturalmente sul MOM: il broker disaccoppia i processi (disaccoppiamento spaziale e temporale, Cap. 12), una coda delle richieste serializza gli accessi, un coordinatore (o la semantica stessa della work queue con un solo token/consumer) concede la sezione critica e riceve i release. L'API del middleware verso il processo utente è la parte "proxy" del progetto (§2.3): acquire()/release() bloccanti localmente, tradotti in publish/consume RabbitMQ sotto il cofano, riusando i pattern di Test0_*Test3_* del lab 13. Da discutere onestamente: il broker (e il coordinatore) restano un punto centrale di guasto.

2.5 Fallimenti parziali: la differenza vera col concorrente locale

In tutti e tre gli esercizi la novità rispetto agli assignment precedenti è il fallimento parziale: un nodo del cluster diventa unreachable, un client TTT sparisce a metà partita (la RemoteException arriva a chi chiama, ma il server non se ne accorge finché non prova la callback), un processo muore dentro la sezione critica senza fare release. In un sistema asincrono non si distingue un processo lento da uno caduto (Cap. 16, Cap. 15): servono timeout e regole esplicite (partita abbandonata, lease sulla sezione critica), da dichiarare nella relazione.

3. Domande probabili

Cos'è lo stub in Java RMI e perché si dice che è un proxy?

È l'oggetto locale che implementa l'interfaccia remota lato client: intercetta la chiamata a metodo, serializza (marshalling) nome del metodo e parametri, li invia via rete alla JVM remota, attende ed effettua l'unmarshalling del risultato. È un'istanza del pattern Proxy: stesso contratto dell'oggetto vero, con la comunicazione di rete nascosta dietro. Lo stesso ruolo lo ha l'ActorRef nel mondo attori (§2.3). Riferimento: Cap. 17.

Perché ogni metodo di un'interfaccia Remote deve dichiarare throws RemoteException?

Perché la trasparenza della distribuzione non è (né può essere) completa: la rete può fallire, il nodo remoto può essere caduto, la chiamata può non arrivare mai. RemoteException rende il fallimento parziale parte del contratto: il chiamante è costretto a gestirlo. È visibile in MyService.java del lab 13, dove anche un banale getSum(int,int) la dichiara.

In RMI i parametri passano per valore o per riferimento? Come lo userebbe il TTT?

Entrambi: gli oggetti non remoti passano per valore (serializzati e copiati, come Message in example_01 — la copia remota è indipendente); gli oggetti che implementano Remote passano per riferimento: viaggia lo stub, non l'oggetto (come RemoteCounterListener in example_02). Nel TTT: la mossa e lo stato della griglia passano per valore; il listener del giocatore (per ricevere le mosse dell'avversario) passa per riferimento remoto.

Chi invoca i metodi del vostro oggetto remoto? Ci possono essere race condition in un server RMI?

Sì: il runtime RMI serve le invocazioni su thread propri e può eseguire più chiamate concorrenti sullo stesso oggetto remoto — nessuna garanzia di serializzazione. Se due client fanno join sulla stessa partita o due mosse arrivano insieme, lo stato condiviso va protetto: l'oggetto partita si progetta come monitor (Cap. 7), con invarianti su turno e terminazione. È l'analisi del flusso di controllo chiesta dal README del lab 13.

Come realizzate "creare una partita con un nome" e "join per nome" con RMI?

Con un oggetto remoto di lobby pubblicato sul registry (LocateRegistry/rebind, come RunServerSide del lab): createGame(name) registra una nuova partita e restituisce (per riferimento remoto) l'oggetto partita; joinGame(name) lo cerca e vi aggiunge il secondo giocatore. Il registry risolve solo il bootstrap (trovare la lobby); il naming delle partite vive nella lobby, che essendo condivisa va anch'essa sincronizzata (due createGame con lo stesso nome).

Come fa un giocatore a sapere che l'avversario ha mosso, senza polling?

Con il pattern observer remoto di example_02: il client esporta un proprio oggetto remoto listener (analogo a MyCounterListenerImpl) e lo registra sulla partita (addListener in Counter.java); a ogni mossa il server invoca la callback (countIncremented → qui moveMade(...)) su tutti i listener. Attenzione: la callback è a sua volta una chiamata remota che può fallire o bloccare — meglio invocarla fuori dal lock della partita, o da un thread dedicato.

A cosa serve rmiregistry e perché nel lab va lanciato dalla directory target/classes?

È il servizio di naming: associa nomi simbolici a stub di oggetti remoti, così il client fa il bootstrap con lookup senza conoscere riferimenti a priori. Nel lab va lanciato dal classpath dei .class perché il registry deve poter caricare le classi (stub/interfacce) degli oggetti che pubblica — dettaglio operativo annotato nel README della lab activity 13.

Perché un cluster di attori è un buon modello per lo smart home alarm?

Perché il dominio è già a componenti attivi che comunicano ad eventi: sensori che notificano, un controllo che reagisce. Gli attori danno incapsulamento dello stato (niente memoria condivisa da proteggere), messaggi asincroni come unico mezzo di interazione, e location transparency: lo stesso protocollo funziona in locale e tra nodi. Il cluster aggiunge membership e failure detection per gestire nodi che entrano/escono. Riferimenti: Cap. 13, Cap. 14.

Qual è la differenza fondamentale tra Java RMI e un MOM come RabbitMQ?

RMI è distributed object computing: chiamate a metodo tipicamente sincrone, proxy/stub, trasparenza oggettuale, accoppiamento diretto chiamante-chiamato. Un MOM è message-oriented: messaggi asincroni via code e exchange, publish/subscribe, disaccoppiamento spaziale (non so chi riceve) e temporale (il destinatario può non essere attivo ora). L'assignment fa usare entrambi apposta: TTT a oggetti remoti, sezioni critiche a messaggi. Riferimenti: Cap. 17, Cap. 12.

Perché non si può usare un lock o un semaforo "normale" per la mutua esclusione tra processi distribuiti?

Perché i meccanismi dei capitoli 4–7 presuppongono memoria condivisa (e un clock/scheduler comune): tra nodi distinti non c'è né l'una né l'altro, e nemmeno rilevamento affidabile dei guasti. La mutua esclusione va ricostruita con soli messaggi: è il tema di Cap. 16 (algoritmo centralizzato, Ricart-Agrawala) ed esattamente ciò che l'esercizio 3 chiede di incapsulare in un middleware.

Per l'esercizio 3, algoritmo centralizzato o Ricart-Agrawala? Perché?

Ricart-Agrawala è decentralizzato e ottimale nei messaggi (2(N−1) per accesso) ma ogni processo deve conoscere e contattare tutti gli altri — contro il requisito "senza sapere nulla degli altri processi". Con un MOM è naturale il centralizzato: una coda di richieste sul broker, un coordinatore che concede il permesso e riceve i release; i processi conoscono solo il nome della coda. Trade-off da dichiarare: coordinatore e broker come punto centrale di guasto e collo di bottiglia (Cap. 16).

Come mappereste acquire/release della sezione critica sui costrutti RabbitMQ del lab 13?

Idea base: acquire() pubblica una richiesta su una coda (pattern Test0_Send/Test1_MultipleSend) con una reply-queue privata su cui attendere il grant in modo bloccante; release() pubblica il rilascio; il coordinatore consuma le richieste in ordine FIFO e concede una grant per volta. In alternativa, un unico messaggio-token in una coda: acquire = consumarlo, release = ripubblicarlo (attenzione ad ack e crash del detentore). Il processo utente vede solo l'API del middleware: è il proxy del §2.3.

Cosa succede se un processo crasha dentro la sezione critica (o un giocatore sparisce a metà partita)?

È la domanda sui fallimenti parziali. Nel sistema asincrono non si distingue lento da morto (Cap. 16): senza contromisure la sezione critica resta occupata per sempre. Risposte pragmatiche: timeout/lease sul grant, ack RabbitMQ non confermati che rimettono il token in coda, e nel TTT la RemoteException sulla callback come segnale per dichiarare la partita abbandonata. L'importante è mostrare di aver scelto una semantica e conoscerne i limiti (un timeout può revocare la CS a un processo solo lento).

Ripercorrete la progressione RPC → RMI → distributed object computing.

RPC: chiamare procedure su nodi remoti come fossero locali, con proxy/stub per marshalling e unmarshalling. RMI aggiunge la dimensione object-oriented: si invocano metodi su oggetti che vivono in JVM remote, con riferimenti remoti passabili come parametri. Il distributed object computing è la visione complessiva: oggetti che interagiscono per chiamate di metodo attraverso la rete. Limite noto: la trasparenza è parziale — latenza e fallimenti restano (per questo RemoteException ovunque). Riferimento: Cap. 17, e la lezione conclusiva del docente (§2.3).

4. Punti deboli

Prima di tutto

Il punto debole principale è che l'assignment non è stato ancora svolto: nessuno dei tre esercizi esiste nel repository, né la relazione. All'orale la discussione è costruita sul proprio codice: senza deliverable non c'è discussione. La priorità è implementare, non ripassare.