Le condizioni di gara dei dati di rete dall'inferno
Stavo scrivendo un client / server di rete (Windows XP / C #) per lavorare con un'applicazione simile su una workstation davvero vecchia (Encore 32/77) scritta da un altro sviluppatore.
Ciò che l'applicazione ha fatto essenzialmente è stato condividere / manipolare determinati dati sull'host per controllare il processo dell'host che esegue il sistema con la nostra interfaccia utente touchscreen multi-monitor basata su PC.
Lo ha fatto con una struttura a 3 strati. Il processo di comunicazione ha letto / scritto i dati da / verso l'host, ha effettuato tutte le conversioni di formato necessarie (endianness, formato a virgola mobile, ecc.) E ha scritto / letto i valori su / da un database. Il database ha funzionato da intermediario dei dati tra le interfacce di comunicazione e touchscreen. L'app UI del touchscreen ha generato interfacce touchscreen in base al numero di monitor collegati al PC (lo ha rilevato automaticamente).
Nell'intervallo di tempo dato un pacchetto di valori tra l'host e il nostro PC è stato in grado di inviare solo 128 valori max attraverso il cavo alla volta con una latenza massima di ~ 110 ms per round trip (UDP è stato utilizzato con una connessione ethernet diretta x-over tra i computer). Pertanto, il numero di variabili consentite in base al numero variabile di touchscreen collegati era sotto stretto controllo. Inoltre, l'host (sebbene abbia un'architettura multiprocessore piuttosto complessa con bus di memoria condiviso utilizzato per il calcolo in tempo reale) aveva circa 1/100 della potenza di elaborazione del mio telefono cellulare, quindi aveva il compito di eseguire il minor numero di elaborazioni possibili e il suo server / client ha dovuto essere scritto in assembly per garantire ciò (l'host stava eseguendo una simulazione in tempo reale completa che non poteva essere influenzata dal nostro programma).
Il problema era. Alcuni valori, se modificati sul touchscreen, non assumono solo il valore appena immesso, ma passano in modo casuale tra quel valore e il valore precedente. Questo e solo su alcuni valori specifici su alcune pagine specifiche con una certa combinazione di pagine ha mai mostrato il sintomo. Abbiamo quasi perso completamente il problema fino a quando non abbiamo iniziato a eseguirlo attraverso il processo di accettazione iniziale del cliente
Per fissare il problema ho scelto uno dei valori oscillanti:
- Ho controllato l'app Touchscreen, stava oscillando
- Ho controllato il database, oscillando
- Ho controllato l'app delle comunicazioni, oscillando
Poi ho fatto scattare il wirehark e ho iniziato a decodificare manualmente le acquisizioni di pacchetti. Risultato:
- Non oscillante ma i pacchetti non sembravano corretti, c'erano troppi dati.
Ho esaminato centinaia di volte ogni dettaglio del codice delle comunicazioni senza trovare difetti / errori.
Alla fine ho iniziato a inviare email agli altri sviluppatori chiedendo in dettaglio come funzionava la sua fine per vedere se mancava qualcosa. Poi l'ho trovato.
Apparentemente, quando ha inviato i dati non ha svuotato l'array di dati prima della trasmissione, quindi, in sostanza, stava semplicemente sovrascrivendo l'ultimo buffer utilizzato con i nuovi valori sovrascrivendo il vecchio, ma i vecchi valori non sovrascritti venivano ancora trasmessi.
Quindi, se un valore fosse nella posizione 80 dell'array di dati e l'elenco dei valori richiesti fosse cambiato in meno di 80 ma lo stesso valore fosse contenuto nel nuovo elenco, entrambi i valori esisterebbero nel buffer di dati per quel buffer specifico in qualsiasi tempo a disposizione.
Il valore letto dal database dipendeva dalla fascia oraria in cui l'interfaccia utente richiedeva il valore.
La correzione era dolorosamente semplice. Leggere il numero di elementi in entrata nel buffer dei dati (in realtà era contenuto come parte del protocollo del pacchetto) e non leggere il buffer oltre quel numero di elementi.
Lezioni imparate:
Non dare per scontata la moderna potenza di calcolo. C'è stato un tempo in cui i computer non supportavano Ethernet e quando si scaricava un array poteva essere considerato costoso. Se vuoi davvero vedere fino a che punto siamo arrivati, immagina un sistema che non ha praticamente alcuna forma di allocazione dinamica della memoria. IE, il processo esecutivo ha dovuto pre-allocare tutta la memoria per tutti i programmi in ordine e nessun programma potrebbe crescere oltre quel limite. Ad esempio, l'allocazione di più memoria a un programma senza ricompilare l'intero sistema potrebbe causare un arresto anomalo. Mi chiedo se un giorno le persone parleranno dei giorni della raccolta dei rifiuti nella stessa luce.
Quando si esegue il collegamento in rete con protocolli personalizzati (o si gestisce la rappresentazione di dati binari in generale), assicurarsi di leggere le specifiche fino a quando non si comprende ogni funzione di ogni valore inviato attraverso la pipe. Voglio dire, leggilo fino a quando i tuoi occhi fanno male. Le persone gestiscono i dati manipolando singoli bit o byte hanno modi molto intelligenti ed efficienti di fare le cose. Manca il più piccolo dettaglio potrebbe rompere il sistema.
Il tempo complessivo di riparazione è stato di 2-3 giorni, con la maggior parte del tempo trascorso a lavorare su altre cose quando sono stato frustrato da questo.
Nota a margine: il computer host in questione non supportava Ethernet per impostazione predefinita. La scheda per guidarla è stata realizzata su misura e adattata e lo stack di protocollo praticamente non esisteva. Lo sviluppatore con cui stavo lavorando era un programmatore infernale, non solo implementava una versione ridotta di UDP e uno stack ethernet falso (il processore non era abbastanza potente da gestire uno stack ethernet completo) sul sistema per questo progetto ma lo ha fatto in meno di una settimana. Era stato anche uno dei leader del team di progetto originale che aveva progettato e programmato il sistema operativo in primo luogo. Diciamo solo, qualsiasi cosa abbia mai avuto da condividere su computer / programmazione / architettura, non importa quanto a lungo o quanto già nuovo, ascolterei ogni parola.