{"record":{"id":"122d4d150ae1fcc7","repo":"apache/druid","slug":"per-segment-timeout-timeoutpersegmentquery-must","errorCode":null,"errorMessage":"Per-segment timeout [timeoutPerSegmentQuery] must be a non negative value, but was [%d]","messagePattern":"Per-segment timeout \\[timeoutPerSegmentQuery\\] must be a non negative value, but was \\[(.+?)\\]","errorType":"validation","errorClass":"BadQueryContextException","httpStatus":400,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/QueryContext.java","lineNumber":568,"sourceCode":"              maxQueryTimeout\n          )\n      );\n    }\n  }\n\n  public long getPerSegmentTimeout()\n  {\n    return getPerSegmentTimeout(QueryContexts.NO_TIMEOUT);\n  }\n\n  public long getPerSegmentTimeout(long defaultPerSegmentTimeout)\n  {\n    final long timeout = getLong(QueryContexts.PER_SEGMENT_TIMEOUT_KEY, defaultPerSegmentTimeout);\n    if (timeout >= 0) {\n      return timeout;\n    }\n\n    throw new BadQueryContextException(\n        StringUtils.format(\n            \"Per-segment timeout [%s] must be a non negative value, but was [%d]\",\n            QueryContexts.PER_SEGMENT_TIMEOUT_KEY,\n            timeout\n        )\n    );\n  }\n\n  public boolean usePerSegmentTimeout()\n  {\n    return getPerSegmentTimeout() != QueryContexts.NO_TIMEOUT;\n  }\n\n  public void verifyMaxScatterGatherBytes(long maxScatterGatherBytesLimit)\n  {\n    long curr = getLong(QueryContexts.MAX_SCATTER_GATHER_BYTES_KEY, 0);\n    if (curr > maxScatterGatherBytesLimit) {\n      throw new BadQueryContextException(","sourceCodeStart":550,"sourceCodeEnd":586,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/QueryContext.java#L550-L586","documentation":"Druid validates the `timeoutPerSegmentQuery` query-context key, which caps the time spent on each per-segment query in scatter-gather execution. QueryContext.getPerSegmentTimeout throws BadQueryContextException when the value is negative, since a negative per-segment budget is meaningless. Like the other timeout checks, this guards deadline arithmetic downstream.","triggerScenarios":"Calling QueryContext.getPerSegmentTimeout(long defaultPerSegmentTimeout) when the context contains PER_SEGMENT_TIMEOUT_KEY (\"timeoutPerSegmentQuery\") with a value < 0, e.g. `{\"timeoutPerSegmentQuery\": -1000}`.","commonSituations":"Copy-pasted query context from another engine where -1 meant 'unlimited'; generated query contexts with placeholder negatives; client bugs computing per-segment budgets from a negative remainder.","solutions":["Set `timeoutPerSegmentQuery` in the query context to a non-negative millisecond value, or remove the key to use the default.","Clamp any computed value: `long t = Math.max(0, computed);` before putting it in the context.","Search the query-issuing code for negative sentinel values and replace them with omission of the key.","Catch BadQueryContextException and log/return the offending context key and value."],"exampleFix":"// before\ncontext.put(\"timeoutPerSegmentQuery\", -1L); // meant 'unlimited'\n// after\n// omit the key to use the default, or supply a positive value\ncontext.put(\"timeoutPerSegmentQuery\", 10_000L);","handlingStrategy":"validation","validationCode":"Object tps = query.getContext().get(\"timeoutPerSegmentQuery\");\nif (tps instanceof Number && ((Number) tps).longValue() < 0) {\n    throw new IllegalArgumentException(\"timeoutPerSegmentQuery must be non-negative, got \" + tps);\n}","typeGuard":"boolean isValidPerSegmentTimeout(Object v) {\n    return !(v instanceof Number) || ((Number) v).longValue() >= 0;\n}","tryCatchPattern":"try {\n    client.query(query);\n} catch (BadQueryContextException e) {\n    if (e.getMessage().contains(\"Per-segment timeout\")) {\n        query.getContext().remove(\"timeoutPerSegmentQuery\");\n        client.query(query); // use default per-segment timeout\n    } else {\n        throw e;\n    }\n}","preventionTips":["Omit timeoutPerSegmentQuery rather than sending negative placeholders.","Sanitize any context value copied from other query engines.","Clamp computed per-segment budgets with Math.max(0, value).","Keep a shared, validated context-builder utility for all query clients."],"tags":["query-context","timeout","validation","druid"],"backgroundTag":"value-out-of-range","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}