{"record":{"id":"2f15dfc492db6666","repo":"nathanmarz/storm","slug":"acked-a-non-existent-or-already-acked-failed-id-id","errorCode":null,"errorMessage":"Acked a non-existent or already acked/failed id: ${id}","messagePattern":"Acked a non-existent or already acked/failed id: (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"storm-core/src/jvm/backtype/storm/task/ShellBolt.java","lineNumber":193,"sourceCode":"            obj.put(\"task\", input.getSourceTask());\n            obj.put(\"tuple\", input.getValues());\n            _pendingWrites.put(obj);\n        } catch(InterruptedException e) {\n            throw new RuntimeException(\"Error during multilang processing\", e);\n        }\n    }\n\n    public void cleanup() {\n        _running = false;\n        _process.destroy();\n        _inputs.clear();\n    }\n\n    private void handleAck(Map action) {\n        String id = (String) action.get(\"id\");\n        Tuple acked = _inputs.remove(id);\n        if(acked==null) {\n            throw new RuntimeException(\"Acked a non-existent or already acked/failed id: \" + id);\n        }\n        _collector.ack(acked);\n    }\n\n    private void handleFail(Map action) {\n        String id = (String) action.get(\"id\");\n        Tuple failed = _inputs.remove(id);\n        if(failed==null) {\n            throw new RuntimeException(\"Failed a non-existent or already acked/failed id: \" + id);\n        }\n        _collector.fail(failed);\n    }\n\n    private void handleError(Map action) {\n        String msg = (String) action.get(\"msg\");\n        _collector.reportError(new Exception(\"Shell Process Exception: \" + msg));\n    }\n","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/nathanmarz/storm/blob/cdb116e942666973bc4eaa0df098d5bab82739e7/storm-core/src/jvm/backtype/storm/task/ShellBolt.java#L175-L211","documentation":"ShellBolt.handleAck processes an \"ack\" command from the multilang subprocess: it removes the pending Tuple keyed by the message id and acks it. If the id is not in _inputs (never emitted with that id, or already acked/failed/removed), it throws this RuntimeException. The subprocess double-acked, acked an unknown id, or acked after a fail.","triggerScenarios":"The subprocess sends {\"command\":\"ack\",\"id\":X} twice for the same id; it acks an id it invented instead of echoing the id from the tuple it received; it acks after the anchor was already failed; emitting without anchoring/ids so _inputs has no such entry.","commonSituations":"Custom Python/shell bolts with hand-rolled ack logic acking both on success and in a finally/error path; script caching tuple ids and re-acking on retry; race between handleFail and handleAck from the reader thread; upgrading the script while topology state persists.","solutions":["Ensure the subprocess acks each id exactly once — remove duplicate ack calls (e.g. ack in both try and finally)","Ack only ids that were passed in the incoming tuple's message id, echoing them verbatim; do not generate ids in the script","Check the script's fail/ack paths are mutually exclusive per id","Review worker logs for the reader thread trace to identify which ids are duplicated and correlate with script logic"],"exampleFix":"# before (python bolt)\ntry:\n    process(tup)\n    storm.ack(tup)\nexcept Exception:\n    storm.fail(tup)\nfinally:\n    storm.ack(tup)  # duplicate ack!\n\n# after\ntry:\n    process(tup)\n    storm.ack(tup)\nexcept Exception:\n    storm.fail(tup)","handlingStrategy":"validation","validationCode":"# inside the multilang bolt script — guard before acking:\nacked = set()\ndef safe_ack(tup):\n    tid = tup['id']\n    if tid not in acked:\n        acked.add(tid)\n        storm.ack(tup)","typeGuard":null,"tryCatchPattern":"// on the reading side (custom ShellBolt subclass or log watcher):\ntry {\n    handleAck(action);\n} catch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Acked a non-existent\")) {\n        LOG.error(\"Multilang process double-acked id {}\", action.get(\"id\"));\n        // treat as warning and continue, or restart the process\n    } else throw e;\n}","preventionTips":["Ack each tuple id exactly once in the script; never ack in both try and finally","Echo the id verbatim from the input tuple; never fabricate ids","Make ack and fail paths mutually exclusive per id","Log every ack/fail with id in the script during development to spot duplicates early"],"tags":["storm","multilang","acking","protocol"],"backgroundTag":"internal-invariant-violation","analyzedSha":"cdb116e942666973bc4eaa0df098d5bab82739e7","analyzedAt":"2026-09-12T14:30:00.714Z","contentChangedAt":"2026-09-12T14:30:00.714Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}