Warunki wyścigu danych sieciowych z piekła rodem
Pisałem klienta / serwer sieciowy (Windows XP / C #) do pracy z podobną aplikacją na naprawdę starej stacji roboczej (Encore 32/77) napisanej przez innego programistę.
Aplikacja zrobiła w zasadzie udostępnianie / manipulowanie niektórymi danymi na hoście, aby kontrolować proces hosta z systemem za pomocą naszego fantazyjnego, wielomonitorowego interfejsu użytkownika z ekranem dotykowym.
Zrobiło to z trójwarstwową strukturą. Proces komunikacji odczytał / zapisał dane do / z hosta, wykonał wszystkie niezbędne konwersje formatu (endianness, format zmiennoprzecinkowy itp.) I zapisał / odczytał wartości do / z bazy danych. Baza danych działała jako pośrednik danych między interfejsem komunikacyjnym a interfejsem dotykowym. Interfejsy ekranu dotykowego generowane przez aplikację interfejsu użytkownika na podstawie liczby monitorów podłączonych do komputera (to automatycznie wykryło).
W podanym przedziale czasowym pakiet wartości między hostem a naszym komputerem mógł przesyłać maksymalnie 128 wartości w poprzek drutu jednocześnie z maksymalnym opóźnieniem ~ 110 ms na podróż w obie strony (UDP zastosowano z bezpośrednim połączeniem ethernetowym x-over między komputery). Tak więc dozwolona liczba zmiennych oparta na zmiennej liczbie dołączonych ekranów dotykowych była pod ścisłą kontrolą. Ponadto host (choć posiadający dość złożoną architekturę wieloprocesorową z magistralą pamięci współużytkowanej wykorzystywaną do obliczeń w czasie rzeczywistym) miał około 1/100 mocy przetwarzania mojego telefonu komórkowego, więc miał za zadanie wykonać tak mało przetwarzania, jak to możliwe, a jego serwer / klient musiał zostać napisany w asemblerze, aby to zapewnić (host prowadził pełną symulację w czasie rzeczywistym, na którą nasz program nie miał wpływu).
Problem polegał na tym. Niektóre wartości po zmianie na ekranie dotykowym nie przyjmowałyby tylko nowo wprowadzonej wartości, ale losowo przełączały się między tą wartością a poprzednią. To i tylko na kilku konkretnych wartościach na kilku konkretnych stronach z pewną kombinacją stron kiedykolwiek wykazywało ten objaw. Prawie przegapiliśmy ten problem, dopóki nie zaczęliśmy go uruchamiać w procesie wstępnej akceptacji klienta
Aby określić problem, wybrałem jedną z oscylujących wartości:
- Sprawdziłem aplikację Touchscreen, oscylowała
- Sprawdziłem bazę danych, oscylując
- Sprawdziłem aplikację komunikacyjną, oscylując
Następnie wybrałem Wireshark i zacząłem ręcznie dekodować przechwytywanie pakietów. Wynik:
- Nie oscyluje, ale pakiety nie wyglądają dobrze, było za dużo danych.
Przeszedłem każdy szczegół kodu komunikacyjnego sto razy, nie znajdując żadnej wady / błędu.
W końcu zacząłem wysyłać wiadomości e-mail do drugiego dewelopera, pytając szczegółowo, jak działa jego koniec, aby sprawdzić, czy czegoś brakuje. Potem to znalazłem.
Najwyraźniej, kiedy wysyłał dane, nie opróżniał tablicy danych przed transmisją, więc w gruncie rzeczy po prostu nadpisał ostatni użyty bufor nowymi wartościami nadpisując stare, ale stare wartości nie nadpisane wciąż są przesyłane.
Tak więc, jeśli wartość znajdowałaby się w pozycji 80 tablicy danych, a lista żądanych wartości zmieniła się na mniej niż 80, ale ta sama wartość była zawarta na nowej liście, wówczas obie wartości istniałyby w buforze danych dla tego konkretnego bufora w dowolnym dany czas.
Wartość odczytywana z bazy danych zależała od przedziału czasu, w którym interfejs użytkownika żądał wartości.
Poprawka była boleśnie prosta. Wczytaj liczbę elementów przychodzących do bufora danych (faktycznie był zawarty jako część protokołu pakietu) i nie odczytuj bufora powyżej tej liczby.
Zdobyta wiedza:
Nie bierz nowoczesnej mocy obliczeniowej za pewnik. Był czas, kiedy komputery nie obsługiwały Ethernetu, a opróżnianie tablicy można było uznać za drogie. Jeśli naprawdę chcesz zobaczyć, jak daleko zaszliśmy, wyobraź sobie system, który praktycznie nie ma formy dynamicznej alokacji pamięci. IE, proces wykonawczy musiał wstępnie przydzielić całą pamięć dla wszystkich programów, aby żaden program nie mógł przekroczyć tej granicy. IE, przydzielenie większej ilości pamięci do programu bez ponownej kompilacji całego systemu może spowodować poważną awarię. Zastanawiam się, czy ludzie będą kiedyś opowiadać o dniach zbierania śmieci w tym samym świetle.
Podczas tworzenia sieci z niestandardowymi protokołami (lub ogólnie obsługi reprezentacji danych binarnych) upewnij się, że czytasz specyfikację, dopóki nie zrozumiesz każdej funkcji każdej wartości przesyłanej przez potok. Mam na myśli, czytaj to, dopóki nie bolą cię oczy. Ludzie przetwarzają dane, manipulując poszczególnymi bitami lub bajtami, w bardzo sprytny i wydajny sposób. Brak najmniejszego szczegółu może uszkodzić system.
Ogólny czas na naprawę wynosił 2-3 dni, a większość czasu spędziłem na pracy nad innymi rzeczami, kiedy byłem z tego sfrustrowany.
Uwaga: Komputer, o którym mowa, domyślnie nie obsługuje sieci Ethernet. Karta do napędu została wykonana na zamówienie i zmodernizowana, a stos protokołów praktycznie nie istniał. Deweloper, z którym pracowałem, był cholernie programistą, nie tylko zaimplementował uproszczoną wersję UDP i minimalny fałszywy stos ethernetowy (procesor nie był wystarczająco silny, aby obsłużyć pełny stos ethernetowy) w systemie dla tego projektu ale zrobił to w niecały tydzień. Był także jednym z liderów zespołu projektowego, który przede wszystkim zaprojektował i zaprogramował system operacyjny. Powiedzmy po prostu, że wszystko, co kiedykolwiek miał do powiedzenia na temat komputerów / programowania / architektury, bez względu na to, jak długo było to rozwinięte lub jak bardzo już byłem nowy, słuchałbym każdego słowa.