ThreadLocal: सही वाक्यांश, गलत युग
Java EE सervlet कंटेनर, 1999 के आसपास: एक अनुरोध प्रति थ्रेड। एक थ्रेड ने शुरुआत से लेकर अंत तक एक ही अनुरोध का सामना किया, फिर समाप्त हो गया। ThreadLocal का मान वर्तमान थ्रेड के साथ लिंक किया गया। एक-थ्रेड-पर-अनुरोध के साथ, ThreadLocal में स्टोर की गई कोई भी मान केवल एक ही अनुरोध के संबंध में होती है। वाक्यांश: सही।
थ्रेड पूल ने अनुबंध बदल दिया। एक थ्रेड अनुरोध ए का सामना करता है, प्रिंसिपल ए को ThreadLocal में सेट करता है, अनुरोध ए का अंत कर लेता है, और पूल वापस जाता है। थ्रेड पूल थ्रेड के राज्य को नहीं रीसेट करते हैं। ThreadLocal.remove() सफाई करता है, लेकिन इसका मतलब स्पष्ट अनुशासन की आवश्यकता होती है। जब अनुशासन विफल होता है, तो अनुरोध बी उसी थ्रेड पर चलता है और ThreadLocal में प्रिंसिपल ए को पढ़ता है।
पांच-चरणीय प्रवाह:
1. अनुरोध ए पहुंचता है। सर्वर थ्रेड-7 को आवंटित करता है।
2. थ्रेड-7 ने अनुरोध की शुरुआत में ThreadLocal.set(principal_A) सेट किया।
3. अनुरोध ए समाप्त होता है। थ्रेड-7 पूल वापस जाता है। ThreadLocal.remove() कॉल नहीं की जाती।
4. अनुरोध बी पहुंचता है। सर्वर थ्रेड-7 (पूल रीसायकल) को आवंटित करता है।
5. थ्रेड-7 ने ThreadLocal.get() पढ़ा: principal_A वापस करता है। अनुरोध बी गलत पहचान के तहत चलता है।
क्यों टेस्ट इसे छोड़ देते हैं
सINGLE टेस्ट आइसोलेशन में चल रहे हैं: कोई थ्रेड पूल नहीं, कोई रीप्रयूज नहीं। इंटीग्रेशन टेस्ट फ्रेश थ्रेड्स का उपयोग करते हैं या टेस्ट के बीच राज्य रीसेट करते हैं। लोड टेस्ट सही यूजर के साथ वॉर्म-अप करते हैं और निम्न सिमुलेशन। यह विकृति थ्रेड पूल रीसायकल के साथ ओवरलैपिंग अनुरोधों की स्थिति में उत्पन्न होती है, जो किसी भी टेस्ट कॉन्फ़िगरेशन में जाँच करने के लिए नहीं होती है जो इसे जाँच करता है।
सुरक्षा का परिणाम
यूजर ए का प्रिंसिपल यूजर बी के अनुरोध में फैलता है। एक क्रैश नहीं। एक अपवाद नहीं। एक चुपके सुरक्षा सीमा लांछन: यूजर बी ने यूजर ए के काम करें, यूजर ए के डेटा पढ़ें, या यूजर बी के अनुमतियों को बायपास करें। प्रणाली कोई त्रुटि नहीं उत्पन्न करती। लॉग्स का अनुरोध बी को मान्य दिखता है। सब कुछ सही दिखता है।
पांच चरण
ThreadLocal के पांच चरण विशेष रूप से महत्वपूर्ण होते हैं: विकृति उस समय नहीं होती है जब गलत कोड चलता है। यह पहले होता है, जब सफाई चरण नहीं होता है।
क्षेत्र-संबद्ध मान
ThreadLocal को थ्रेड से एक मान जोड़ता है। एक थ्रेड एक अनुरोध के जीवनकाल से अधिक रहता है। असंगति।
क्षेत्र-संबद्ध मान को काम के एक इकाई से एक मान जोड़ता है। जब इकाई का काम समाप्त होता है, तब मान भी समाप्त हो जाता है। व्यक्तिगत साफ़कर्ता। remove() भूलने की नहीं।
Java 21: ScopedValue
// ThreadLocal (विकृति वाहक)
static final ThreadLocal<Principal> PRINCIPAL = new ThreadLocal<>();
PRINCIPAL.set(principal); // सेट करता है अनुरोध शुरू पर
// ... अनुरोध प्रसंस्करण ...
PRINCIPAL.remove(); // MUST be called; often forgotten
// ScopedValue (सही वाहक)
static final ScopedValue<Principal> PRINCIPAL = ScopedValue.newInstance();
ScopedValue.where(PRINCIPAL, principal).run(() -> {
// ... अनुरोध प्रसंस्करण ...
// मान ऑटोमेटिकली गायब होता है जब run() वापस आता है
});
Go: context.Context
// context.Context मान व्यक्तिगत रूप से ले जाता है; क्षेत्र = फंक्शन कॉल चेन
ctx := context.WithValue(r.Context(), principalKey, principal)
handleRequest(ctx) // ctx पास किया जाता है विशेषतया; गायब होता है जब फंक्शन वापस आता है
Python asyncio: contextvars.ContextVar
# ContextVar प्रत्येक असिंक्रोन टास्क के लिए सीमित
PRINCIPAL: ContextVar[str] = ContextVar('principal')
token = PRINCIPAL.set(principal) # सेट इस काम के लिए केवल
# ... कार्य प्रबंधन ...
PRINCIPAL.reset(token) # या: कार्य समाप्त होता है
इनकी संपत्ति: जीवनकाल कार्य के कार्य की इकाई के साथ संबंधित होता है। जब अनुरोध समाप्त होता है (run() वापस आता है, फ़ंक्शन वापस आता है, कार्य पूरा होता है), मूल्य समाप्त होता है। सफाई भूलने की चिंता नहीं। कोई पूल को क्षतिग्रस्त नहीं कर सकता।
पहचानें और बदलें
एक Java EE अनुप्रयोग अनुरोध की शुरुआत में टेनेंट आईडी को एक ThreadLocal में स्टोर करता है। उच्च भार के तहत, टेनेंट बी का आईडी टेनेंट ए के अनुरोधों में दिखाई देता है। टेनेंट बी के प्रश्न टेनेंट ए के डेटा को वापस कर देते हैं। कोई अपवाद नहीं फेंका जाता। यह विकृति केवल उत्पादन लोड टेस्टिंग में दिखाई देती है।