Prep esame · Assignment 3

Assignment 3 — Smart Home Alarm + Odds-and-Evens

~15 min di letturascheda di preparazione all'orale

In questa scheda

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

1. Cosa ho costruito

Stato reale del repository

Nel repository del corso la cartella assignments/assignment-03/ contiene solo la traccia (README.md, v2.0.0-20260506). Non c'è nessuna directory src, nessun sorgente Pekko o Go e nessun doc/report.pdf. Anche la storia git conferma che per questo assignment è stato committato soltanto il README. Se il deliverable è stato consegnato a parte (zip sul sito del corso), va recuperato e riletto prima dell'orale; qui sotto ricostruiamo comunque cosa la traccia richiede e su quale materiale di laboratorio, questo sì presente nel repo, possiamo appoggiare la discussione.

Esercizio 1 — Smart Home Alarm System (Apache Pekko): non presente nel repo

La traccia (descrizione di N. Farabegoli, linkata dal README) chiede un sistema di controllo di un allarme domestico modellato ad attori, con cinque stati operativi: Disarmed, Exit Delay (20–60 s dopo l'inserimento, sensori inattivi), Armed, Entry Delay (10–30 s di countdown dopo una rilevazione, il PIN corretto disarma) e Alarm (sirena attiva, solo il PIN la ferma). I componenti richiesti sono un tastierino (keypad) per il PIN, sensori di movimento e porta/finestra, una centralina (control unit) che governa le transizioni di stato e una sirena; i ritardi devono essere configurabili con transizioni automatiche allo scadere. Bonus: zone della casa con inserimento parziale. Di tutto questo nel repository non esiste alcuna implementazione: all'orale non possiamo raccontare classi che non abbiamo scritto qui.

Il materiale Pekko realmente presente nel repo, su cui il progetto andrebbe (o andava) modellato, è lab-activities/lab-activity-11/pcd-lab-11: step1_pingpong (protocollo messaggi come interfaccia sigillata, PingerPongerProtocol.java), step3_multiple_behav/ActorWithBehaviours.java (un behavior per stato, cioè esattamente la macchina a stati dell'allarme), step4_multiple_behav_stash/ActorWithBehavioursAndStashing.java (stash dei messaggi non gestibili nello stato corrente) e step5_gui (attore che dialoga con una GUI Swing).

Esercizio 2 — Odds-and-Evens in Go: non presente nel repo

La traccia chiede un torneo di pari-e-dispari con N = 2m giocatori organizzato in m round: a ogni round le partite corrono concorrentemente, i vincitori avanzano fino alla finale (con m = 3: 8 giocatori, 4 partite, poi 2, poi la finale). Vincolo esplicito: interazione basata solo su message passing, nessuna memoria condivisa. Anche qui: nessun sorgente Go nel repository per questo assignment.

Il codice Go effettivamente nel repo, che è la base naturale della soluzione, è in lab-activities/lab-activity-09/go-conc-examples (goroutine, canali, spawn di massa in ex-04/main.go) e soprattutto in lab-activities/lab-activity-10/go-src/exchanging-values-problem-*: le tre topologie centralized, ring e symmetric viste a lezione. Il pattern della soluzione centralizzata — un Coord che riceve da un canale condiviso in ingresso e risponde a ciascun Peer sul suo canale privato — è lo stesso schema coordinatore/giocatori che serve per arbitrare una partita del torneo. Questo frammento è codice reale di exchanging-values-problem-centralized/main.go:

coord_ch := make(chan int)
channels := make([]chan MinMaxMsg, n_peers)
for i := 0; i < n_peers; i++ {
    channels[i] = make(chan MinMaxMsg)
}
go Coord(n_peers, coord_ch, channels)
for i := 0; i < n_peers; i++ {
    go Peer(i, coord_ch, channels[i])
}

2. Scelte concorrenti

Mancando il codice consegnato nel repo, questa sezione fissa le scelte di design che la traccia impone e che all'orale dovremo saper motivare, ancorandole ai capitoli di teoria e al codice di laboratorio citato sopra. Partiamo dal metodo che il professore stesso ha indicato per affrontare la traccia.

Il metodo del professore: strategia di lettura della traccia

A lezione il professore ha insistito che il punto non è la lettura superficiale del testo: bisogna cercare il protocollo, i ruoli e gli stati, non i dettagli di contorno.

Per l'assignment

Prima di scrivere codice, mappa la traccia in tre cose: chi parla con chi, quali messaggi esistono, e quali invarianti devono restare vere.

1. Leggi la traccia e individua gli attori/ruoli
2. Elenca i messaggi e le risposte possibili
3. Definisci lo stato minimo di ogni actor
4. Disegna i casi limite: ordine, assenza di messaggi, errori
5. Solo dopo passa all'implementazione
Suggerimento pratico

Se la traccia parla di request-reply, buffering o coordinazione, pensa subito in termini di mailbox e transizioni di stato. È il modo più rapido per non perderti.

// schema mentale minimo
Actor client = ...;
Actor server = ...;

client.send(server, "request", id, payload);
// server decide se rispondere subito, accodare o cambiare stato

Applicato all'assignment: per l'allarme i ruoli sono keypad, sensori, control unit e sirena; i messaggi sono eventi dei sensori, digitazioni del PIN e comandi alla sirena; l'invariante è che la sirena suona solo nello stato Alarm e che ogni transizione passa dalla validazione del PIN o dallo scadere di un timer. Per il torneo i ruoli sono giocatori (e opzionalmente arbitri), i messaggi sono le mosse e gli esiti, l'invariante è che a ogni round avanza esattamente metà dei partecipanti.

Esercizio 1 — scelte da sostenere

Esercizio 2 — scelte da sostenere

3. Domande probabili

Domande plausibili all'orale su questo assignment, con schema di risposta. Le risposte presuppongono di aver completato (o di completare) la soluzione secondo il design della sezione 2.

Perché la traccia divide i due esercizi in message passing "sincrono" (Go) e "asincrono" (attori)?

È la distinzione del README stesso: in Go i canali non bufferizzati realizzano comunicazione sincrona in stile CSP (mittente e ricevente si bloccano fino al rendez-vous); nel modello ad attori la tell è asincrona e non bloccante, i messaggi finiscono in una mailbox e vengono processati uno alla volta. La sincronizzazione in Pekko è un pattern (request-reply, ask) costruito sopra l'asincronia, non una primitiva.

Come rappresenti i cinque stati dell'allarme in Pekko Typed?

Un behavior per stato: Disarmed, ExitDelay, Armed, EntryDelay, Alarm sono cinque funzioni che restituiscono Behavior<Command>; la transizione è il valore di ritorno dell'handler (come in ActorWithBehaviours.java del lab 11). Lo stato immutabile (PIN, configurazione ritardi) si passa come parametro del behavior, mai come campo mutabile condiviso.

Come implementi exit delay ed entry delay? Cosa succede se il PIN arriva prima dello scadere?

Behaviors.withTimers + startSingleTimer con un messaggio di timeout dedicato: allo scadere l'attore riceve il messaggio e transisce (ExitDelay→Armed, EntryDelay→Alarm). Se il PIN corretto arriva prima, l'handler cancella il timer (o usa la stessa chiave di timer, che sovrascrive) e transisce a Disarmed: il messaggio di timeout non deve più produrre effetti.

Differenza tra startTimerAtFixedRate e startTimerWithFixedDelay: quale serve qui?

Fixed rate mantiene l'intervallo tra gli inizi delle attivazioni; fixed delay attende la fine dell'elaborazione precedente. Per i ritardi dell'allarme serve però startSingleTimer: è un timeout one-shot, non un tick periodico. Un timer periodico servirebbe semmai per un heartbeat dei sensori o per il lampeggio della sirena.

Un evento sensore arriva mentre l'allarme è Disarmed o in Exit Delay: che fai?

La traccia lo dice esplicitamente: in Disarmed ed Exit Delay gli eventi si ignorano o si loggano; in Armed fanno partire l'Entry Delay; in Alarm sono ridondanti (l'allarme è già scattato). È la dimostrazione che la semantica dipende dallo stato, ed è il motivo per cui lo stato è codificato nel behavior e non in un if.

A cosa serve lo stash in questo progetto?

A non perdere né gestire fuori contesto i messaggi che nello stato corrente non hanno senso: ad esempio digitazioni del keypad che arrivano mentre la centralina sta completando una transizione. Si accumulano con stash() e si riconsegnano con unstashAll() al passaggio al behavior giusto, come nello step 4 del lab 11. L'alternativa (scartarli) va motivata caso per caso.

Perché nel modello ad attori non servono lock, e cosa garantisce l'assenza di race?

Ogni attore processa un messaggio alla volta dalla propria mailbox e non condivide stato: l'unico modo di interagire è inviare messaggi immutabili. La mutua esclusione è quindi strutturale (isolamento dello stato), non ottenuta con lock. Le race restano possibili solo a livello di protocollo (ordini di messaggi non previsti), ed è per questo che il professore insiste sui casi limite dell'ordine dei messaggi.

Nel torneo Go, come garantisci che nessun giocatore veda la mossa dell'avversario prima di scegliere la propria?

Con un arbitro per partita: entrambi i giocatori inviano la mossa all'arbitro su canali (fan-in), e solo dopo averle ricevute entrambe l'arbitro calcola pari/dispari e comunica l'esito. Senza arbitro, uno scambio diretto richiede che entrambi inviino prima di leggere, il che su canali sincroni simmetrici causa deadlock: va rotta la simmetria (uno invia e poi riceve, l'altro riceve e poi invia) come nelle topologie del lab 10.

Canali bufferizzati o non bufferizzati? Che differenza fa per la correttezza?

Non bufferizzato: send e receive si bloccano fino al rendez-vous, quindi la comunicazione è anche sincronizzazione (semantica CSP). Bufferizzato: il mittente prosegue finché il buffer non è pieno, disaccoppiando i tempi. Nel torneo i canali non bufferizzati rendono i vincoli di ordine espliciti; un buffer di capacità 2 sul canale dell'arbitro eliminerebbe il blocco dei giocatori sull'invio della mossa, ma la correttezza non deve dipendere dalla capacità.

Come struttureresti l'albero dei round con m generico?

Ricorsivamente o iterativamente: ogni partita ha un canale di uscita "vincitore"; le uscite di un round sono gli ingressi del successivo. Con una funzione che dato un insieme di canali-ingresso costruisce round finché ne resta uno solo, il main fa una sola receive sul canale della finale. La concorrenza tra partite dello stesso round è gratuita: sono goroutine indipendenti senza stato condiviso.

Come termina il programma Go senza goroutine leak?

I perdenti fanno return dopo la notifica dell'esito, gli arbitri terminano dopo aver comunicato il vincitore, il main attende il canale della finale. Non serve né for {} (busy-wait, presente solo negli esempi didattici) né sync.WaitGroup, che tra l'altro sarebbe borderline rispetto al vincolo "solo message passing": la terminazione stessa fluisce lungo i canali.

Goroutine e attori: che differenza c'è tra i due modelli usati nei due esercizi?

Le goroutine comunicano su canali anonimi e tipati per messaggio: il canale è l'entità di prima classe, chi legge non sa chi scrive (CSP). Gli attori hanno identità (ActorRef) e mailbox: si invia a qualcuno, in modo asincrono. Inoltre le goroutine sincronizzano sul rendez-vous, gli attori mai. Stessa filosofia di fondo — nessuna memoria condivisa, "share memory by communicating" — con primitive duali.

Come testeresti/verificheresti la macchina a stati dell'allarme?

Enumerando per ciascuno dei 5 stati la reazione a ogni tipo di messaggio (evento sensore, PIN corretto/errato, timeout): è la matrice stati×messaggi suggerita dalla strategia di lettura del professore. In Pekko si può testare con un probe che impersona sensori e keypad e asserisce i messaggi verso la sirena; i casi limite prioritari sono PIN durante entry delay allo scadere e eventi sensore concorrenti alla transizione.

Il bonus delle zone: come cambierebbe il design?

Lo stato Armed diventa parametrico sull'insieme delle zone attive (inserimento totale o parziale); ogni evento sensore porta la zona di provenienza e la control unit lo filtra: solo le zone attive innescano l'entry delay. Nel design ad attori basta aggiungere il campo zona ai messaggi dei sensori e l'insieme di zone come parametro del behavior Armed — nessun nuovo meccanismo di concorrenza.

4. Punti deboli