{"record":{"id":"a947b6001866a7e3","repo":"crossoverJie/JCSprout","slug":"data-error","errorCode":null,"errorMessage":"data error","messagePattern":"data error","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/main/java/com/crossoverjie/actual/LRUAbstractMap.java","lineNumber":248,"sourceCode":"    }\n\n    /**\n     * 增加size\n     */\n    private void sizeUp(){\n\n        //在put值时候认为里边已经有数据了\n        flag = true ;\n\n        if (size == null){\n            size = new AtomicInteger() ;\n        }\n        int size = this.size.incrementAndGet();\n        if (size >= MAX_SIZE) {\n            //找到队列头的数据\n            Node node = QUEUE.poll() ;\n            if (node == null){\n                throw new RuntimeException(\"data error\") ;\n            }\n\n            //移除该 key\n            Object key = node.key ;\n            remove(key) ;\n            lruCallback() ;\n        }\n\n    }\n\n    /**\n     * 数量减小\n     */\n    private void sizeDown(){\n\n        if (QUEUE.size() == 0){\n            flag = false ;\n        }","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/crossoverJie/JCSprout/blob/fc4c6e5f6d1772c2aec4c0f94cb3c8e7eb04018f/src/main/java/com/crossoverjie/actual/LRUAbstractMap.java#L230-L266","documentation":"Thrown inside sizeUp() of the custom LRUAbstractMap when the internal size counter has reached MAX_SIZE (1024) but QUEUE.poll() returns null — an invariant violation signalling that the AtomicInteger size tracker and the backing ArrayBlockingQueue have drifted out of sync. The map is not actually thread-safe despite using AtomicInteger/volatile, and several code paths drain the queue independently of the size counter, so the two can (and do) diverge under real use.","triggerScenarios":"Calling put() repeatedly until size >= 1024 at a moment when the shared static QUEUE is already empty. This is almost guaranteed because: (a) QUEUE is declared static final (shared across all LRUAbstractMap instances) while the size counter is per-instance, so a second instance's eviction polls a queue it never populated; (b) remove() unconditionally calls QUEUE.poll() on the head regardless of which key was removed, corrupting the mapping; (c) the background CheckTimeThread continuously polls QUEUE.poll() without offering back, draining entries that the size counter still thinks exist.","commonSituations":"Creating more than one LRUAbstractMap instance in the same JVM (static queue, per-instance size). Concurrent put/remove from multiple threads. Long-lived instances where the daemon CheckTimeThread has had time to drain the queue. Any production use beyond a single-threaded, single-instance toy demo.","solutions":["If you must use this class, restrict to a single instance and single-threaded access — the static QUEUE / per-instance size split makes multi-instance use fundamentally broken.","Stop the background CheckTimeThread or rewrite it so it does not drain the QUEUE (it should peek, not poll).","Fix remove() to remove the matching Node from the queue rather than blindly polling the head.","Replace the hand-rolled size counter with QUEUE.size() so the eviction predicate reflects queue reality, not a divergent AtomicInteger.","Prefer ConcurrentHashMap + ConcurrentLinkedDeque, or Caffeine/Guava Cache, which handle bounded LRU eviction correctly under concurrency."],"exampleFix":"// before (broken: static queue, per-instance size, blind poll in remove)\nprivate final static ArrayBlockingQueue<Node> QUEUE = new ArrayBlockingQueue<>(MAX_SIZE);\nprivate volatile AtomicInteger size;\n// sizeUp checks size >= MAX_SIZE then QUEUE.poll() → null → throws\n\n// after (queue and size co-located, single lock)\nprivate final ArrayDeque<Node> queue = new ArrayDeque<>();\nprivate final Object lock = new Object();\n\nprivate void sizeUp() {\n    synchronized (lock) {\n        if (queue.size() >= MAX_SIZE) {\n            Node node = queue.pollFirst();\n            if (node != null) {\n                remove(node.key);\n                lruCallback();\n            }\n        }\n    }\n}","handlingStrategy":"try-catch","validationCode":"// This class is not safe for concurrent or multi-instance use.\n// Best validation: avoid it. If you must, at least check coherence.\nLRUAbstractMap map = new LRUAbstractMap();\n// There is no public queue-depth accessor; the only pre-check is size(),\n// which does NOT reflect the static QUEUE. Wrap every put in try-catch.","typeGuard":null,"tryCatchPattern":"try {\n    map.put(key, value);\n} catch (RuntimeException e) {\n    if (\"data error\".equals(e.getMessage())) {\n        // size counter and queue are out of sync — recreate the map instance\n        LOGGER.error(\"LRU map invariant violated, size counter desynced from queue\", e);\n        map = new LRUAbstractMap(); // last resort: reset state\n    } else {\n        throw e;\n    }\n}","preventionTips":["Never create more than one LRUAbstractMap instance in the same JVM — the backing QUEUE is static but size is per-instance.","Do not use this class from multiple threads — it is not thread-safe despite AtomicInteger/volatile fields.","Consider replacing it with Caffeine or Guava Cache, which implement bounded LRU eviction correctly.","If you fork the class, make QUEUE non-static or make size static so they share the same lifecycle."],"tags":["concurrency","lru-cache","data-integrity","race-condition"],"backgroundTag":null,"analyzedSha":"fc4c6e5f6d1772c2aec4c0f94cb3c8e7eb04018f","analyzedAt":"2026-08-14T05:43:20.992Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}