Tiga contoh bug yang tidak bersembunyi dari siapa pun





Saya menulis banyak tentang meneliti bug rumit - bug CPU, bug kernel, alokasi memori menengah 4 GB, tetapi sebagian besar bug tidak begitu eksotis. Terkadang, untuk menemukan bug, Anda hanya perlu melihat dasbor server, menghabiskan beberapa menit di profiler, atau membaca peringatan compiler.



Dalam artikel ini, saya akan membahas tiga bug utama yang telah saya temukan dan perbaiki; semuanya tidak bersembunyi sama sekali dan hanya menunggu seseorang untuk memperhatikan mereka.



Kejutan di prosesor server







Beberapa tahun yang lalu, saya menghabiskan beberapa minggu mempelajari perilaku memori di server game langsung. Server menjalankan Linux di pusat data jarak jauh, sehingga sebagian besar waktu dihabiskan untuk mendapatkan izin yang diperlukan sehingga saya dapat melakukan tunnel ke server, serta mempelajari cara bekerja secara efektif dengan perf dan alat diagnostik Linux lainnya. Saya menemukan serangkaian bug yang menyebabkan konsumsi memori menjadi tiga kali lebih tinggi dari yang diperlukan, dan memperbaikinya:



  • Saya menemukan ketidakcocokan ID peta, yang menyebabkan setiap game tidak menggunakan salinan data yang sama sekitar 20MB, tetapi memuat yang baru.
  • Saya menemukan variabel global 50 MB yang tidak terpakai (!) (!!), yang menetapkan nol memset (!!!), yang menyebabkannya mengonsumsi RAM fisik dalam setiap proses.
  • Berbagai bug yang tidak terlalu serius.


Tapi cerita kita bukan tentang itu.



Setelah meluangkan waktu untuk mempelajari cara membuat profil server game kami, saya menyadari bahwa saya dapat menjelajahinya lebih dalam. Oleh karena itu, saya menjalankan performa terbaik di server salah satu game kami. Proses server pertama yang saya buat profilnya ... aneh. Menonton data sampel prosesor secara langsung, saya melihat bahwa satu fungsi menghabiskan 100% waktu CPU. Namun, hanya empat belas instruksi yang dijalankan dalam fungsi ini. Itu tidak masuk akal.



Pada awalnya saya berasumsi bahwa saya menggunakan perf secara tidak benaratau salah menafsirkan data. Saya melihat beberapa proses server lainnya dan menemukan bahwa sekitar setengah dari mereka berada dalam keadaan yang aneh. Paruh kedua memiliki profil CPU yang lebih normal.



Fungsi yang menarik bagi kami melewati daftar node navigasi yang ditautkan. Saya bertanya kepada rekan-rekan saya dan menemukan seorang programmer yang mengatakan bahwa masalah presisi floating point dapat menyebabkan game menghasilkan daftar navigasi yang berulang. Mereka selalu ingin membatasi jumlah maksimum node yang dapat dilewati, tetapi mereka tidak pernah bisa melakukannya.



Jadi teka-teki itu terpecahkan? Ketidakstabilan penghitungan floating point menyebabkan loop dalam daftar navigasi, yang membuat game terus menerus melewati mereka - itu saja, perilakunya dijelaskan.



Tapi ... penjelasan seperti itu berarti bahwa ketika ini terjadi, proses server memasuki loop tanpa akhir, semua pemain harus memutuskan koneksi darinya, dan proses server akan menghabiskan seluruh inti prosesor tanpa henti. Jika itu masalahnya, bukankah pada akhirnya kita akan kehabisan sumber daya di server kita? Tidakkah ada yang memperhatikan ini?



Saya mencari data pemantauan server dan menemukan sesuatu seperti ini:







Selama seluruh periode pemantauan (satu hingga dua tahun), saya mengamati fluktuasi harian dan mingguan dalam beban server, yang ditumpangkan oleh pola bulanan. Tingkat pemanfaatan prosesor secara bertahap meningkat dan kemudian turun ke nol. Setelah bertanya lebih banyak, saya menemukan bahwa server di-boot ulang sebulan sekali. Dan akhirnya, logika muncul dalam semua ini:



  • , .
  • , , .
  • CPU , 50%.
  • .


Bug diperbaiki dengan menambahkan beberapa baris kode yang berhenti melintasi daftar setelah dua puluh node navigasi, mungkin menghemat beberapa juta dolar untuk biaya server dan daya. Saya tidak menemukan bug ini dengan melihat grafik pemantauan, tetapi siapa pun yang melihatnya dapat melakukannya.



Saya suka fakta bahwa frekuensi bug bertepatan dengan maksimalisasi biayanya; pada saat yang sama, dia tidak pernah menyebabkan masalah yang cukup serius untuk ditemukan. Ini mirip dengan tindakan virus yang berevolusi untuk membuat orang bersin, bukan membunuh mereka.



Pemuatan lambat







Produktivitas pengembang perangkat lunak berkaitan erat dengan kecepatan siklus edit / kompilasi / tautan / debug. Dengan kata lain, ini bergantung pada berapa lama setelah melakukan perubahan pada file sumber untuk menjalankan biner baru dengan perubahan yang dibuat. Saya telah melakukan pekerjaan yang hebat selama bertahun-tahun untuk mengurangi waktu kompilasi / tautan, tetapi waktu muat juga penting. Beberapa game melakukan banyak pekerjaan setiap kali dimulai. Saya tidak sabar dan oleh karena itu sering menjadi orang pertama yang menghabiskan berjam-jam atau berhari-hari untuk membuat game memuat beberapa detik lebih cepat.



Dalam hal ini, saya menjalankan profiler favorit saya dan melihat grafik penggunaan CPU selama fase pemuatan awal game. Satu langkah tampak paling menjanjikan: butuh sekitar sepuluh detik untuk menginisialisasi beberapa data pencahayaan. Saya berharap dapat ditemukan cara untuk mempercepat penghitungan ini dengan menghemat lima detik pada tahap pengaktifan. Sebelum mendalami studi, saya berkonsultasi dengan spesialis grafis. Dia berkata:



β€œKami tidak menggunakan data pencahayaan ini dalam game. Hapus saja tantangan ini. "



Oh bagus. Itu mudah.



Dengan menghabiskan setengah jam membuat profil dan mengubah satu baris, saya dapat mengurangi separuh waktu pemuatan menu utama, dan tidak membutuhkan upaya yang luar biasa.



Keberangkatan sebelum waktunya



Karena banyaknya argumen yang berubah - ubah dalam pemformatan, printfsangat mudah untuk mendapatkan kesalahan jenis tidak cocok. Dalam praktiknya, hasil dapat sangat bervariasi:



  1. printf ("0x% 08lx", p); // Cetak pointer sebagai int - truncate atau lebih buruk lagi pada 64 bit
  2. printf ("% d,% f", f, i); // Mengubah tempat float dan int - mungkin terlihat tidak masuk akal, atau mungkin berhasil (!)
  3. printf ("% s% d", i, s); // Mengubah urutan string dan int - kemungkinan besar akan menyebabkan crash


Standar mengatakan bahwa ketidakcocokan jenis seperti itu adalah perilaku yang tidak ditentukan, dan beberapa kompiler menghasilkan kode yang sengaja macet dengan salah satu ketidakcocokan ini, tetapi daftar di atas mencantumkan hasil yang paling mungkin (catatan: pertanyaan mengapa paragraf kedua sering mengeluarkan hasil yang diinginkan adalah baik Teka-teki pengetahuan ABI ).



Kesalahan seperti itu sangat mudah dibuat, sehingga semua kompiler modern memiliki kemampuan untuk memperingatkan pengembang bahwa telah terjadi ketidakcocokan. Baik gcc dan clang memiliki anotasi fungsi gaya printf, dan keduanya dapat memperingatkan ketidakcocokan (namun, sayangnya, anotasi tidak berfungsi dengan fungsi gaya wprintf). VC ++ memiliki anotasi (sayangnya yang lain) yang / menganalisis dapat digunakan untuk memperingatkan tentang ketidakcocokan, tetapi jika Anda tidak menggunakan / menganalisis, itu hanya akan memperingatkan tentang fungsi gaya CRT gaya printf / wprintf, bukan fungsi kustom Anda ...



Perusahaan tempat saya bekerja membuat anotasi fungsinya dalam gaya printf sehingga gcc / clang akan mengeluarkan peringatan, tetapi kemudian memutuskan untuk mengabaikan peringatan tersebut. Ini adalah keputusan yang aneh, karena peringatan semacam itu adalah indikator bug yang benar-benar akurat - rasio signal-to-noise tidak terbatas.



Saya memutuskan untuk mulai membersihkan bug ini dengan VC ++ dan / menganalisis anotasi untuk menemukan semua bug dengan tepat. Saya bekerja melalui sebagian besar bug dan membuat satu perubahan besar menunggu kode untuk diperiksa sebelum mengirimkannya.







Ada pemadaman listrik di pusat data akhir pekan itu dan semua server kami mati (mungkin karena kesalahan dalam konfigurasi daya). Petugas darurat bergegas untuk memulihkan dan memperbaiki segala sesuatu sebelum terlalu banyak uang yang hilang.



Aspek lucu dari bug printf adalah bahwa mereka melakukan kesalahan 100% setiap saat. Artinya, jika mereka akan menampilkan data yang salah atau menyebabkan program macet, maka ini terjadi setiap saat. Oleh karena itu, mereka dapat tetap berada dalam program hanya jika mereka berada dalam kode pencatatan yang tidak pernah dibaca, atau dalam kode penanganan kesalahan yang jarang dijalankan.



Ternyata peristiwa "restart serentak semua server" menyebabkan kode bergerak di sepanjang jalur yang biasanya tidak akan dijalankan. Server awal mulai mencari server lain, tidak dapat menemukannya, dan menampilkan sesuatu seperti pesan berikut:



fprintf (log, "Tidak dapat menemukan server% s. Kode kesalahan% d. \ n", err, nama_server);


Ups. Ketik ketidakcocokan untuk jumlah argumen yang berubah-ubah. Dan keberangkatan.



Responden darurat memiliki masalah tambahan. Server perlu di-boot ulang, tetapi ini tidak dapat dilakukan sebelum dump kerusakan diperiksa, bug ditemukan, biner server tidak dibuat ulang, dan versi baru dirilis. Itu adalah proses yang cukup cepat - sepertinya, tidak lebih dari beberapa jam, tetapi cukup bisa dihindari.



Saya pikir cerita ini dengan sempurna menunjukkan mengapa kita harus menghabiskan waktu untuk memecahkan masalah penyebab peringatan ini - mengapa mengabaikan peringatan yang memberi tahu kita bahwa kode pasti akan rusak atau berperilaku buruk saat dijalankan? Namun, tidak ada yang peduli bahwa menghilangkan kelas peringatan ini dapat menghemat beberapa jam waktu henti. Bahkan, budaya perusahaan tidak peduli tentang setiap perbaikan tersebut. Tetapi bug terakhir inilah yang membuat saya menyadari bahwa sudah waktunya untuk pindah ke perusahaan lain.



Pelajaran apa yang bisa dipetik dari ini?



Jika semua orang yang terlibat bekerja keras pada fitur produk dan memperbaiki bug yang terkenal, maka mungkin ada bug yang sangat sederhana yang ditampilkan di depan umum. Luangkan sedikit waktu untuk mempelajari log, membersihkan peringatan compiler (meskipun, pada kenyataannya, jika Anda memiliki peringatan compiler, maka mungkin ada baiknya memikirkan kembali keputusan yang telah Anda buat dalam hidup), jalankan profiler selama beberapa menit. Anda mendapatkan poin ekstra jika Anda menambahkan sistem logging Anda sendiri, mengaktifkan peringatan baru, atau menggunakan profiler yang tidak digunakan orang lain selain Anda.



Jika Anda melakukan perbaikan luar biasa yang meningkatkan penggunaan atau stabilitas memori / cpu, dan tidak ada yang peduli, carilah perusahaan yang menghargainya.



Diskusi Hacker News di sini , diskusi Reddit di sini , diskusi Twitter di sini .






Periklanan



Server yang dapat diandalkan untuk disewa dan pilihan yang tepat dari rencana tarif akan membuat Anda tidak terlalu terganggu oleh pemberitahuan pemantauan yang tidak menyenangkan - semuanya akan bekerja dengan lancar dan dengan waktu kerja yang sangat tinggi!









All Articles