Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Menangani Kebocoran Sumber Daya GDI

Kebocoran GDI (atau, penggunaan terlalu banyak objek GDI) adalah salah satu masalah yang paling umum. Ini akhirnya menyebabkan masalah rendering, kesalahan, dan/atau masalah kinerja. Artikel tersebut menjelaskan cara kami men-debug masalah ini.

Pada tahun 2016, ketika sebagian besar program dijalankan di kotak pasir di mana bahkan pengembang yang paling tidak kompeten pun tidak dapat merusak sistem, saya kagum menghadapi masalah yang akan saya bicarakan di artikel ini. Terus terang, saya berharap masalah ini hilang selamanya bersama dengan Win32Api. Namun demikian, saya menghadapinya. Sebelumnya, saya hanya mendengar cerita horor tentangnya dari pengembang lama yang lebih berpengalaman.

Masalahnya

Kebocoran atau penggunaan objek GDI dalam jumlah besar.

Gejala

  1. Kolom objek GDI pada tab Details dari Task Manager menunjukkan 10.000 kritis (jika kolom ini tidak ada, Anda dapat menambahkannya dengan mengklik kanan header tabel dan memilih Select Columns).
  2. Saat mengembangkan dalam C# atau dalam bahasa lain yang dijalankan oleh CLR, kesalahan informasi yang buruk berikut terjadi:
    Pesan:Terjadi kesalahan umum di GDI+.
    Sumber:System.Drawing
    Situs Sasaran:IntPtr GetHbitmap(System.Drawing.Color)
    Jenis:System.Runtime.InteropServices.ExternalException
    Kesalahan mungkin tidak terjadi pada setelan tertentu atau pada versi sistem tertentu, tetapi aplikasi Anda tidak akan dapat merender objek tunggal:
  3. Selama pengembangan di /С++, semua metode GDI, seperti Create%SOME_GDI_OBJECT%, mulai mengembalikan NULL.

Mengapa?

Sistem Windows tidak mengizinkan pembuatan lebih dari 65535 objek GDI. Jumlah ini, pada kenyataannya, sangat mengesankan dan saya hampir tidak dapat membayangkan skenario normal yang membutuhkan sejumlah besar objek. Ada batasan untuk proses – 10.000 per proses yang dapat dimodifikasi (dengan mengubah HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota nilai dalam kisaran 256 hingga 65535), tetapi Microsoft tidak menyarankan peningkatan batasan ini. Jika Anda masih melakukannya, satu proses akan dapat membekukan sistem sehingga tidak dapat merender pesan kesalahan sekalipun. Dalam hal ini, sistem hanya dapat dihidupkan kembali setelah reboot.

Bagaimana cara memperbaikinya?

Jika Anda hidup di dunia CLR yang nyaman dan terkelola, ada kemungkinan besar Anda mengalami kebocoran memori yang biasa terjadi di aplikasi Anda. Masalahnya tidak menyenangkan, tetapi ini adalah kasus yang cukup biasa. Setidaknya ada selusin alat hebat untuk mendeteksi ini. Anda perlu menggunakan profiler apa pun untuk melihat apakah jumlah objek yang membungkus sumber daya GDI (Sytem.Drawing.Brush, Bitmap, Pen, Region, Graphics) meningkat. Jika demikian, Anda dapat berhenti membaca artikel ini. Jika kebocoran objek pembungkus tidak terdeteksi, kode Anda menggunakan API GDI secara langsung dan ada skenario ketika mereka tidak dihapus

Apa yang direkomendasikan orang lain?

Panduan resmi Microsoft atau artikel lain tentang hal ini akan merekomendasikan Anda sesuatu seperti ini:

Temukan semua Buat %SOME_GDI_OBJECT% dan mendeteksi apakah DeleteObject yang sesuai (atau ReleaseDC untuk objek HDC) ada. Jika seperti DeleteObject ada, mungkin ada skenario yang tidak menyebutnya.

Ada versi yang sedikit lebih baik dari metode ini yang berisi langkah tambahan:

Unduh utilitas GDIView. Ini dapat menunjukkan jumlah objek GDI yang tepat berdasarkan jenisnya. Perhatikan bahwa jumlah total objek tidak sesuai dengan nilai di kolom terakhir. Tapi kita bisa menutup mata jika ini membantu mempersempit bidang pencarian.

Proyek yang saya kerjakan memiliki basis kode 9 juta catatan, kira-kira jumlah catatan yang sama terletak di perpustakaan pihak ketiga, ratusan panggilan fungsi GDI yang tersebar di lusinan file. Saya telah membuang banyak waktu dan energi sebelum saya mengerti bahwa analisis manual tanpa kesalahan adalah tidak mungkin.

Apa yang bisa saya tawarkan?

Jika metode ini tampaknya terlalu panjang dan melelahkan bagi Anda, Anda belum melewati semua tahap keputusasaan dengan yang sebelumnya. Anda dapat mencoba mengikuti langkah sebelumnya, tetapi jika tidak membantu, jangan lupakan solusi ini.

Dalam mengejar kebocoran, saya bertanya pada diri sendiri:Di mana objek bocor itu dibuat? Tidak mungkin menyetel breakpoint di semua tempat di mana fungsi API dipanggil. Selain itu, saya tidak yakin itu tidak terjadi di .NET Framework atau di salah satu perpustakaan pihak ketiga yang kami gunakan. Beberapa menit googling membawa saya ke utilitas Monitor API yang memungkinkan untuk mencatat dan melacak panggilan ke semua fungsi sistem. Saya dengan mudah menemukan daftar semua fungsi yang menghasilkan objek GDI, menemukan dan memilihnya di API Monitor. Kemudian, saya menyetel breakpoint.

Setelah itu, saya menjalankan proses debug di Visual Studio dan memilihnya di pohon Proses. Breakpoint kelima segera berhasil:

Saya menyadari bahwa saya akan tenggelam dalam torrent ini dan saya membutuhkan sesuatu yang lain. Saya menghapus breakpoint dari fungsi dan memutuskan untuk melihat log. Itu menunjukkan ribuan panggilan. Menjadi jelas bahwa saya tidak akan dapat menganalisisnya secara manual.

Tugasnya adalah Menemukan panggilan fungsi GDI yang tidak menyebabkan penghapusan . Log menampilkan semua yang saya butuhkan:daftar panggilan fungsi dalam urutan kronologis, nilai yang dikembalikan, dan parameter. Oleh karena itu, saya perlu mendapatkan nilai yang dikembalikan dari fungsi Create%SOME_GDI_OBJECT% dan menemukan panggilan DeleteObject dengan nilai ini sebagai argumen. Saya memilih semua catatan di API Monitor, memasukkannya ke dalam file teks dan mendapatkan sesuatu seperti CSV dengan pembatas TAB. Saya menjalankan VS, di mana saya bermaksud untuk menulis program kecil untuk penguraian, tetapi sebelum itu dapat dimuat, ide yang lebih baik muncul di benak saya:untuk mengekspor data ke dalam database dan menulis kueri untuk menemukan apa yang saya butuhkan. Itu adalah pilihan yang tepat karena memungkinkan saya untuk mengajukan pertanyaan dan mendapatkan jawaban dengan cepat.

Ada banyak alat untuk mengimpor data dari CSV ke database, jadi saya tidak akan membahas hal ini (mysql, mssql, sqlite).

Saya punya tabel berikut:

CREATE TABLE apicalls (
id int(11) DEFAULT NULL,
`Time of Day` datetime DEFAULT NULL,
Thread int(11) DEFAULT NULL,
Module varchar(50) DEFAULT NULL,
API varchar(200) DEFAULT NULL,
`Return Value` varchar(50) DEFAULT NULL,
Error varchar(100) DEFAULT NULL,
Duration varchar(50) DEFAULT NULL
)

Saya menulis fungsi MySQL berikut untuk mendapatkan deskriptor objek yang dihapus dari panggilan API:

CREATE FUNCTION getHandle(api varchar(1000))
RETURNS varchar(100) CHARSET utf8
BEGIN
DECLARE start int(11);
DECLARE result varchar(100);
SET start := INSTR(api,','); -- for ReleaseDC where HDC is second parameter. ex: 'ReleaseDC ( 0x0000000000010010, 0xffffffffd0010edf )'
IF start = 0 THEN
SET start := INSTR(api, '(');
END IF;
SET result := SUBSTRING_INDEX(SUBSTR(api, start + 1), ')', 1);
RETURN TRIM(result);
END

Dan akhirnya, saya menulis kueri untuk menemukan semua objek saat ini:

SELECT creates.id, creates.handle chandle, creates.API, dels.API deletedApi
FROM (SELECT a.id, a.`Return Value` handle, a.API FROM apicalls a WHERE a.API LIKE 'Create%') creates
LEFT JOIN (SELECT
d.id,
d.API,
getHandle(d.API) handle
FROM apicalls d
WHERE API LIKE 'DeleteObject%'
OR API LIKE 'ReleaseDC%' LIMIT 0, 100) dels
ON dels.handle = creates.handle
WHERE creates.API LIKE 'Create%';

(Pada dasarnya, itu hanya akan menemukan semua Hapus panggilan untuk semua Buat panggilan).

Seperti yang Anda lihat dari gambar di atas, semua panggilan tanpa satu Hapus telah ditemukan sekaligus.

Jadi, pertanyaan terakhir yang tersisa:Bagaimana menentukan, dari mana metode ini dipanggil dalam konteks kode saya? Dan di sini ada satu trik keren yang membantu saya:

  1. Jalankan aplikasi di VS untuk debugging
  2. Temukan di Api Monitor, lalu pilih.
  3. Pilih fungsi yang diperlukan di API dan tempatkan breakpoint.
  4. Terus klik 'Berikutnya' hingga akan dipanggil dengan parameter yang dimaksud (saya benar-benar melewatkan breakpoint bersyarat dari VS)
  5. Saat Anda menerima panggilan yang diperlukan, alihkan ke CS dan klik Hancurkan Semua .
  6. VS Debugger akan dihentikan tepat di tempat objek yang bocor dibuat dan yang perlu Anda lakukan hanyalah mencari tahu mengapa objek tersebut tidak dihapus.

Catatan:Kode ini ditulis untuk tujuan ilustrasi.

Ringkasan:

Algoritme yang dijelaskan rumit dan membutuhkan banyak alat, tetapi memberikan hasil yang jauh lebih cepat dibandingkan dengan pencarian bodoh melalui basis kode yang besar.

Berikut adalah ringkasan dari semua langkah:

  1. Menelusuri kebocoran memori objek pembungkus GDI.
  2. Jika ada, hilangkan dan ulangi langkah 1.
  3. Jika tidak ada kebocoran, cari panggilan ke fungsi API secara eksplisit.
  4. Jika jumlahnya tidak besar, cari skrip di mana objek tidak dihapus.
  5. Jika jumlahnya besar atau sulit dilacak, unduh API Monitor dan atur untuk mencatat panggilan fungsi GDI.
  6. Jalankan aplikasi untuk debugging di VS.
  7. Reproduksi kebocoran (ini akan menginisialisasi program untuk menyembunyikan objek yang diuangkan).
  8. Terhubung dengan API Monitor.
  9. Reproduksi kebocoran.
  10. Salin log ke file teks, impor ke database mana pun yang ada (skrip yang ditampilkan dalam artikel ini adalah untuk MySQL, tetapi dapat dengan mudah diadopsi untuk sistem manajemen database relasional apa pun).
  11. Bandingkan metode Buat dan Hapus (Anda dapat menemukan skrip SQL di artikel ini di atas), dan temukan metode tanpa panggilan Hapus.
  12. Setel breakpoint di API Monitor saat memanggil metode yang diperlukan.
  13. Terus klik Lanjutkan hingga metode dipanggil dengan parameter yang diperoleh kembali.
  14. Saat metode dipanggil dengan parameter yang diperlukan, klik Hancurkan Semua di VS.
  15. Cari tahu mengapa objek ini tidak dihapus.

Semoga artikel ini bermanfaat dan membantu Anda menghemat waktu.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Tren Basis Data 2019 – SQL vs. NoSQL, Basis Data Teratas, Penggunaan Basis Data Tunggal vs. Banyak

  2. 3 Statistik I/O Buruk yang Menunda Kinerja Kueri SQL

  3. Cara Menginstal Cassandra v3 di CentOS 6

  4. Memahami redo log group vs file vs member

  5. Mengurangi Biaya Hosting Basis Data Anda:DigitalOcean vs. AWS vs. Azure