un

guest
1 / ?
back to lessons

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.

Lalui langkah 5. Di langkah mana kelemahan terjadi, dan mengapa suatu suite ujian melewatinya?

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.

MOAD apa yang deskripsinya ini? Siapa carrier yang membuat kecacatan mungkin? Apa yang menggantikannya, dan apa properti pengganti yang mencegah kebocoran?