{"record":{"id":"5824775cb2a8f0be","repo":"sgl-project/sglang","slug":"kv-canary-type-obj-name-method-name-alre","errorCode":null,"errorMessage":"kv-canary: {type(obj).__name__}.{method_name} already wrapped by kv-canary","messagePattern":"kv-canary: (.+?)\\.(.+?) already wrapped by kv-canary","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/kv_canary/pool_patcher/utils.py","lineNumber":33,"sourceCode":"    \"\"\"Replace ``obj.method_name`` with a closure that delegates to ``wrapper``.\n\n    ``wrapper(original, *args, **kwargs)`` receives the original bound method as its first arg and the\n    call-site args/kwargs as the rest. It decides when (and whether) to call ``original`` and what to\n    return. The patched callable is installed as a plain function; :func:`functools.wraps` preserves\n    ``__name__`` / ``__doc__`` but the bound-method nature of the original is not retained.\n\n    Raises:\n        AttributeError: ``obj`` has no attribute ``method_name``.\n        RuntimeError: ``obj.method_name`` has already been wrapped by ``wrap_method`` (idempotency\n            guard — re-wrapping silently would stack two transforms and corrupt return values).\n    \"\"\"\n    if not hasattr(obj, method_name):\n        raise AttributeError(\n            f\"kv-canary: {type(obj).__name__} missing required method {method_name!r}\"\n        )\n    original = getattr(obj, method_name)\n    if getattr(original, _WRAPPED_MARKER_ATTR, None) is not None:\n        raise RuntimeError(\n            f\"kv-canary: {type(obj).__name__}.{method_name} already wrapped by kv-canary\"\n        )\n\n    @functools.wraps(original)\n    def patched(*args: Any, **kwargs: Any) -> Any:\n        return wrapper(original, *args, **kwargs)\n\n    setattr(patched, _WRAPPED_MARKER_ATTR, method_name)\n    setattr(obj, method_name, patched)\n","sourceCodeStart":15,"sourceCodeEnd":43,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/kv_canary/pool_patcher/utils.py#L15-L43","documentation":"wrap_method marks wrapped methods with a marker attribute and refuses to wrap the same method twice. Double-wrapping would stack two transforms and corrupt return values, so the idempotency guard raises RuntimeError.","triggerScenarios":"Calling wrap_method twice on the same obj/method without unwrapping — e.g. patching model forward in _patch_model_forward, then calling patch again on re-init, or a test reusing a patched object across cases.","commonSituations":"Calling the kv-canary install/patch routine twice in one process (server restart-in-place, retry logic in tests); patching both at module level and instance level so the same bound method is seen twice.","solutions":["Check the wrapped marker (getattr(method, _WRAPPED_MARKER_ATTR, None)) or expose/track patch state before calling wrap_method again","Add an uninstall/unpatch step that restores the original before re-patching","Make the install path idempotent: skip if already wrapped instead of calling wrap_method"],"exampleFix":"// before\nwrap_method(model, 'forward', wrapper)  # called again -> RuntimeError\n# after\nif getattr(getattr(model, 'forward', None), '__kv_canary_wrapped__', None) is None:\n    wrap_method(model, 'forward', wrapper)","handlingStrategy":"validation","validationCode":"orig = getattr(obj, method_name, None)\nif getattr(orig, '__kv_canary_wrapped__', None) is not None:\n    return  # already patched — idempotent skip\nwrap_method(obj, method_name, wrapper)","typeGuard":"def is_wrapped_by_canary(fn) -> bool:\n    return getattr(fn, _WRAPPED_MARKER_ATTR, None) is not None","tryCatchPattern":"try:\n    wrap_method(obj, method_name, wrapper)\nexcept RuntimeError as e:\n    if 'already wrapped' not in str(e):\n        raise","preventionTips":["Track patch state in an install/uninstall manager","Make install paths idempotent; skip if the marker attribute is present"],"tags":["kv-canary","monkey-patching","idempotency","runtimeerror"],"backgroundTag":"double-initialization","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}