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.
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).
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 del problema dello scambio di valori. 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])
}
Mancando il codice consegnato nel repo, questa sezione fissa le scelte di design che la traccia impone e che all'orale vanno motivate, ancorandole ai capitoli di teoria e al codice di laboratorio citato sopra.
Prima di scrivere codice conviene ridurre la traccia a tre cose: chi parla con chi, quali messaggi esistono, quali invarianti devono restare vere.
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.
Behavior distinto restituito dall'handler, come in ActorWithBehaviours.java del lab 11. Niente flag booleani mutabili: lo stato è il behavior corrente. È il cuore del modello ad attori (Cap. 15 — Il modello ad attori).PingerPongerProtocol.java), così il compilatore documenta chi può ricevere cosa e non esiste chiamata diretta tra componenti, solo tell asincrono su mailbox (Cap. 15).startSingleTimer dentro Behaviors.withTimers: allo scadere il timer recapita un messaggio (es. ExitTimeout) che causa la transizione automatica. Un attore è puramente reattivo: i timer sono il modo idiomatico di introdurre pro-attività temporale (Cap. 16 — Attori avanzati: timer e cluster). Cruciale: cancellare il timer se il PIN arriva prima dello scadere.ActorWithBehavioursAndStashing.java (Cap. 15). Non esiste invece un messaggio che arriva «durante una transizione»: l'attore elabora un messaggio alla volta e il nuovo behavior vale dal messaggio successivo. Molti eventi fuori stato, per la traccia, vanno semplicemente ignorati (sensori in Disarmed).ActorRef della centralina. Nessuno stato condiviso: la GUI, se c'è, dialoga via messaggi come in step5_gui/ViewActor.java.ex-04/main.go, ne lancia un milione), quindi 2m giocatori concorrenti non sono un problema di scheduling (Cap. 14 — Message passing e canali).Coord nella versione centralized del lab 10) e comunica il vincitore; l'alternativa peer-to-peer richiede di rompere la simmetria per evitare deadlock, come nel confronto tra le tre topologie (Cap. 14).for {} finale degli esempi di lab è proprio ciò da non replicare: si attende un valore dal canale della finale).Su questo assignment Ricci ha chiesto: come avete strutturato ad alto livello l'assignment; come avete strutturato attori e messaggi nel primo esercizio. La risposta va data sul proprio codice (elenco degli attori, protocollo dei messaggi, behavior per stato); la sezione 2 e le domande qui sotto preparano il ragionamento.
Le risposte presuppongono di aver completato (o di completare) la soluzione secondo il design della sezione 2.
È 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.
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.
Behaviors.withTimers + startSingleTimer con un messaggio di timeout dedicato: allo scadere l'attore riceve il messaggio e cambia stato (ExitDelay→Armed, EntryDelay→Alarm). Se il PIN corretto arriva prima, l'handler cancella il timer (cancel(chiave)) o ne avvia un altro con la stessa chiave, che sostituisce il precedente, e passa a Disarmed. Pekko garantisce che il messaggio di un timer cancellato o sostituito non venga consegnato, anche se era già in mailbox.
Fixed delay garantisce almeno l'intervallo dato tra un invio e il successivo: se un invio slitta (GC, carico), slittano anche i successivi. Fixed rate compensa i ritardi per mantenere nel tempo la frequenza media. In nessuno dei due casi il timer aspetta che l'attore abbia elaborato il messaggio 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.
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 non perdere messaggi che nello stato corrente non si possono ancora servire ma avranno senso più avanti: per esempio, se nel design la centralina aspetta una conferma (dalla sirena o da un altro componente) prima di completare il cambio di stato, i comandi che arrivano nel frattempo si accumulano con stash() e si riconsegnano con unstashAll() nel nuovo behavior, come nello step 4 del lab 11. Non esiste un «durante la transizione»: l'attore elabora un messaggio alla volta. Stash o scarto va deciso caso per caso; gli eventi sensore in Disarmed, per la traccia, si ignorano.
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): per questo i casi limite sull'ordine dei messaggi vanno studiati uno per uno.
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, se entrambi inviano prima di leggere su canali sincroni si ha deadlock; rompendo la simmetria (uno invia e poi riceve, l'altro riceve e poi invia, come nella topologia ring del lab 10) il deadlock sparisce, ma il secondo riceve la mossa del primo prima di inviare la propria. Va bene solo se ogni giocatore fissa la mossa prima dello scambio; la garanzia strutturale la dà l'arbitro.
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à.
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.
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.
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 su canali non bufferizzati sincronizzano sul rendez-vous, gli attori mai. Stessa filosofia di fondo — nessuna memoria condivisa, "share memory by communicating" — con primitive duali.
Enumerando per ciascuno dei 5 stati la reazione a ogni tipo di messaggio (evento sensore, PIN corretto/errato, timeout): è la matrice stati×messaggi che viene dalla lettura della traccia (sezione 2). 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.
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.
report.pdf in assignments/assignment-03/: se il deliverable esiste (zip consegnato sul sito), va recuperato, rieseguito e riletto riga per riga prima dell'orale; se non esiste, l'esercizio va completato, perché il colloquio è costruito sul portfolio degli assignment. Tutto ciò che sta in questa scheda è design difendibile, ma all'orale serve poterlo mostrare nel proprio codice.WaitGroup usato come scorciatoia) e che ogni informazione fluisce nei canali.