nathanmarz/storm · error · RuntimeException
Acked a non-existent or already acked/failed id
Error message
Acked a non-existent or already acked/failed id: ${id} What it means
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.
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
Example fix
# before (python bolt)
try:
process(tup)
storm.ack(tup)
except Exception:
storm.fail(tup)
finally:
storm.ack(tup) # duplicate ack!
# after
try:
process(tup)
storm.ack(tup)
except Exception:
storm.fail(tup) Defensive patterns
Strategy: validation
Validate before calling
# inside the multilang bolt script — guard before acking:
acked = set()
def safe_ack(tup):
tid = tup['id']
if tid not in acked:
acked.add(tid)
storm.ack(tup) Try / catch
// on the reading side (custom ShellBolt subclass or log watcher):
try {
handleAck(action);
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Acked a non-existent")) {
LOG.error("Multilang process double-acked id {}", action.get("id"));
// treat as warning and continue, or restart the process
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Error when launching multilang subprocess
- Error when launching multilang subprocess
- Error during multilang processing
- Failed a non-existent or already acked/failed id
- Anchored onto after ack/fail
AI-assisted analysis of nathanmarz/storm@cdb116e942 (2026-09-12).
Data as JSON: /api/errors/2f15dfc492db6666.
Report an issue: GitHub.
Appendix: source
Thrown at storm-core/src/jvm/backtype/storm/task/ShellBolt.java:193
obj.put("task", input.getSourceTask());
obj.put("tuple", input.getValues());
_pendingWrites.put(obj);
} catch(InterruptedException e) {
throw new RuntimeException("Error during multilang processing", e);
}
}
public void cleanup() {
_running = false;
_process.destroy();
_inputs.clear();
}
private void handleAck(Map action) {
String id = (String) action.get("id");
Tuple acked = _inputs.remove(id);
if(acked==null) {
throw new RuntimeException("Acked a non-existent or already acked/failed id: " + id);
}
_collector.ack(acked);
}
private void handleFail(Map action) {
String id = (String) action.get("id");
Tuple failed = _inputs.remove(id);
if(failed==null) {
throw new RuntimeException("Failed a non-existent or already acked/failed id: " + id);
}
_collector.fail(failed);
}
private void handleError(Map action) {
String msg = (String) action.get("msg");
_collector.reportError(new Exception("Shell Process Exception: " + msg));
}
View on GitHub (pinned to cdb116e942)