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 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])
}
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.
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.
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
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.
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. 13 — 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. 13).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 l'unico modo pulito di introdurre pro-attività temporale (Cap. 14 — Attori avanzati: timer e cluster). Cruciale: cancellare il timer se il PIN arriva prima dello scadere.ActorWithBehavioursAndStashing.java (Cap. 14, sezione stash+timer).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. 12 — 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 discusso nel confronto tra le tre topologie (Cap. 12).for {} finale degli esempi di lab è proprio ciò da non replicare: si attende un valore dal canale della finale).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.
È 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 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.
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.
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 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.
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.
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.
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 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 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.
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.