English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

gäst
1 / ?

ThreadLocal: Korrekt idiom, fel era

Java EE Servlet-containrar, cirka 1999: en tråd per request. En tråd hanterar exakt en request från start till slut, sedan avslutas den. ThreadLocal lagrar ett värde knutet till den aktuella tråden. Med en-tråd-per-request tillhör ett värde som lagras i ThreadLocal exakt en request. Idiomet: korrekt.

Trådpooler ändrade kontraktet. En tråd hanterar request A, lagrar principal A i ThreadLocal, avslutar request A och återgår till poolen. Trådpooler återställer inte trådtillstånd. ThreadLocal.remove() städar upp, men att anropa det kräver explicit disciplin. När disciplinen brister kör request B på samma tråd och läser principal A i ThreadLocal.

5-stegs-läckan:

1. Request A anländer. Servern tilldelar Thread-7.

2. Thread-7 anropar ThreadLocal.set(principal_A) när begäran startar.

3. Begäran A slutförs. Thread-7 återgår till trådpoolen. ThreadLocal.remove() anropas inte.

4. Begäran B anländer. Servern tilldelar Thread-7 (återanvändning av trådpool).

5. Thread-7 läser ThreadLocal.get(): returnerar principal_A. Begäran B körs under fel identitet.

Varför tester missar det

Enhetstester körs isolerat: ingen trådpool, ingen återanvändning. Integrationstester använder nya trådar eller återställer tillstånd mellan tester. Belastningstester värms upp med korrekta användare och låg samtidighet. Felet uppstår endast vid återanvändning av trådpool med överlappande begäran, ett tillstånd som förekommer i produktion under normal trafik, inte i någon testkonfiguration som söker efter det.

Säkerhetskonsekvensen

Användare A:s principal läcker in i användare B:s begäran. Inte en krasch. Inte ett undantag. En tyst säkerhetsgränsöverträdelse: användare B utför åtgärder som användare A, läser användare A:s data eller kringgår användare B:s behörigheter. Systemet genererar inget fel. Loggar visar att begäran B godkändes. Allt ser korrekt ut.

De fem stegen

De fem stegen i en ThreadLocal-läcka spelar exakt roll: felet inträffar inte när den felaktiga koden körs. Det inträffar tidigare, i avsaknad av ett städsteg.

Gå igenom de 5 stegen. Vid vilket steg uppstår felet, och varför skulle en testsvit missa det?

Scope-anknutna värden

ThreadLocal kopplar ett värde till en tråd. En tråd lever längre än en request. Felaktig livstid.

Scope-attached values kopplar ett värde till en arbetsenhet. När arbetsenheten avslutas, försvinner värdet med den. Ingen explicit cleanup. Inget remove() att glömma.

Java 21: ScopedValue

// ThreadLocal (DEFECT carrier)
static final ThreadLocal<Principal> PRINCIPAL = new ThreadLocal<>();
PRINCIPAL.set(principal);           // sätts vid request start
// ... request-hantering ...
PRINCIPAL.remove();                 // MÅSTE anropas; glöms ofta bort

// ScopedValue (KORREKT bärare)
static final ScopedValue<Principal> PRINCIPAL = ScopedValue.newInstance();
ScopedValue.where(PRINCIPAL, principal).run(() -> {
// ... hantering av begäran ...
// värdet tas automatiskt bort när run() returnerar
});

Go: context.Context

// context.Context bär värden explicit; scope = funktionsanropskedja
ctx := context.WithValue(r.Context(), principalKey, principal)
handleRequest(ctx)  // ctx skickas explicit; försvinner när funktionen returnerar

Python asyncio: contextvars.ContextVar

# ContextVar är begränsad till varje async task
PRINCIPAL: ContextVar[str] = ContextVar('principal')
token = PRINCIPAL.set(principal)    # sätts endast för denna task
# ... hantering av task ...
PRINCIPAL.reset(token)              # eller: scope avslutas med task

Det gemensamma för dessa: livslängden matchar arbetsenheten. När requesten avslutas (run() returnerar, funktionen returnerar, tasken slutförs), avslutas värdet. Ingen cleanup att glömma. Ingen pool att förstöra.

Identifiera & Ersätt

En Java EE-applikation lagrar tenant-ID i en ThreadLocal vid begärans start. Under hög belastning dyker tenant A:s ID upp i begäran från tenant B. Tenant B:s frågor returnerar tenant A:s data. Inget undantag kastas. Felet syns bara vid produktionslasttestning.

Vilken MOAD beskriver detta? Vilken bärare gjorde felet möjligt? Vad ersätter den, och vilken egenskap hos ersättningen förhindrar läckan?