Odpowiedź Gillesa wyjaśnia stan wyścigu. Odpowiem tylko na tę część:
Czy jest jakiś sposób, w jaki mogę zmusić ten skrypt do wyprowadzania zawsze 0 linii (więc przekierowanie We / Wy do tmp jest zawsze przygotowywane jako pierwsze, więc dane są zawsze niszczone)? Dla jasności mam na myśli zmianę ustawień systemu
IDK, jeśli narzędzie do tego już istnieje, ale mam pomysł, jak można je wdrożyć. (Zauważ jednak, że nie zawsze będzie to 0 linii, tylko przydatny tester, który łatwo łapie takie proste wyścigi i kilka bardziej skomplikowanych wyścigów. Zobacz komentarz @Gillesa .) Nie gwarantuje to, że skrypt jest bezpieczny , ale może być użytecznym narzędziem do testowania, podobnym do testowania wielowątkowego programu na różnych procesorach, w tym słabo uporządkowanych procesorach innych niż x86, takich jak ARM.
Uruchomiłbyś to jako racechecker bash foo.sh
Użyj tego samego systemu-call śledzenie / przechwytywania obiektów, które strace -f
i ltrace -f
wykorzystanie dołączyć do każdego procesu potomnego. (W systemie Linux jest to to samo ptrace
wywołanie systemowe, którego używa GDB i inne debugery do ustawiania punktów przerwania, pojedynczego kroku i modyfikowania pamięci / rejestrów innego procesu.)
Instrumentu open
i openat
układ połączeń: kiedy każdy proces uruchomiony w ramach tego narzędzia sprawia wywołania systemowego (lub ) z , uśpienia przez jakieś 1/2 lub 1 sekundę. Pozwól innym wywołaniom systemowym (zwłaszcza tym ) wykonać się bezzwłocznie.open(2)
openat
O_RDONLY
open
O_TRUNC
Powinno to pozwolić pisarzowi wygrać wyścig w prawie każdym stanie wyścigu, chyba że obciążenie systemu było również wysokie lub był to skomplikowany stan wyścigu, w którym obcinanie nastąpiło dopiero po kolejnym przeczytaniu. Tak więc losowa odmiana, które są opóźnione open()
(a może read()
s lub zapisuje) , zwiększyłaby moc wykrywania tego narzędzia, ale oczywiście bez testowania przez nieskończony czas za pomocą symulatora opóźnienia, który ostatecznie obejmie wszystkie możliwe sytuacje, w których można się spotkać w prawdziwym świecie, nie możesz być pewien, że twoje skrypty są wolne od ras, chyba że przeczytasz je uważnie i udowodnisz, że nie są.
Prawdopodobnie byłbyś potrzebny do dodania do białej listy (nie opóźniania open
) plików, /usr/bin
a /usr/lib
więc proces uruchamiania nie trwa wiecznie. (Dynamiczne dowiązanie środowiska wykonawczego musi obejmować open()
wiele plików (spójrz na niego strace -eopen /bin/true
lub /bin/ls
kiedyś), chociaż jeśli sama powłoka nadrzędna wykonuje obcinanie, to będzie w porządku. Ale to nadal dobrze, aby to narzędzie nie powodowało nadmiernego spowolnienia skryptów).
A może najpierw umieść na białej liście każdy plik, do którego proces wywołujący nie ma uprawnień do obcięcia. tzn. proces śledzenia może wykonać access(2)
wywołanie systemowe przed faktycznym zawieszeniem procesu, który chciał open()
utworzyć plik.
racechecker
sam musiałby być napisany w języku C, a nie w powłoce, ale być może mógłby użyć strace
kodu jako punktu wyjścia i może nie zająć dużo pracy.
Możesz mieć tę samą funkcjonalność z systemem plików FUSE . Prawdopodobnie istnieje BEZPIECZNY przykład czystego systemu plików typu pass-through, więc możesz dodać kontrole do open()
funkcji w tym, co sprawia, że jest ona uśpiona dla otwierania tylko do odczytu, ale pozwala od razu obciąć.
racechecker
cały czas. Prawdopodobnie chciałbyś, aby czas uśpienia typu otwartego do odczytu był konfigurowalny z korzyścią dla osób na bardzo obciążonych komputerach, które chcą ustawić go wyżej, na przykład 10 sekund. Lub ustaw niższą wartość, na przykład 0,1 sekundy dla długich lub nieefektywnych skryptów, które często otwierają pliki .Dlaczego występuje warunek wyścigu?
Dwie strony rury są wykonywane równolegle, a nie jedna po drugiej. Jest to bardzo prosty sposób, aby to wykazać: uruchomić
To zajmuje jedną sekundę, a nie dwie.
Powłoka uruchamia dwa procesy potomne i czeka na zakończenie ich obu. Te dwa procesy wykonać równolegle: jedynym powodem, dlaczego jeden z nich będzie synchronizować z drugiej jest, gdy trzeba czekać na drugą. Najczęstszym punktem synchronizacji jest sytuacja, gdy prawa strona blokuje oczekiwanie na odczyt danych na standardowym wejściu i zostaje odblokowana, gdy lewa strona zapisuje więcej danych. Odwrotna sytuacja może się również zdarzyć, gdy prawa strona wolno odczytuje dane, a lewa strona blokuje się w operacji zapisu, dopóki prawa strona nie odczyta większej ilości danych (w samym potoku znajduje się bufor zarządzany przez jądro, ale ma mały maksymalny rozmiar).
Aby zaobserwować punkt synchronizacji, należy przestrzegać następujących poleceń (
sh -x
wypisuje każde polecenie podczas jego wykonywania):Graj odmianami, aż poczujesz się komfortowo z tym, co obserwujesz.
Biorąc pod uwagę złożone polecenie
proces po lewej stronie wykonuje następujące czynności (wymieniłem tylko kroki, które są istotne dla mojego wyjaśnienia):
cat
z argumentemtmp
.tmp
do czytania.Proces po prawej stronie wykonuje następujące czynności:
tmp
, obcięcie pliku w tym procesie.head
z argumentem-1
.Jedynym punktem synchronizacji jest to, że prawy-3 czeka, aż lewy-3 przetworzy jedną pełną linię. Nie ma synchronizacji między lewym-2 a prawym-1, więc mogą się zdarzyć w dowolnej kolejności. Kolejność, w jakiej występują, nie jest przewidywalna: zależy to od architektury procesora, powłoki, jądra, od których rdzeni procesy zostaną zaplanowane, od tego, co zakłóca procesor w tym czasie itp.
Jak zmienić zachowanie
Nie można zmienić zachowania, zmieniając ustawienie systemowe. Komputer robi to, co mu każesz. Kazałeś skrócić
tmp
i czytaćtmp
równolegle, więc robi to dwie rzeczy równolegle.Ok, jest jedno „ustawienie systemowe”, które możesz zmienić: możesz zastąpić
/bin/bash
go innym programem, który nie jest bash. Mam nadzieję, że zrozumiałoby to, że nie jest to dobry pomysł.Jeśli chcesz, aby obcięcie miało miejsce przed lewą stroną rury, musisz umieścić je poza rurociągiem, na przykład:
lub
Nie mam pojęcia, dlaczego tego chcesz. Po co czytać z pliku, o którym wiesz, że jest pusty?
I odwrotnie, jeśli chcesz, aby przekierowanie danych wyjściowych (w tym obcinanie) miało miejsce po
cat
zakończeniu odczytu, musisz albo całkowicie buforować dane w pamięci, np.lub napisz do innego pliku, a następnie przenieś go na miejsce. Jest to zwykle solidny sposób wykonywania skryptów i ma tę zaletę, że plik jest zapisywany w całości, zanim będzie widoczny przez oryginalną nazwę.
Moreutils kolekcja zawiera program, który nie tylko, że nazywa
sponge
.Jak automatycznie wykryć problem
Jeśli Twoim celem było wzięcie źle napisanych skryptów i automatyczne ustalenie, gdzie się psują, przepraszam, życie nie jest takie proste. Analiza środowiska wykonawczego nie znajdzie problemu w sposób wiarygodny, ponieważ czasami
cat
kończy się odczyt, zanim nastąpi obcięcie. Analiza statyczna może w zasadzie to zrobić; uproszczony przykład twojego pytania został złapany przez Shellcheck , ale może nie wychwycić podobnego problemu w bardziej złożonym skrypcie.źródło
strace
(np. Linuxptrace
), aby wszystkieopen
wywołania systemowe do odczytu (we wszystkich procesach potomnych) spały przez pół sekundy, więc podczas wyścigu z obcięcie, obcięcie prawie zawsze wygrywa.