ThreadLocal: Idiom Benar, Era Salah
Java EE Servlet containers, sekitar tahun 1999: satu thread per permintaan. Thread mengatasi persis satu permintaan dari awal hingga akhir, kemudian berhenti. ThreadLocal menyimpan nilai yang diatur ke thread saat ini. Dengan satu-thread-per-mintaan, nilai yang disimpan dalam ThreadLocal milik permintaan yang tepat. Idiom: benar.
Kolam thread mengubah kontrak. Thread mengatasi permintaan A, menyimpan principal A dalam ThreadLocal, menyelesaikan permintaan A, dan kembali ke kolam. Kolam thread tidak mengatur ulang state thread. ThreadLocal.remove() membersihkan, tetapi memanggilnya membutuhkan disiplin eksplisit. Ketika disiplin gagal, permintaan B dijalankan pada thread yang sama dan membaca principal A dalam ThreadLocal.
Cerita 5 langkah kebocoran:
1. Permintaan A tiba. Server menetapkan Thread-7.
2. Thread-7 menetapkan ThreadLocal.set(principal_A) pada awal permintaan.
3. Permintaan A selesai. Thread-7 kembali ke kolam. ThreadLocal.remove() tidak dipanggil.
4. Permintaan B tiba. Server menetapkan Thread-7 (penggunaan kembali kolam).
5. Thread-7 membaca ThreadLocal.get(): mengembalikan principal_A. Permintaan B dijalankan di bawah identitas yang salah.
Mengapa Ujian Melewatinya
Ujian unit berjalan sendirian: tidak ada kolam thread, tidak ada penggunaan kembali. Ujian integrasi menggunakan thread segar atau mengatur ulang state antara ujian. Ujian beban memanas dengan pengguna yang benar & koncurensi rendah. Kelemahan hanya muncul di bawah penggunaan kembali kolam thread dengan permintaan yang saling bertumpang gilir, kondisi yang muncul di produksi di bawah lalu lintas normal, bukan dalam konfigurasi ujian yang memeriksanya.
Akibat Keamanan
Principal pengguna A mengalir ke permintaan pengguna B. Bukan kegagalan. Bukan pengecualian. Pelanggaran batas keamanan diam: pengguna B melakukan aksi sebagai pengguna A, membaca data pengguna A, atau melampaui izin pengguna B. Sistem menghasilkan tidak ada kesalahan. Log menunjukkan permintaan B yang diotorisasi. Semua terlihat benar.
Langkah Lima
Langkah 5 dari kebocoran ThreadLocal penting: kelemahan tidak terjadi pada saat kode yang salah dijalankan. Kelemahan terjadi lebih awal, dalam ketiadaan langkah bersih.
Nilai yang Dibawa Oleh Lingkungan
ThreadLocal menempelkan nilai pada thread. Thread lebih lama dari permintaan. Kesalahan cocok.
Nilai yang terpasang oleh lingkungan menempelkan nilai pada unit kerja. Ketika unit kerja berakhir, nilai juga berakhir. Diperlukan pembuangan. Tidak perlu remove() untuk melupakan.
Java 21: ScopedValue
// ThreadLocal (penyambung kecacatan)
static final ThreadLocal<Principal> PRINCIPAL = new ThreadLocal<>();
PRINCIPAL.set(principal); // tetapkan pada awal permintaan
// ... penanganan permintaan ...
PRINCIPAL.remove(); // HARUS dipanggil; sering dilupakan
// ScopedValue (penyambung yang BENAR)
static final ScopedValue<Principal> PRINCIPAL = ScopedValue.newInstance();
ScopedValue.where(PRINCIPAL, principal).run(() -> {
// ... penanganan permintaan ...
// nilai secara otomatis hilang ketika run() kembali
});
Go: context.Context
// context.Context membawa nilai secara eksplisit; lingkungan = rantai panggilan fungsi
ctx := context.WithValue(r.Context(), principalKey, principal)
handleRequest(ctx) // ctx ditransfer secara eksplisit; hilang ketika fungsi kembali
Python asyncio: contextvars.ContextVar
# ContextVar terpasang pada setiap tugas async
PRINCIPAL: ContextVar[str] = ContextVar('principal')
token = PRINCIPAL.set(principal) # tetapkan untuk tugas ini saja
# ... penanganan tugas ...
PRINCIPAL.reset(token) # atau: lingkaran akhir dengan tugas
Sifat yang mereka bagikan: masa hidup cocok dengan unit kerja. Ketika permintaan berakhir (run() kembali, fungsi kembali, tugas selesai), nilai berakhir. Tidak ada pengurasan untuk dilupakan. Tidak ada kolam untuk diubah.
Identifikasi & Ganti
Aplikasi Java EE menyimpan ID penyewa dalam ThreadLocal pada awal permintaan. Dalam beban tinggi, ID penyewa A muncul dalam permintaan dari penyewa B. Kueri penyewa B kembali data penyewa A. Tidak ada pengecualian yang dilemparkan. Ke cacat hanya muncul dalam pengujian beban produksi.