[Bug]: JsonCache.get() crashes with unhandled AttributeError on non-dict or invalid envelope JSON payloads

未关闭 适合新手
#2,614 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 5 天内回复

还没有人认领这个 Issue。

  • #1 来自 @wangkangmao —— 已关闭,未合并

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
82/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
python
领域
backend, databases

调研方向

阅读 packages/graphrag-cache/graphrag_cache/json_cache.py,并将 JsonCache.get() 与 issue 中描述的 SQLiteCache 行为进行比较。检查缓存测试,并为有效但不是字典的 JSON 值,以及缺少 result 的字典补充测试覆盖。完成标准是删除无效条目,且 get() 不抛出异常并返回 None。

由索引模型根据 Issue 内容生成。

描述

Do you need to file an issue?
  • I have searched the existing issues and this bug is not already filed.
  • I believe this is a legitimate bug, not just a question. If this is a question, please use the Discussions area.
Describe the bug

In packages/graphrag-cache/graphrag_cache/json_cache.py, JsonCache.get() decodes stored cache data using json.loads(data) and assumes the deserialized object is always a dictionary:

    async def get(self, key: str) -> Any:
        """Get method definition."""
        if await self.has(key):
            try:
                data = await self._storage.get(key)
                data = json.loads(data)
            except UnicodeDecodeError:
                await self._storage.delete(key)
                return None
            except json.decoder.JSONDecodeError:
                await self._storage.delete(key)
                return None
            else:
                return data.get("result")

If the storage contains a valid JSON payload that is not a dictionary (e.g. a JSON array [1, 2, 3], primitive string, number, or boolean from an external tool, corrupted state, or older schema format), json.loads() succeeds without raising JSONDecodeError. Then execution jumps to data.get("result"), immediately raising:

AttributeError: 'list' object has no attribute 'get'

Furthermore, unlike SQLiteCache (which explicitly handles invalid envelopes and purges corrupted entries via if not isinstance(data, dict) or "result" not in data:), JsonCache neither catches this exception nor deletes the offending key. As a result, every subsequent call to cache.get(key) repeatedly crashes the pipeline instead of recovering gracefully with None.

Steps to reproduce

Self-contained minimal reproduction (no external APIs or configs needed):

import asyncio
import json

class InMemoryStorage:
    def __init__(self):
        self._store = {}
    async def get(self, key):
        return self._store.get(key)
    async def set(self, key, value):
        self._store[key] = value
    async def has(self, key):
        return key in self._store
    async def delete(self, key):
        self._store.pop(key, None)

# Directly reproduces packages/graphrag-cache/graphrag_cache/json_cache.py
class JsonCache:
    def __init__(self, storage):
        self._storage = storage

    async def get(self, key: str):
        if await self.has(key):
            try:
                data = await self._storage.get(key)
                data = json.loads(data)
            except UnicodeDecodeError:
                await self._storage.delete(key)
                return None
            except json.decoder.JSONDecodeError:
                await self._storage.delete(key)
                return None
            else:
                return data.get("result")
        return None

    async def has(self, key: str) -> bool:
        return await self._storage.has(key)

async def main():
    storage = InMemoryStorage()
    cache = JsonCache(storage)
    
    # Store a valid JSON array or primitive payload under a cache key
    await storage.set("bad_envelope", "[1, 2, 3]")
    
    # Crashes with unhandled AttributeError: 'list' object has no attribute 'get'
    result = await cache.get("bad_envelope")
    print("Result:", result)

asyncio.run(main())
Expected Behavior

Consistent with SQLiteCache, JsonCache.get() should verify that isinstance(data, dict) and "result" in data. If invalid, it should purge the corrupted key from storage and safely return None.

Proposed fix:

    async def get(self, key: str) -> Any | None:
        """Get method definition."""
        if await self.has(key):
            try:
                data = await self._storage.get(key)
                data = json.loads(data)
            except (UnicodeDecodeError, json.decoder.JSONDecodeError):
                await self._storage.delete(key)
                return None
            else:
                if not isinstance(data, dict) or "result" not in data:
                    await self._storage.delete(key)
                    return None
                return data["result"]
        return None
GraphRAG Config Used

N/A (Reproducible directly in graphrag_cache.json_cache.JsonCache)

Logs and screenshots
Traceback (most recent call last):
  File "repro.py", line 47, in <module>
    asyncio.run(main())
  File ".../asyncio/runners.py", line 190, in run
    return runner.run(main)
  File ".../asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
  File ".../asyncio/base_events.py", line 654, in run_until_complete
    return future.result()
  File "repro.py", line 43, in main
    result = await cache.get("bad_envelope")
  File "repro.py", line 32, in get
    return data.get("result")
AttributeError: 'list' object has no attribute 'get'
Additional context
  • GraphRAG Version: main branch (post-monorepo split / PR #2591)
  • Python Version: Python 3.10 / 3.11 / 3.12
  • Operating System: Linux / macOS / Windows
  • Note: We have prepared a fix with regression tests and can submit a PR if this looks good to maintainers.
主要语言
Python
星标
36.6k
派生
3.9k
平均合并
4 天 11 小时
30 天内合并 PR
1

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

microsoft/graphrag 的其他 Issue

查看 microsoft/graphrag 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。