News · Το OpenAI εντόπισε τα crashes της αναζήτησης στο ChatGPT σε έναν προβληματικό host της Azure και σε ένα race condition 18 ετών στη libunwind
Το OpenAI εντόπισε τα crashes της αναζήτησης στο ChatGPT σε έναν προβληματικό host της Azure και σε ένα race condition 18 ετών στη libunwind
Πώς μια ανάλυση σε επίπεδο πληθυσμού, βασισμένη σε έναν χρόνο core dumps, ξεχώρισε δύο άσχετα bugs που παρουσιάζονταν σαν ένα αδύνατο σφάλμα
Δύο crashes με το ίδιο μεταμφιεσμένο πρόσωπο
Το θέμα είναι το Rockset, το cloud-native σύστημα αναζήτησης και ανάλυσης που εξαγόρασε το OpenAI το 2024 και χρησιμοποιεί πλέον για να διατηρεί ενημερωμένο το ευρετήριο της βάσης γνώσης ενός workspace για το ChatGPT. Το επίπεδο εκτέλεσής του είναι γραμμένο σε C++, και λίγους μήνες πριν από τη δημοσίευση ξεκίνησε να παρουσιάζει crashes που φαινομενικά ήταν αδύνατα: μια συνηθισμένη συνάρτηση έδειχνε να τερματίζει κανονικά και μετά επέστρεφε σε μια εσφαλμένη διεύθυνση, άλλοτε σε μια μηδενική θέση return-address, άλλοτε με τον stack pointer %rsp μετατοπισμένο κατά ακριβώς 8 bytes.
Κανένα από τα δύο συμπτώματα δεν ταιριάζει σε συνηθισμένο κώδικα εφαρμογής. Μια αυθαίρετη εγγραφή που καταλήγει αποκλειστικά σε μια αποθηκευμένη διεύθυνση επιστροφής είναι εξαιρετικά απίθανη, ενώ η μετατόπιση του %rsp κατά 8 bytes χωρίς inline assembly, setcontext ή longjmp —τίποτα από τα οποία δεν χρησιμοποιεί το Rockset— είναι ακόμη πιο παράξενη, καθώς ο μεταγλωττισμένος κώδικας αγγίζει αυτόν τον καταχωρητή μόνο στο prologue και το epilogue μιας συνάρτησης. Κάθε υπόθεση που πρότειναν η ομάδα ή το ChatGPT είχε ισχυρά στοιχεία εναντίον της. Ο λόγος, όπως αποδείχτηκε, ήταν ότι παρατηρούσαν ταυτόχρονα δύο άσχετα bugs: σιωπηλή βλάβη υλικού σε έναν και μόνο host της Azure, και ένα race condition 18 ετών στη GNU libunwind.
Γιατρός εναντίον επιδημιολόγου
Το πρώτο ένστικτο της ομάδας ήταν κλινικό: να εξετάσουν προσεκτικά μερικά core dumps, να διαμορφώσουν υποθέσεις και να τις αποκλείσουν μία προς μία. Τα περισσότερα crashes εντοπίζονταν σε μία και μόνη έντονα inlined μέθοδο, την DocumentTree::updateDocument, γεγονός που άφηνε ένα υπερβολικά μεγάλο σύνολο υποψήφιων συναρτήσεων. Τα ερωτήματα στα logs παρήγαγαν τόσο ψευδώς θετικά όσο και ψευδώς αρνητικά αποτελέσματα, καθώς τα bugs καταστροφής stack αλλοίωναν ακριβώς τα ίχνη στοίβας που χρησιμοποιούνταν για την κατηγοριοποίησή τους. Η χειροκίνητη επιθεώρηση δεν κλιμακωνόταν. Το χειρότερο, η ομάδα είχε ήδη αποκλείσει την αιτία υλικού επειδή τα crashes εμφανίζονταν σε πολλαπλές περιοχές και τύπους υλικού —ένα συμπέρασμα που ήταν ακριβώς ανάποδο για έναν από τους δύο πληθυσμούς σφαλμάτων.
Το σημείο καμπής ήταν μια αλλαγή μεθόδου. Αντί να διαγνώσουν έναν ασθενή, αντιμετώπισαν όλο τον στόλο ως πληθυσμό. Ζήτησαν από το ChatGPT να γράψει ένα script που κατέβαζε ένα πρόθεμα κάθε core file, εξήγε τους καταχωρητές, φιλτράριζε γνωστά ψευδώς θετικά βάσει των logs, και χαρακτήριζε κάθε crash ως return-to-null, misaligned-stack ή άλλο. Εκτελώντας το σε κάθε core dump παραγωγής του Rockset από την προηγούμενη χρονιά, οι συσχετίσεις έγιναν αμέσως εμφανείς: τα crashes τύπου return-to-null εμφανίζονταν διάσπαρτα σε πολλά clusters χωρίς σαφή ημερομηνία εκκίνησης, ενώ τα crashes τύπου misaligned-stack προέρχονταν από μία περιοχή, είχαν σαφή ημερομηνία εκκίνησης, και δεν συνέβαιναν ποτέ σε κόμβους με μακρά λειτουργία — η υπογραφή ενός μεμονωμένου φυσικού μηχανήματος με ελαττωματικό υλικό.
Ένα παράθυρο εκατό picoseconds, και η εκτίμηση Fermi που το έκανε αξιόπιστο
Ο προβληματικός host τοποθετήθηκε σε denylist, και τα crashes τύπου misaligned-stack εξαφανίστηκαν, παρότι η ομάδα δεν κατάφερε ποτέ να αναπαραγάγει τη βλάβη των καταχωρητών σε εβδομάδες stress testing. Μετά την αφαίρεση αυτού του cluster, τα υπόλοιπα crashes τύπου return-to-null συνέβαιναν όλα κατά τη διάρκεια unwinding εξαιρέσεων C++. Η GNU libunwind δημιουργεί ένα ucontext_t στη στοίβα και το παραδίδει σε μια εσωτερική ρουτίνα, την _Ux86_64_setcontext, οι τελικές εντολές της οποίας μετακινούν το %rsp στη νέα βάση στοίβας πριν μια επόμενη εντολή διαβάσει τον δείκτη εντολών προορισμού από την ίδια δομή. Μόλις μετακινηθεί το %rsp, η δομή δεν προστατεύεται πλέον από τη red zone, οπότε ένα σήμα που παραδίδεται σε αυτό το κενό επιτρέπει στον πυρήνα να χτίσει το δικό του signal frame πάνω σε αυτή τη μνήμη και να μηδενίσει τον αποθηκευμένο δείκτη εντολών.
Το ευάλωτο παράθυρο είναι μία και μόνο εντολή —περίπου εκατό picoseconds σε μια σύγχρονη out-of-order CPU— που φαινόταν πολύ μικρό για να εξηγήσει περισσότερα από μια δωδεκάδα crashes τύπου return-to-null την ημέρα. Μια εκτίμηση Fermi έκλεισε αυτό το κενό: ένα παράθυρο 10⁻¹⁰ δευτερολέπτων απέναντι σε ένα SIGUSR2 που καταφθάνει κάθε 10⁻² δευτερόλεπτα χρόνου CPU δίνει σε κάθε cleanup handler περίπου 10⁻⁸ πιθανότητες να χάσει το race· ένας host που ρίχνει 10⁴ εξαιρέσεις το δευτερόλεπτο λόγω backpressure στην εισαγωγή δεδομένων αποτυγχάνει τότε περίπου μία φορά κάθε λίγες ώρες, κάτι που σε επίπεδο στόλου είναι υπεραρκετό.
Το Rockset ήταν ασυνήθιστο σε όλους τους τρεις άξονες που καθορίζουν το ρυθμό crash. Ρίχνει εξαιρέσεις συχνά ως μηχανισμό ελέγχου υπερφόρτωσης, παραδίδει SIGUSR2 ασυνήθιστα συχνά μέσω του coarse_thread_cputime_clock, και νωρίτερα εκείνη τη χρονιά είχε προστεθεί μια κλήση timer_getoverrun που έκανε τον signal handler να καταναλώνει περισσότερη στοίβα — αρκετή για να φτάσει και να αλλοιώσει το παλιωμένο ucontext_t. Πριν από αυτή την αλλαγή, τα crashes δεν εμφανίζονταν καθόλου. Το bug 18 ετών υπήρχε πάντα· το γινόμενο του ρυθμού εξαιρέσεων, του ρυθμού σημάτων και της χρήσης στοίβας του handler είχε μόλις ξεπεράσει το όριο πέρα από το οποίο έγινε ορατό.
Το instrumentation ως η πραγματική λύση
Ο άμεσος μετριασμός ήταν η μετάβαση από τη GNU libunwind στον unwinder της libgcc — ανεξάρτητα μια καλή επιλογή, καθώς η libgcc έχει δουλευτεί περισσότερο για τη μείωση lock contention σε μεγάλα VMs — και το OpenAI ανέβασε ανάντη (upstream) έναν αυτόνομο αναπαραγωγό σφάλματος και τη λύση του στη GNU libunwind. Για τη βλάβη υλικού, η ομάδα βελτίωσε τον χειριστή θανατηφόρων σημάτων ώστε να καταγράφει την κατάσταση των καταχωρητών, επιτρέποντας την ανίχνευση επανεμφάνισης αποκλειστικά από τα logs χωρίς να απαιτείται core dump, και άλλαξε το control plane ώστε να επαναχρησιμοποιεί τα VMs αντί να τα ανακυκλώνει, κάτι που διευκολύνει τον εντοπισμό προβληματικών κόμβων.
Το ουσιαστικό δίδαγμα δεν είναι καμία από τις λεπτομέρειες ABI, DWARF ή του μηχανισμού εξαιρέσεων που αποκάλυψε η έρευνα. Είναι ότι η σύγχυση παρέμεινε μόνο επειδή δύο φαινόμενα είχαν αναμειχθεί σε μία ιστορία, και διαλύθηκε τη στιγμή που το σύνολο δεδομένων έγινε καθαρό και πλήρες. Αυτό είναι το δίδαγμα που αξίζει να μεταφερθεί σε άλλες ομάδες υποδομών: όταν ένα bug φαίνεται αδύνατο, το κομμάτι που λείπει είναι συχνά ακριβή δεδομένα πληθυσμού, όχι μια πιο έξυπνη διάγνωση μεμονωμένης περίπτωσης.
Η αξιοπιστία δεν αφορά μόνο την επιδιόρθωση σφαλμάτων αφού συμβούν—αφορά την οικοδόμηση δεδομένων, ροών εργασίας και δεξιοτήτων που μετατρέπουν τα αδύνατα προβλήματα σε διαγνωστικά και επιλύσιμα.Montana Labs
Find this story relevant to you?
Contact us to find a unique solution
Χρειάζεστε συνεργάτη AI engineering που μπορεί να παραδώσει;
Βοηθάμε ομάδες στην Ελλάδα να ενσωματώσουν AI, να δημιουργήσουν AI-powered προϊόντα, να αυτοματοποιήσουν κρίσιμες ροές εργασίας και να εκσυγχρονίσουν τα συστήματα που τα στηρίζουν.
Σχετικά άρθρα
Περισσότερη ανάλυση για την παράδοση προϊόντων, τη λειτουργική ΤΝ και τη δουλειά στα συστήματα που κάνει την ανάπτυξη να αντέχει στην πράξη.