3,200% CPU उपयोग
(josephmate.github.io)- Java 17 एप्लिकेशन में 32ों कोर पूरी तरह saturation में चले गए और CPU उपयोग 3,200% तक उछल गया; thread dump को CPU time के अनुसार sort करने पर पता चला कि कई threads
TreeMap.put()में फँसे हुए थे - शुरुआत में संदिग्ध loop code अक्षम था, लेकिन सिर्फ
O(N lg(M))complexity और input size की जाँच से outage-स्तर के runtime की व्याख्या नहीं हो सकी - असली कारण यह था कि कई threads साझा TreeMap को बिना सुरक्षा के modify कर रहे थे, जिससे red-black tree के अंदर cycle बन गया और search/insert अनंत loop में फँस गए
- साधारण repro code,
ExecutorService, और gRPC service — सभी में यही समस्या दिखी, और thread pool ने NPE को standard output पर नहीं दिखाया, जिससे root cause ढूँढना और कठिन हो गया - fix सिर्फ
Collections.synchronizedMapयाConcurrentHashMapपर switch करने से पूरा नहीं होता; executor exception handling, CPU/NPE alerts, static analysis, और multithreaded tests भी साथ होने चाहिए
आउटेज के लक्षण और पहला सुराग
- मशीन लगभग इतनी खराब हो गई थी कि
sshसे लॉगिन करना भी मुश्किल था, और CPU उपयोग 3,200% तक पहुँच गया- host के 32ों कोर पूरी तरह उपयोग में थे
- यह उस पुराने bug से अलग था जिसमें सिर्फ 1 core 100% उपयोग हो रहा था
- Java 17 runtime के thread dump में CPU time शामिल था, और CPU time के आधार पर sort करने पर एक जैसे threads सामने आए
- stack
java.util.TreeMap.put()की ओर इशारा कर रहा था - application code location
BusinessLogic.someFunction(BusinessLogic.java:29)थी
- stack
शुरू में संदिग्ध code और खारिज की गई परिकल्पनाएँ
- समस्या वाली जगह का code
unrelatedObjectsपर iterate कर रहा था, लेकिन loop body में केवलrelatedObjectका उपयोग हो रहा था
for (SomeOtherType unrelatedObject : unrelatedObjects) {
treeMap.put(relatedObject.a(), relatedObject.b()); // line 29
}
- इस code को नीचे की तरह सिर्फ एक
putतक घटाया जा सकता था
treeMap.put(relatedObject.a(), relatedObject.b());
unrelatedObjectsका उपयोग function के आगे के हिस्से में होता था, इसलिए parameter को पूरी तरह हटाया नहीं जा सकता था- संभव था कि refactoring के दौरान
unrelatedObjectका उपयोग हट गया हो - unit test में
treeMapऔरunrelatedObjectsदोनों को 1,000,000 entries तक बढ़ाने पर भी समस्या reproduce नहीं हुई- अगर
unrelatedObjectsका आकारNऔरtreeMapका आकारMहै, तो complexityO(N lg(M))है - लगभग 1 मिनट के runtime के लिए 10 करोड़ से 100 करोड़ entries चाहिए होंगी, ऐसा अनुमान था
- यह उस धारणा से भी मेल नहीं खाता था कि वास्तविक application में दोनों data structures 1,000 entries से ऊपर नहीं जाते
- अगर
बिना सुरक्षा वाला TreeMap और अनंत loop
TreeMapfield की परिभाषा यह थी
private final Map<K,V> treeMap = new TreeMap<>();
- कई threads इस TreeMap को access कर रहे थे, लेकिन कोई synchronization या protection नहीं था
- Java का
TreeMapred-black tree के रूप में implement होता है, और concurrent modification से internal node links टूटने पर cycle बन सकता है - search करते समय या नया value insert करते समय code उस cycle का पीछा करते हुए अनंत loop में फँस सकता है
सरल repro प्रयोग
- साझा
TreeMapको कई threads द्वारा random updates के साथ test करने वाला प्रयोग लिखा गया
for (int i = 0; i < numThreads; i++) {
threads.add(new Thread(() -> {
Random random = new Random();
for(int j = 0; j < numUpdates; j++) {
try {
treeMap.put(random.nextInt(1000), random.nextInt(1000));
} catch (NullPointerException e) {
// let it keep going so we can reproduce the issue.
}
}
}));
}
- पूरा project SimpleRepro.java में है
- शुरुआत में
try/catchमुख्य कारण जैसा लगाtry/catchन हो तो कुछ threadsNullPointerExceptionसे मर जाते थे और program रुक जाता थाtry/catchजोड़ने के बाद कुछ runs में 500% CPU उपयोग देखा गया
- race condition केवल data corruption या deadlock ही नहीं बनाती; यह data structure को ऐसे रूप में भी बिगाड़ सकती है जहाँ performance outage के रूप में अनंत loop हो जाए
TreeMap के अंदर cycle की पुष्टि
- reflection के जरिए
TreeMapकेroot,left,right,key,colorfields तक पहुँचकर nodes और colors print करने वाला प्रयोग लिखा गया - traversal के दौरान अगर पहले से देखी गई
TreeMap.Entryफिर मिलती थी, तो उसे cycle माना गया
private void print(
Object treeMapEntry, String tabs, IdentityHashMap<Object, Object> visited
) throws Exception {
if (treeMapEntry != null && !visited.containsKey(treeMapEntry)) {
visited.put(treeMapEntry, treeMapEntry);
print(treeMapEntryLeft.get(treeMapEntry), tabs + " ", visited);
System.out.println(tabs + treeMapEntryKey.get(treeMapEntry) + ":"
+ (treeMapEntryColor.getBoolean(treeMapEntry) ? "BLACK" : "RED"));
print(treeMapEntryRight.get(treeMapEntry), tabs + " ", visited);
} else if (treeMapEntry != null && visited.containsKey(treeMapEntry)) {
System.out.println(tabs + treeMapEntryKey.get(treeMapEntry) + ":"
+ (treeMapEntryColor.getBoolean(treeMapEntry) ? "BLACK" : "RED")
+ " CYCLE"
);
}
}
- Java module access restriction खोलने के लिए run करते समय यह JVM argument चाहिए था
--add-opens java.base/java.util=ALL-UNNAMED
पुराने संबंधित उदाहरण और इस केस का फर्क
- इसी तरह की समस्या पहले भी दर्ज की जा चुकी थी
- इस केस में वास्तविक repro code, अलग-अलग languages में reproduction की कोशिश, और
TreeMapव red-black tree के लिए modification ideas भी देखे गए
ExecutorService में अधिक वास्तविक reproduction
- सिर्फ NPE को ignore करने वाला code अवास्तविक लग सकता है, लेकिन
ExecutorServiceमें exceptions आसानी से छिप सकती हैं
final ExecutorService pool = Executors.newFixedThreadPool(numThreads);
final TreeMap<Integer,Integer> treeMap = new TreeMap<>();
Random random = new Random();
for (int i = 0; i < numThreads*numUpdatesPerThread; i++) {
pool.submit(() -> {
treeMap.put(random.nextInt(10000), random.nextInt(10000));
});
}
- पूरा code ExecutorUncaughtRepro.java में है
- इसे चलाने पर program रुक गया और thread dump ने
TreeMap.put()पर अटके threads दिखाए - standard output पर कुछ भी नहीं था
- thread pool ने NPE को निगल लिया था, इसलिए समस्या का signal दिखा ही नहीं
- वास्तविक स्थिति भी ऐसी ही थी
- अगर आप thread pool को सीधे manage करते हैं, तो ये कदम ज़रूरी हैं
- thread factory के जरिए uncaught exception handler register करें
- returned
Futureको handle करें औरfuture.get()सेExecutionExceptionमें लिपटा NPE देखें
gRPC service में भी वही समस्या
- gRPC service जैसी thread-pool आधारित service में भी बिना सुरक्षा वाला
TreeMapयही समस्या पैदा कर सकता है
@Override
public void addReceipt(
ReceiptProcessorServiceOuterClass.AddReceiptRequest req,
StreamObserver<ReceiptProcessorServiceOuterClass.AddReceiptResponse> responseObserver
) {
int timestamp = req.getTimestamp();
int totalPrice = req.getTotalPrice();
receipts.put(timestamp, totalPrice);
ReceiptProcessorServiceOuterClass.AddReceiptResponse response =
ReceiptProcessorServiceOuterClass.AddReceiptResponse.newBuilder().build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
- पूरा code GrpcRepro.java में है
- protobuf definition ReceiptProcessorService.proto में है
- thread dump में
grpc-default-executor-*threadsTreeMap.put()पर runnable state में अटके दिखे
कारण पर अनुमान और अलग-अलग languages में प्रयोग
- संभावित कारण के रूप में यह शक था कि दो threads स्वतंत्र रूप से tree को उलटी दिशाओं में rotate कर रहे हों, या overlapping rotations की writes interleaving होकर cycle बना रही हों
- हालाँकि किस thread interleaving से cycle बनता है, इसका कोई प्रमाण नहीं है
- शुरू में लगा कि NPE ज़रूरी है, लेकिन बाद के प्रयोगों ने यह धारणा तोड़ दी
- कई languages में इसी समस्या को reproduce करने की कोशिश की गई
- Java में तो यह स्वाभाविक रूप से reproduce हुआ क्योंकि मूल मामला वही था
- C++ का
std::mapred-black tree का उपयोग करता है, और कभी-कभी segfault के बजाय threads अटक जाते हैं और CPU उपयोग ऊँचा हो जाता है - Go में भी अपेक्षा के विपरीत reproduction हुआ; code Go experiment में है
- Ruby में, जबकि वहाँ NPE-जैसी exception पकड़ना संभव है, reproduction नहीं हुआ; संभव है GIL ने वह interleaving रोक दी हो जो समस्या पैदा करती है
- C++ experiment में अपेक्षा के विपरीत
try/catchया null pointer exception के बिना भी अनंत loop reproduce हुआ- कभी-कभी program
segmentation faultसे बंद हो गया - बहुत कम मामलों में 10 मिनट से अधिक समय तक progress रुक गई और
topमेंSimpleRepro170.8% CPU उपयोग करता दिखा - C++ में null pointer dereference segfault देता है, इसलिए ऐसा interleaving होना चाहिए जो null से गुज़रे बिना cycle बना दे
- कभी-कभी program
- यह देखने के बाद Java experiment को भी NPE पकड़े बिना 12 बार फिर चलाया गया, और NPE catch के बिना भी
TreeMap.put()का अनंत loop reproduce हो गया
आसान fix और data structure स्तर की सुरक्षा
- सबसे आसान fix साझा
TreeMapको सुरक्षित बनाना हैCollections.synchronizedMapमें wrap करेंConcurrentHashMapपर जाएँ और sorting केवल ज़रूरत पड़ने पर करें
- एक अधिक विवादास्पद सुरक्षा red-black tree traversal के दौरान visited nodes को track करना है
- अगर पहले से देखे गए node पर फिर पहुँचे, तो
ConcurrentModificationExceptionthrow करें - यह data corruption को खुद नहीं रोकता
- लेकिन अनंत loop की वजह से 100% CPU उपयोग को रोक सकता है
- अतिरिक्त memory tree height तक सीमित रहती है, यानी
O(lg(n)) - red-black tree की height
O(lg(n))guaranteed होती है - लेखक का मानना है कि standard library इस approach को अपनाने की संभावना कम है
- अगर पहले से देखे गए node पर फिर पहुँचे, तो
- fix का उदाहरण
getEntryऔरputमेंIdentityHashMapसे visited nodes रिकॉर्ड करता है
IdentityHashMap<Entry<?,?>, Boolean> visited = new IdentityHashMap<>();
while (p != null) {
visited.put(p, true);
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
if (visited.containsKey(p)) {
throw new ConcurrentModificationException("TreeMap corrupted. Loop detected");
}
}
- पूरा modified code ProtectedTreeMap.java में है
कई परतों वाली सुरक्षा
-
गलतियाँ होंगी ही, इसलिए सिर्फ एक layer की defense काफ़ी नहीं है
-
इस case में कई गलतियाँ एक साथ हुईं, लेकिन कुछ monitoring बची हुई थी, इसलिए समस्या पकड़ी जा सकी
-
NPE alert
- NPE होने पर कोई alert नहीं था
- केवल error-rate alert था, और यह NPE हर API handler worker thread में सिर्फ एक बार हुआ, इसलिए error-rate threshold पार नहीं हुआ
- executor के handling behavior की वजह से NPE log भी नहीं बना
-
CPU उपयोग anomaly alert
- CPU उपयोग monitor किया जा रहा था और साधारण threshold-based alert था
- CPU उपयोग threshold से ऊपर गया, इसे abnormal behavior माना गया और alert उठा; इसी रास्ते समस्या पकड़ी गई
-
Executor exception handling
- अगर आप executor में task submit करते हैं, तो uncaught exception handler ज़रूर सेट करें
thread.setUncaughtExceptionHandler( (dyingThread, throwable) -> { logger.error("uncaught exception!", throwable); } );- अपने custom
ThreadFactoryके बजाय Apache Commons BasicThreadFactory या Guava ThreadFactoryBuilder का उपयोग किया जा सकता है - handler न होने पर exception निगल ली जाती है और log monitoring भी समस्या पहचान नहीं पाती
-
Code review और static analysis
- code review में threads और
TreeMapका combination पकड़ा जा सकता था, या यह सुझाव आ सकता था कि अगर sorting की ज़रूरत नहीं है तोTreeMapइस्तेमाल न करें; लेकिन इस केस में ऐसा नहीं हुआ - SpotBugs, JLint, Chord जैसे static analysis tools build time पर इस तरह की concurrency problems पकड़ सकते हैं
- static analysis पर संदर्भ सामग्री के रूप में How Good is Static Analysis at Finding Concurrency Bugs? का उल्लेख है
- code review में threads और
-
Testing
- इस code path के लिए multithreaded tests नहीं थे
- अगर code concurrent access की संभावना रखता है, तो tests को भी multithreaded scenario कवर करना चाहिए
निष्कर्ष
- बिना सुरक्षा के concurrent modification केवल data corruption ही नहीं, बल्कि अनंत loop और high CPU उपयोग के रूप में भी सामने आ सकती है
TreeMapजैसे internal pointer structure वाले data structure को कई threads एक साथ modify करें तो red-black tree में cycle बन सकता है- synchronized data structure का चयन, exception handling, alerts, static analysis, और multithreaded testing साथ में हों तो समस्या को जल्दी पकड़ा या रोका जा सकता है
1 टिप्पणियां
Hacker News की राय
यह अच्छी तरह ज्ञात है कि Java के core collections डिज़ाइन के हिसाब से thread-safe नहीं होते, इसलिए यह पकड़ा जाना चाहिए था
OP को बाकी code भी देखना चाहिए कि कहीं और कई threads के एक साथ collections को manipulate करने की संभावना तो नहीं है
Collections.synchronizedMapसे wrap करने याConcurrentHashMapमें बदलने से अलग-अलग map operations thread-safe हो जाते हैं, लेकिन operations के लगातार समूह भी safe हैं या नहीं, यह अलग बात हैजिस object के पास
TreeMapहै, वह खुद thread-safe है या नहीं, इस पर भी शक है, और visited nodes को track करने जैसी modification collection को अभी भी unsafe रखते हुए बस और subtle तरीके से fail कराएगीअसली मुद्दा
TreeMapका side effect नहीं, बल्कि collection के contract का उल्लंघन है;HashMapमें बदल देने पर भी code गलत ही रहेगाकई threads द्वारा इस्तेमाल होने वाले code में सबसे पक्का तरीका यह रहा है कि जितने हो सकें सभी objects immutable बनाए जाएं, और जिन objects को immutable नहीं बनाया जा सकता, उन्हें छोटे, स्वतंत्र और कड़े नियंत्रण वाले क्षेत्रों तक सीमित रखा जाए
size()से length check करने के बादelement(10)access करने वाले दो operations का combination atomic नहीं हैउन दो calls के बीच कोई दूसरा thread element हटा दे, तो out-of-bounds access हो सकता है
समाधान है ऐसा atomic method इस्तेमाल करना जो दोनों काम साथ करे, जैसे
element_or_null(), या फिर शुरुआत से ही सामान्य array इस्तेमाल करना और दोनों operations को पूरा mutex से protect करनातीनों conditions जरूरी हैं, इसलिए समाधान भी तीन हैं: lock आदि से synchronization जोड़ना, channels इस्तेमाल करने वाले single-owner model की तरह mutable access share न करना, या pure functional languages से मिली समझ की तरह data को immutable बनाना
मेरी जानकारी में Google ने भी Guava में इस model में काफी invest किया था
Rust आपको चुनने देता है कि इन तीनों में से किसे छोड़ना है, और तीनों conditions को एक साथ सच होने से statically रोकता है
mutex लगाकर जबरन fit करने पर आमतौर पर दूसरे problems और bottlenecks पैदा होते हैं
immutable objects इस्तेमाल करने हैं तो structural sharing का उपयोग करने वाले immutable data structures इस्तेमाल करने चाहिए, ताकि memory usage के explosion से बचा या उसे घटाया जा सके
pure functional data structures इस्तेमाल करने से समस्या का एक हिस्सा गायब हो जाता है, लेकिन अगर threads एक-दूसरे द्वारा बनाई intermediate versions पर निर्भर करते हैं, तो एक और सिरदर्द पैदा होता है और कोई दूसरा data structure चाहिए हो सकता है
अगर पहले से इस्तेमाल हो रहे mutable data structure को जबरन बचाना हो, तो access attempts को data structure तक पहुंचने से पहले serialize किया जा सकता है और access को transactions में बांधकर सिर्फ complete transactions चलने दिए जा सकते हैं; ऐसा करते-करते लगभग database implement करने जैसा लगने लगता है
ऐसी चीज को चलाने में लगी मेहनत बेहतर process model में लगनी चाहिए थी, ऐसा मेरा मानना है
मूल लेख में code को simplify किया गया example सटीक नहीं है
original code
unrelatedObjectsखाली न होने पर हीtreeMap.putकरता है, और यह फर्क bug भी हो सकता है और नहीं भीयह भी verify करना चाहिए कि
aऔरbहर बार वही value लौटाते हैं या नहीं, औरtreeMapसच में सिर्फ map की तरह ही behave करता है या नहींउदाहरण के लिए, अगर map updates को log करता है, तो इसे केवल एक बार log करने के लिए बदलना ठीक है या नहीं, यह देखना होगा
ifमें बदलना चाहिएComparator या
Comparableimplementation consistent total order provide न करे, तब भी infinite loop हो सकता है: https://stackoverflow.com/questions/62994606/concurrentskips...इसका concurrency से कोई संबंध नहीं है
यह होगा या नहीं, यह process किए जा रहे specific data और processing order पर depend कर सकता है, इसलिए लंबे समय तक normal दिखने के बाद production environment में फट सकता है
total order न देने वाला buggy comparator मुझे व्यक्तिगत रूप से अभी तक नहीं मिला
मुझे लगता था कि race condition सिर्फ डेटा करप्शन या deadlock पैदा करती है, यह नहीं सोचा था कि यह performance problem भी बना सकती है
लेकिन अगर यह डेटा को corrupt करके infinite loop बना सकती है, तो बात समझ आती है
इसलिए मैं अक्सर सोचता हूँ कि project की errors, अजीब behavior और warnings को सिद्धांततः fix करना चाहिए। क्योंकि वे ऊपर से असंबंधित दिखने वाली समस्या पैदा कर सकती हैं
हालांकि क्या करना है तय करने वालों के बीच यह सिद्धांत स्वीकार होना दुर्लभ है
कुछ projects में सभी errors, अजीब behavior और warnings हटाने की लागत, कभी-कभार आने वाली ऊपर से असंबंधित दिखने वाली समस्या झेलने की लागत से कहीं ज्यादा हो सकती है
किसी खास error के भविष्य की समस्या में शामिल होने की संभावना और उसे पहले से fix करना सस्ता पड़ेगा या नहीं—यह लगभग predict करना असंभव है, इसलिए आखिरकार यह science से ज्यादा craft जैसा हो जाता है
“कुछ भी fix न करना” भयानक है और “सब कुछ fix करना” आम तौर पर अवास्तविक है, इसलिए decision-making framework या अनुभव से आई gut feeling, और stakeholders का भरोसा चाहिए
discipline चाहिए, लेकिन एक बार उस state में पहुँचा दें तो maintain करना कहीं आसान हो जाता है
जब तक आपको specifically यह न पता हो कि program execution जारी रखना safe है, “catch करके सिर्फ log कर देना” भी बुरा idea है
बेहतर है exception को propagate होने दें, ताकि वह ऐसी जगह तक पहुँचे जहाँ useful handling हो सके—जैसे
500return करना या error dialog दिखानाएक Rails project जिसे हम maintain करते थे, उसमें logs “unsupported parameters” से भरे पड़े थे। controllers को बारीकी से check करके allow करने के बाद भी यह आता रहा
शायद harmless था, लेकिन logs में बहुत noise पैदा करता था
कई लोगों ने इसे solve करने की कोशिश की, लेकिन हमेशा higher priority काम थे, और किसी feature impact न रखने वाली चीज पर बहुत समय खर्च करना justify करना भी मुश्किल था
यह बवासीर जैसी परेशान करने वाली समस्या है। surgery कराके कुछ हफ्ते बहुत दर्द झेलना है या बस सहकर जीना है—ऐसा चुनाव; आम तौर पर benign, लेकिन बिगड़कर बड़ी समस्या बनने की संभावना भी होती है
ऐसे phenomenon को digital hemorrhoids कहना ठीक लगेगा
अगर आपने warning को unrelated माना है, तो कम से कम comment में explain करें, और संभव हो तो warning को जितने narrow scope में हो उतने में बंद करने वाला
pragmaलगाएँअजीब behavior को हटाना मुझे ज्यादा पसंद है। “क्यों काम करता है पता नहीं” के रूप में marked code बाद में काम करना बंद कर चुका है, और अगर पहले साफ किया होता तो सावधानी से fix करने का समय होता, लेकिन हमें जल्दबाजी में फिर से लिखना पड़ा
“मुश्किल से ssh से connect किया” वाला हिस्सा देखकर मुझे grad school का एक वाकया याद आया
हमारी lab में parallel/distributed processing पर काम करने वाला लगभग 8 लोगों का छोटा group था, जो शायद Sun UltraSparc 170 मशीन share करता था; hard disk 1GB और RAM 128 या 256MB के आसपास थी
यह भी ध्यान रखना चाहिए कि Sun machines को लगभग reboot नहीं किया जाता था
लगता है एक नए student ने बड़ी text file को line numbers के आधार पर 32 या 64 ranges में बाँटा, और अलग files बनाए बिना
perlकी N copies parallel चलाकर हर copy से अपना line range process करवायाउस समय के हिसाब से
perlinterpreters की N copies ने काफी RAM खा ली, और swap शुरू होते ही वे उसी file के अलग-अलग हिस्सों पर पागलों की तरह seek करते हुए हर process कुछ और lines पढ़ने की कोशिश कर रहा थाऊपर से Jवें process को अपने section तक पहुँचने के लिए file का J/N हिस्सा पढ़ना पड़ा होगा
console पर login prompt भी नहीं मिल रहा था, लेकिन सौभाग्य से एक session पहले से logged in था और
suने 20–30 मिनट बाद password prompt दिखाया5–10 मिनट और बाद root session मिला,
topसे कारण देखा, user से contact करने की कोशिश की और problematic processes kill किए, तब system normal हुआidea खुद सही था, लेकिन system limits की बिल्कुल समझ नहीं थी; hard disk और कम RAM के कारण I/O bottleneck इतना गंभीर था कि simple linear processing कहीं बेहतर रहती
Java हो या कोई और language, thread-safe न होने वाले object पर concurrent operations करने से दुनिया के सबसे interesting bugs निकलते हैं
multithreading errors debug करने में सबसे खराब होती हैं
इस case में इसे design time पर पहचानना बहुत आसान था, और multithreaded environment में normal container इस्तेमाल करने की कोशिश करते ही warning light जल जानी चाहिए थी
volatilemodifier हटाने पर होने वाली inconsistency को मैंने जानबूझकर reproduce नहीं कियाशायद उस समय इस्तेमाल किया गया JVM बहुत अच्छी तरह बनाया गया था
language race होने पर practically बेअर्थ पागल semantics define करती है, लेकिन असल में memory corruption या noisy problems अक्सर सामने आ जाती हैं
क्योंकि races ज्यादातर illegal हैं, semantics दिखाने के लिए source code बदले बिना thread checker जैसे tools बनाए जा सकते हैं
इसके उलट Java में undefined behavior नहीं है, लेकिन दो fields के subtly mismatch होने जैसी बातें आसानी से हो सकती हैं, और इन्हें track करना कहीं ज्यादा मुश्किल है
उदाहरण के लिए map
ConcurrentModificationExceptionthrow करता हैproduction में चल रहे C# में भी मैंने यही phenomenon देखा है
symptoms वही थे और process dump देखने पर corrupted dictionary मिली
मुझे लगा था कि सब जगह
ConcurrentDictionaryइस्तेमाल हो रहा है, लेकिन library से आया एक instance problem थाउस समय .NET Framework इस्तेमाल कर रहे थे, और जहाँ तक याद है .NET Core में concurrent modification detect करने वाला code है
implementation ठीक से नहीं जानता, लेकिन सिर्फ version counter हो तो भी संभव होगा
NPE को जैसे key ingredient मानकर obsession दिखाना अजीब है। original occurrence में वह दिखता नहीं, और सिर्फ exception न होने के कारण यह मानने की वजह नहीं कि C में ऐसे bugs नहीं होते
core बात class invariants है। आम तौर पर mutator चलने के दौरान invariant valid नहीं रहता और अंत में ही restore होता है
invariant restore होने से पहले दूसरा mutator चलाएँ तो data structure corrupt हो जाता है। अगर valid state से शुरू नहीं करते, तो अंत में valid state में होने की संभावना कम है
uncaught NPE से reproduce न हो पाने की बदकिस्मती हुई, इसलिए गलत conclusion निकाल लिया कि uncaught NPE necessary condition है
पर्याप्त synchronization न होने वाले
java.util.HashMapमें भी मैंने यही चीज़ देखी हैयह करीब 2009 की बात थी, लेकिन जहाँ तक मुझे पता है आज भी संभव है
मेरी याद के मुताबिक
HashMapcollision resolution के लिए chaining इस्तेमाल करता है, और शायद chain के अंदर कोई cycle बन गई थीहालांकि इसे verify करने के लिए गहराई में जाने के बजाय मैंने problem code को पूरी तरह हटाने पर ध्यान दिया
Concurrency knowledge अक्सर interviews में पूछा जाता है, और अगर आप data race को छोटी-मोटी समस्या समझते हैं तो अच्छा impression बनाना मुश्किल है; यह case उसकी वजह बताता है
HashMapमें भी ऐसा हो सकता हैक्या यह collision handling में इस्तेमाल होने वाली linked list से जुड़ा है?
Cycle खोजने के लिए एक incrementing counter रखना, और अगर वह tree depth या collection size से बड़ा हो जाए तो exception throw करना कैसा रहेगा, यह सोच रहा हूँ
लेखक ने जो hash set वाला तरीका सुझाया है, उसके उलट इसमें memory या CPU overhead लगभग नहीं है और इसके accept होने की संभावना भी ज़्यादा लगती है
हालांकि C# को 10 साल से ज़्यादा इस्तेमाल करते हुए ऐसा कभी नहीं हुआ कि concurrent situation में data structure के concurrent access पर विचार न किया हो
Tree के corrupt होने के तरीके इसके अलावा भी बहुत हैं
जहाँ संभव हो infinite loop से बचें, और जिन cases में नहीं बच सकते वे काफी rare होते हैं
यह fix नहीं है, लेकिन अच्छी mitigation strategy है
Infinite loop सबसे खराब bugs में से एक है। Debugger में इसे ढूँढना आसान है, लेकिन OP की “मुश्किल से ssh चल पा रहा था” वाली situation जैसे हालात में इसका नतीजा बहुत unfriendly हो सकता है
खासकर library code में infinite loop और भी परेशान करने वाला होता है
यह guarantee होती है कि nodes की संख्या tree height से ज़्यादा नहीं है
Thread के अंदर exceptions सच में घातक होते हैं
C++,
select(), और exceptions इधर-उधर फेंकने वाले thread की मुख्य भूमिका वाली एक डरावनी bug-tracking story है: https://news.ycombinator.com/item?id=42532979फिर से पढ़नी पड़ेगी