Mungkin, hampir semua penduduk Habr tahu apa dikotomi itu dan bagaimana menggunakannya untuk menangkap singa di padang pasir. Kesalahan dalam program juga dapat ditangkap dengan dikotomi, terutama karena tidak adanya informasi diagnostik yang waras.

Setelah men-debug proyek saya di PHP / Laravel, saya melihat kesalahan ini di browser:

Ini, setidaknya, aneh, karena, dilihat dari deskripsi di RFC 2616, kesalahan 502 berarti bahwa "Server, yang bertindak sebagai gateway atau proxy, menerima respons yang salah dari server upstream." Dalam kasus saya, tidak ada gateway, tidak ada proxy antara server Web dan browser, server Web nginx berjalan di bawah virtualbox, dan mengirimkan konten Web secara langsung, tanpa perantara. Log nginx memiliki ini:
2018/06/20 13:42:41 [error] 2791#2791: *2206 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 192.168.10.1, server: colg.test, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.1-fpm.sock:", host: "colg.test"
Kata-kata "server upstream" dalam deskripsi kesalahan 502 ("server upstream" dalam versi bahasa Inggris asli RFC) menyarankan beberapa server jaringan tambahan pada jalur permintaan dari browser ke nginx, tetapi, tampaknya, dalam hal ini, yang disebutkan dalam pesan modul PHP-FPM, sebagai program server, bertindak sebagai server yang sangat hulu ini. Dalam log PHP, ini adalah:
[20-Jun-2018 13:42:41] WARNING: [pool www] child 26098 exited on signal 11 (SIGSEGV - core dumped) after 102247.908379 seconds from start
Sekarang jelas di mana masalah itu muncul, tetapi penyebabnya tidak jelas. PHP baru saja jatuh ke dump inti, tidak menampilkan informasi tentang pada titik apa dalam penafsiran program PHP terjadi kesalahan. Jadi sudah tiba saatnya untuk menangkap singa di gurun - untuk menggunakan metode favorit saya debugging dengan dikotomi dalam kasus seperti itu. Mengantisipasi keberatan dalam komentar, saya perhatikan bahwa orang dapat menggunakan debugger di sini, misalnya, XDebug yang sama, tetapi dikotomi lebih menarik. Selain itu, giliran akan datang ke XDebug.
Jadi, dalam cara memproses permintaan Web, saya mengatur output diagnostik paling sederhana, dengan penyelesaian lebih lanjut dari program, untuk memastikan bahwa tidak ada kesalahan terjadi pada tempat pemasangannya:
echo βI am hereβ; die();
Sekarang halaman yang buruk tampak seperti ini:

Setelah meletakkan perintah yang tertulis di atas, pertama di awal, dan kemudian di akhir jalur pemrosesan permintaan Web, saya menemukan bahwa kesalahan (siapa yang meragukannya!) Terjadi di suatu tempat di antara dua titik ini. Setelah mengatur diagnostik di tengah jalur permintaan Web, saya menemukan bahwa kesalahan muncul di suatu tempat di dekat akhir. Setelah beberapa iterasi seperti itu, saya menyadari bahwa kesalahan tidak terjadi pada pengontrol arsitektur Laravel MVC itu sendiri, tetapi sudah di pintu keluar darinya, ketika menampilkan tampilan, yang paling sederhana di sini, dalam semangat ini:
@extends('layouts.app') @section('content') <div> <div class="panel-heading">Myservice</div> <div class="panel-body"></div> </div> @endsection
Seperti yang Anda lihat, template tampilan tidak mengandung kode PHP (mesin template Laravel memungkinkan Anda untuk menggunakan kode PHP dalam tampilan), dan masalahnya tentu tidak ada di sini. Tetapi di atas kita melihat bahwa tampilan ini mewarisi templat layouts.app, jadi lihat di sana. Itu sudah lebih rumit: ada elemen navigasi, formulir masuk, dan hal-hal lain yang umum untuk semua halaman layanan. Mengabaikan semua yang ada di sana, saya hanya akan memberikan garis, karena yang gagal muncul, ditemukan semua dikotomi yang sama. Inilah barisnya:
<script> window.bkConst = {!! (new App\Src\Helpers\UtilsHelper())->loadBackendConstantsAsJSData() !!}; </script>
Di sini, hanya dalam kode templat tampilan, PHP digunakan. Itu adalah "pesona" saya - derivasi dari konstanta backend, dalam bentuk JS-code, untuk menggunakannya di frontend, atas nama prinsip KERING. Metode loadBackendConstantsAsJSData daftar beberapa kelas dengan konstanta yang diperlukan di frontend. Kesalahan terjadi pada metode addClassConstants yang digunakan olehnya, di mana introspeksi PHP digunakan untuk mendapatkan daftar konstanta kelas:
private function addClassConstants(string $classFullName, array &$constantsArray) { $r = new ReflectionClass($classFullName); $result = []; $className = $r->getShortName(); $classConstants = $r->getConstants(); foreach($classConstants as $name => $value) { if (is_array($value) || is_object($value)) { continue; } $result["$className::$name"] = $value; } $constantsArray = array_merge($constantsArray, $result); }
Setelah mencari di antara kelas dengan konstanta yang diteruskan ke metode ini, ternyata alasan untuk semuanya - kelas ini dengan konstanta - adalah jalur ke metode REST API.
class APIPath { const API_BASE_PATH = '/api/v1'; const DATA_API = self::API_BASE_PATH . "/data"; ... const DATA_ADDITIONAL_API = DATA_API . "/additional"; }
Ada beberapa baris di dalamnya, dan untuk menemukan yang tepat, dikotomi berguna lagi. Sekarang, saya harap semua orang memperhatikan bahwa self :: tidak ada dalam definisi konstanta di depan nama konstan DATA_API. Setelah menambahkannya ke tempat yang seharusnya, semuanya bekerja.
Setelah memutuskan bahwa masalahnya ada dalam mekanisme introspeksi, saya mulai menulis contoh minimal untuk mereproduksi bug:
class SomeConstants { const SOME_CONSTANT = SOME_NONSENSE; } $r = new \ReflectionClass(SomeConstants::class); $r->getConstants();
Namun, ketika menjalankan skrip ini, PHP tidak akan crash, tetapi mengeluarkan peringatan yang benar-benar waras.
PHP Warning: Use of undefined constant SOME_NONSENSE - assumed 'SOME_NONSENSE' (this will throw an Error in a future version of PHP) in /home/vagrant/code/colg/_tmp/1.php on line 17
Pada titik ini, saya sudah yakin bahwa masalah memanifestasikan dirinya tidak hanya ketika memuat situs, tetapi juga ketika mengeksekusi kode yang ditulis di atas melalui baris perintah. Satu-satunya perbedaan antara runtime dan skrip minimal adalah keberadaan konteks Laravel: kode masalah dijalankan melalui utilitas perajinnya. Jadi di bawah Laravel ada semacam perbedaan. Untuk memahami apa itu, saatnya menggunakan debugger. Menjalankan kode di bawah xdebug, saya melihat bahwa crash terjadi setelah memanggil metode ReflectionClass :: getConstants dalam metode Illuminate \ Foundation \ Bootstrap \ HandleExceptions :: handleError, yang terlihat sangat sederhana:
public function handleError($level, $message, $file = '', $line = 0, $context = []) { if (error_reporting() & $level) { throw new ErrorException($message, 0, $level, $file, $line); } }
Utas eksekusi sampai di sana setelah melemparkan pengecualian karena kesalahan yang sangat dalam menggambarkan konstanta dari mana semuanya dimulai, dan PHP macet ketika mencoba melempar ErrorException. Pengecualian dalam handler pengecualian ... Saya langsung ingat
kesalahan ganda yang terkenal. Jadi, untuk menyebabkan kegagalan, Anda perlu menginstal penangan pengecualian yang mirip dengan Laravel. Sedikit lebih tinggi dalam kode hanyalah metode bootstrap yang melakukan ini:
Sekarang, contoh minimal yang difinalisasi tampak seperti ini:
<?php class SomeConstants { const SOME_CONSTANT = SOME_NONSENSE; } function handleError() { throw new ErrorException(); } set_error_handler('handleError'); set_exception_handler('handleError'); $r = new \ReflectionClass(SomeConstants::class); $r->getConstants();
dan peluncurannya terus mengemas penerjemah versi PHP 7.2.4 ke dalam dump inti.
Tampaknya ada rekursi tak berujung di sini - saat menangani pengecualian dari kesalahan asli, pengecualian berikutnya dilemparkan ke handleException, ditangani lagi di handleException, dan seterusnya hingga tak terbatas. Selain itu, untuk mereproduksi kegagalan, Anda perlu mengatur error_handler dan exception_handler, jika hanya satu saja yang disetel, maka masalahnya tidak terjadi. Itu juga gagal untuk hanya melemparkan pengecualian, bukannya melemparkan kesalahan, tampaknya ini bukan rekursi yang biasa, tetapi sesuatu seperti ketergantungan melingkar.
Setelah itu, saya memeriksa masalah di berbagai versi PHP (terima kasih, Docker!). Ternyata kegagalan hanya memanifestasikan dirinya, dimulai dengan versi PHP 7.1, versi PHP sebelumnya berfungsi dengan benar - mereka bersumpah pada pengecualian ErrorException yang tidak tertangkap.
Kesimpulan apa yang bisa ditarik dari semua ini?
- Debugging dengan dikotomi, meskipun merupakan metode debug antediluvian, tetapi kadang-kadang mungkin diperlukan, terutama dalam kondisi kurangnya informasi diagnostik
- Menurut pendapat saya, kesalahan 502 tidak dapat dipahami, baik pesan tentang hal itu ("Gerbang buruk") dan penguraiannya dalam RFC tentang "respons yang salah dari server upstream". Meskipun, jika Anda menganggap modul yang terhubung ke server Web sebagai program server, Anda dapat memahami arti dari decoding kesalahan dalam RFC. Namun, katakanlah PHP-FPM yang sama dalam dokumentasi disebut modul dan bukan server.
- Drive penganalisa statis, ia akan segera melaporkan kesalahan dalam deskripsi konstanta. Tetapi kemudian bug tidak akan tertangkap.
Biarkan saya menyelesaikan ini, terima kasih atas perhatian Anda!
Laporan Bag -
dikirim .
UPD: bug
diperbaiki Dilihat oleh kode, namun berakhir pada mekanisme refleksi - dalam penanganan kesalahan metode ReflectionClass :: getConstants