Jawaban Gilles menjelaskan kondisi balapan. Saya hanya akan menjawab bagian ini:
Apakah ada cara saya bisa memaksa skrip ini untuk selalu menghasilkan 0 baris (sehingga I / O redirection ke tmp selalu disiapkan terlebih dahulu dan data selalu dihancurkan)? Agar jelas, maksud saya mengubah pengaturan sistem
IDK jika alat untuk ini sudah ada, tapi saya punya ide untuk bagaimana seseorang dapat diimplementasikan. (Tetapi perhatikan bahwa ini tidak selalu 0 baris, hanya tester yang berguna yang menangkap balapan sederhana seperti ini dengan mudah, dan beberapa balapan yang lebih rumit. Lihat komentar @Gilles .) Itu tidak akan menjamin bahwa skrip aman , tetapi mungkin menjadi alat yang berguna dalam pengujian, mirip dengan menguji program multi-ulir pada CPU yang berbeda, termasuk CPU non-x86 yang dipesan dengan lemah seperti ARM.
Anda akan menjalankannya sebagai racechecker bash foo.sh
Gunakan fasilitas penelusuran / penyadapan panggilan sistem yang sama strace -f
dan ltrace -f
gunakan untuk melampirkan ke setiap proses anak. (Di Linux, ini adalah ptrace
panggilan sistem yang sama yang digunakan oleh GDB dan debugger lain untuk mengatur breakpoint, langkah tunggal, dan memodifikasi memori / register dari proses lain.)
Instrumen open
dan openat
sistem panggilan: ketika proses berjalan di bawah alat ini membuat sebuah open(2)
system call (atau openat
) dengan O_RDONLY
, tidur selama mungkin 1/2 atau 1 detik. Biarkan open
panggilan sistem lain (terutama yang termasuk O_TRUNC
) dijalankan tanpa penundaan.
Ini harus memungkinkan penulis untuk memenangkan perlombaan di hampir setiap kondisi perlombaan, kecuali beban sistem juga tinggi, atau itu adalah kondisi perlombaan yang rumit di mana pemotongan tidak terjadi sampai setelah beberapa pembacaan lainnya. Jadi variasi acak yang open()
s (dan mungkin read()
s atau tulis) ditunda akan meningkatkan daya deteksi alat ini, tetapi tentu saja tanpa pengujian untuk jumlah waktu tak terbatas dengan simulator penundaan yang pada akhirnya akan mencakup semua situasi yang mungkin Anda dapat temui di di dunia nyata, Anda tidak dapat memastikan skrip Anda bebas dari balapan kecuali jika Anda membacanya dengan cermat dan membuktikannya.
Anda mungkin akan perlu untuk daftar putih (tidak delay open
) untuk file dalam /usr/bin
dan /usr/lib
sehingga proses-startup tidak mengambil selamanya. (Tautan dinamis Runtime harus memiliki open()
banyak file (lihat strace -eopen /bin/true
atau /bin/ls
kadang - kadang), meskipun jika induk shell itu sendiri melakukan pemotongan, itu akan baik-baik saja. Tetapi alat ini masih bagus untuk alat ini agar skrip tidak berjalan lambat).
Atau mungkin daftar putih setiap file proses panggilan tidak memiliki izin untuk memotong di tempat pertama. yaitu proses pelacakan dapat membuat access(2)
panggilan sistem sebelum benar-benar menangguhkan proses yang ingin open()
file.
racechecker
itu sendiri harus ditulis dalam C, bukan di shell, tetapi mungkin bisa menggunakan strace
kode sebagai titik awal dan mungkin tidak membutuhkan banyak pekerjaan untuk diimplementasikan.
Anda mungkin bisa mendapatkan fungsionalitas yang sama dengan sistem file FUSE . Mungkin ada contoh FUSE dari sistem file passthrough murni, sehingga Anda dapat menambahkan pemeriksaan ke open()
fungsi yang membuatnya menjadi hanya untuk read-only yang terbuka tetapi membiarkan pemotongan segera terjadi.
racechecker
sepanjang waktu. Dan mungkin Anda ingin membuka-untuk-membaca waktu tidur agar dapat dikonfigurasi untuk kepentingan orang-orang pada mesin yang sangat berat yang ingin membuatnya lebih tinggi, seperti 10 detik. Atau mengaturnya menurunkan, seperti 0,1 detik untuk panjang atau script yang tidak efisien yang membuka kembali file yang banyak .Kenapa ada kondisi balapan
Kedua sisi pipa dieksekusi secara paralel, bukan satu demi satu. Ada cara yang sangat sederhana untuk menunjukkan ini: lari
Ini membutuhkan satu detik, bukan dua.
Shell memulai dua proses anak dan menunggu keduanya selesai. Kedua proses ini dijalankan secara paralel: satu-satunya alasan mengapa salah satu dari mereka akan melakukan sinkronisasi dengan yang lain adalah ketika harus menunggu yang lain. Titik sinkronisasi yang paling umum adalah ketika sisi kanan blok menunggu data untuk dibaca pada input standarnya, dan menjadi tidak terblokir ketika sisi kiri menulis lebih banyak data. Kebalikannya juga bisa terjadi, ketika sisi kanan lambat untuk membaca data dan sisi kiri blok dalam operasi penulisan sampai sisi kanan membaca lebih banyak data (ada penyangga di dalam pipa itu sendiri, dikelola oleh kernel, tetapi memiliki ukuran maksimum kecil).
Untuk mengamati titik sinkronisasi, perhatikan perintah berikut (
sh -x
mencetak setiap perintah saat dijalankan):Bermainlah dengan variasi sampai Anda merasa nyaman dengan apa yang Anda amati.
Diberi perintah majemuk
proses di sebelah kiri melakukan hal berikut (Saya hanya mencantumkan langkah-langkah yang relevan dengan penjelasan saya):
cat
dengan argumentmp
.tmp
untuk membaca.Proses di sebelah kanan melakukan hal berikut:
tmp
, memotong file dalam proses.head
dengan argumen-1
.Satu-satunya titik sinkronisasi adalah bahwa kanan-3 menunggu kiri-3 untuk memproses satu baris penuh. Tidak ada sinkronisasi antara kiri-2 dan kanan-1, sehingga bisa terjadi dalam urutan apa pun. Urutan apa yang terjadi tidak dapat diprediksi: tergantung pada arsitektur CPU, pada shell, pada kernel, di mana core dari proses yang dijadwalkan, pada apa yang mengganggu CPU terima sekitar waktu itu, dll.
Cara mengubah perilaku
Anda tidak dapat mengubah perilaku dengan mengubah pengaturan sistem. Komputer melakukan apa yang Anda perintahkan. Anda menyuruhnya memotong
tmp
dan membacatmp
secara paralel, sehingga ia melakukan dua hal secara paralel.Ok, ada satu "pengaturan sistem" yang bisa Anda ubah: Anda bisa menggantinya
/bin/bash
dengan program lain yang bukan bash. Saya harap ini akan berjalan tanpa mengatakan bahwa ini bukan ide yang baik.Jika Anda ingin pemotongan terjadi sebelum sisi kiri pipa, Anda harus meletakkannya di luar pipa, misalnya:
atau
Saya tidak tahu mengapa Anda menginginkan ini. Apa gunanya membaca dari file yang Anda tahu kosong?
Sebaliknya, jika Anda ingin pengalihan output (termasuk pemotongan) terjadi setelah
cat
selesai membaca, maka Anda perlu buffer penuh data dalam memori, misalnyaatau menulis ke file yang berbeda dan kemudian pindahkan ke tempatnya. Ini biasanya merupakan cara yang kuat untuk melakukan hal-hal dalam skrip, dan memiliki keuntungan bahwa file tersebut ditulis secara penuh sebelum terlihat melalui nama aslinya.
The MoreUtils koleksi mencakup program yang tidak hanya itu, disebut
sponge
.Cara mendeteksi masalah secara otomatis
Jika tujuan Anda adalah untuk mengambil skrip yang ditulis dengan buruk dan secara otomatis mencari tahu di mana mereka melanggar, maka maaf, hidup tidak sesederhana itu. Analisis Runtime tidak akan andal menemukan masalah karena terkadang
cat
selesai membaca sebelum pemotongan terjadi. Analisis statis pada prinsipnya dapat melakukannya; contoh disederhanakan dalam pertanyaan Anda ditangkap oleh Shellcheck , tetapi mungkin tidak menangkap masalah serupa di skrip yang lebih kompleks.sumber
strace
(yaitu Linuxptrace
) untuk membuat semuaopen
panggilan sistem-untuk-membaca (dalam semua proses anak) tidur selama setengah detik, jadi ketika balapan dengan pemotongan, pemotongan akan hampir selalu menang.