[Fix][Engine] Add defensive null checks to JobDAGInfo toJsonObject#11398
[Fix][Engine] Add defensive null checks to JobDAGInfo toJsonObject#11398Isha2790 wants to merge 1 commit into
Conversation
DanielLeens
left a comment
There was a problem hiding this comment.
Thanks for your first contribution to Apache SeaTunnel! The approach of adding defensive null checks to JobDAGInfo.toJsonObject() makes sense — when JobDAGInfo is constructed via the no-arg constructor (e.g. in tests or deserialization edge cases), pipelineEdges can be null and the entrySet() call would throw an NPE.
I have one concern before we can merge:
Issue 1: Incomplete null-check coverage in toJsonObject()
- Location:
seatunnel-engine/seatunnel-engine-core/src/main/java/org/apache/seatunnel/engine/core/job/JobDAGInfo.java:67-70 - The PR adds null guards for
pipelineEdges,entry.getValue(), and individualedgeobjects, but the method still dereferencesvertexInfoMap(line 69),jobId(line 67), andenvOptions(line 68) without any null check. SinceJobDAGInfohas a@NoArgsConstructor, all fields can benull. If the goal is defensive null safety, these fields need the same treatment. For example,vertexInfoMap.entrySet()will NPE ifvertexInfoMapis null — and the existing testBaseServiceTableMetricsTestalready createsJobDAGInfoinstances with onlyvertexInfoMapset and leavespipelineEdgesnull. - Suggested fix: add null guards for
vertexInfoMap(skip the loop if null),jobId(useString.valueOf(jobId)or a null check), andenvOptions(pass an emptyJsonObjectif null).
CI note: The Build check currently shows action_required — this is expected for first-time contributors. Once a maintainer approves the workflow run, CI will proceed. No action needed from you on that front.
Keep up the good work! Looking forward to the next iteration.
DanielLeens
left a comment
There was a problem hiding this comment.
Thanks for your first contribution to Apache SeaTunnel! The approach of adding defensive null checks to JobDAGInfo.toJsonObject() makes sense — when JobDAGInfo is constructed via the no-arg constructor (e.g. in tests or deserialization edge cases), pipelineEdges can be null and the entrySet() call would throw an NPE.
I have one concern before we can merge:
Issue 1: Incomplete null-check coverage in toJsonObject()
- Location:
seatunnel-engine/seatunnel-engine-core/src/main/java/org/apache/seatunnel/engine/core/job/JobDAGInfo.java:67-70 - The PR adds null guards for
pipelineEdges,entry.getValue(), and individualedgeobjects, but the method still dereferencesvertexInfoMap(line 69),jobId(line 67), andenvOptions(line 68) without any null check. SinceJobDAGInfohas a@NoArgsConstructor, all fields can benull. If the goal is defensive null safety, these fields need the same treatment. For example,vertexInfoMap.entrySet()will NPE ifvertexInfoMapis null — and the existing testBaseServiceTableMetricsTestalready createsJobDAGInfoinstances with onlyvertexInfoMapset and leavespipelineEdgesnull. - Suggested fix: add null guards for
vertexInfoMap(skip the loop if null),jobId(useString.valueOf(jobId)or a null check), andenvOptions(pass an emptyJsonObjectif null).
CI note: The Build check currently shows action_required — this is expected for first-time contributors. Once a maintainer approves the workflow run, CI will proceed. No action needed from you on that front.
Keep up the good work! Looking forward to the next iteration.
|
Please enable CI following the instruction. |
|
Thanks for the CI ping. The current One important note from Daniel's side: enabling the workflow is not enough by itself for this PR yet. My earlier blocker on |
SEZ9
left a comment
There was a problem hiding this comment.
Thanks for working on this. I re-reviewed the latest head 5ac02727f834 from scratch.
What this PR fixes
- User pain: REST/state-reporting endpoints that serialize a job's DAG can crash the handler thread with an NPE when
JobDAGInfocontains null maps, null edge lists, or null edges, returning 500s instead of job info. - Fix approach: Wrap the
pipelineEdgesiteration intoJsonObject()with null guards at three levels (the map itself, each entry's edge list, and each edge), and fall back to an empty-string JSON key when the pipeline id key is null. - One-line summary: The change does harden the
pipelineEdgesloop, but it is incomplete against its own stated goals —edge.getInputVertexId()/getTargetVertexId()can still NPE on.toString()inside the new guard, the promised protections forjobId,vertexId,envOptions, and nestedvertexInfo.getType().getType()paths are absent from the diff, and serializing a null pipeline id as key""emits a malformed payload that downstream REST/UI consumers cannot map to a pipeline.
Runtime chain I rechecked
GET /hazelcast/rest/maps/job-info/{jobId}
RestHttpGetCommandProcessor.handleJobInfoById() RestHttpGetCommandProcessor.java
RestHttpGetCommandProcessor.convertToJson(jobInfo, jobId) RestHttpGetCommandProcessor.java
DAGUtils.getJobDAGInfo(jobImmutableInformation, ...) DAGUtils.java (constructs JobDAGInfo with pipelineEdges/vertexInfoMap)
JobDAGInfo.toJsonObject() JobDAGInfo.java:47
pipelineEdges null-guard + entry iteration JobDAGInfo.java:50-63
edge.getInputVertexId().toString() / edge.getTargetVertexId().toString() JobDAGInfo.java:57-58 <-- still unguarded NPE if vertex ids are null
pipelineEdgesJsonObject.add(entry.getKey() != null ? ... : "", jsonArray) JobDAGInfo.java:62 <-- emits "" key for null pipeline id
Findings
Issue 1: NPE is still possible inside the newly guarded block: edge.getInputVertexId()/getTargetVertexId() can be null and .toString() will throw
- Location:
seatunnel-engine/seatunnel-engine-core/src/main/java/org/apache/seatunnel/engine/core/job/JobDAGInfo.java:57 - Why it matters: - Root cause: the new
if (edge != null)guard only protects against a nullEdgeelement, but the very next lines still dereferenceedge.getInputVertexId().toString()andedge.getTargetVertexId().toString()without any check.Edgevertex IDs areLongwrappers that can legitimately be null (e.g., a source/sink boundary edge or a partially built DAG), which is exactly the class of failure this PR claims to fix. - Impact: an NPE thrown here bubbles out of
toJsonObject()and breaks the Zeta REST endpoints / job info reporting that serializeJobDAGInfo, so the 'defensive' fix does not actually remove the crash scenario it targets — it only moves it two lines down. - Fix: extract the IDs first (
Long inputId = edge.getInputVertexId();), and either skip the edge with alog.warnwhen either ID is null, or serialize a null-safe string viaString.valueOf(inputId). Skipping with a warning is preferable so consumers do not receive edges with fabricated "null" vertex IDs. - Evidence:
seatunnel-engine/seatunnel-engine-core/src/main/java/org/apache/seatunnel/engine/core/job/JobDAGInfo.java:58 edgeJsonObject.add("targetVertexId", edge.getTargetVertexId().toString());;seatunnel-engine/seatunnel-engine-core/src/main/java/org/apache/seatunnel/engine/core/job/JobDAGInfo.java:55 if (edge != null) { - Suggested fix: The
edge != nullguard is not sufficient here:edge.getInputVertexId()andedge.getTargetVertexId()returnLongwrappers that can themselves be null, so.toString()on the next two lines can still throw an NPE — the exact failure mode this PR sets out to prevent. Please null-check the IDs as well, e.g. skip the edge with alog.warn("Edge with null vertex id in pipeline {} skipped", entry.getKey())when either ID is null, or useString.valueOf(...)if you intend to serialize them regardless. - Severity: High
Issue 2: PR description promises null checks for jobId/vertexId/envOptions and nested type paths, but the diff only guards the pipelineEdges loop — most of the claimed NPE fixes are missing
- Location:
seatunnel-engine/seatunnel-engine-core/src/main/java/org/apache/seatunnel/engine/core/job/JobDAGInfo.java:50 - Why it matters: - Root cause: the description states this PR handles nulls for
jobId,vertexId,envOptions, and deep paths likevertexInfo.getType().getType(), yet the only changed file/lines are thepipelineEdgesloop inJobDAGInfo.toJsonObject(). None of those other paths appear in the diff. - Impact: reviewers and users reading the changelog would believe the REST API DAG serialization is fully hardened, while the majority of the described NPE surfaces remain unguarded in the Zeta engine's job info reporting.
- Fix: either implement the remaining guards described in the PR body (jobId, envOptions fallback, vertex type null handling) in this PR, or rewrite the PR description to accurately scope it to the pipelineEdges loop only. Also please link the GitHub issue that reproduces the NPE, per the contribution checklist.
- Evidence:
seatunnel-engine/seatunnel-engine-core/src/main/java/org/apache/seatunnel/engine/core/job/JobDAGInfo.java:55 if (edge != null) { - Suggested fix: The PR description says this change adds null guards for
jobId,vertexId,envOptions, andvertexInfo.getType().getType(), but the diff only covers thepipelineEdgesloop. Please either add the remaining guards described in the PR body or update the description to match the actual scope, and link the issue that reproduces the NPE. - Severity: High
Issue 3: A null pipeline id is serialized as an empty-string JSON key "", producing a malformed DAG payload that REST/UI consumers cannot correlate to any pipeline.
- Location:
seatunnel-engine/seatunnel-engine-core/src/main/java/org/apache/seatunnel/engine/core/job/JobDAGInfo.java:63 - Why it matters: - Root cause: line 63 substitutes
""whenentry.getKey()is null:pipelineEdgesJsonObject.add(entry.getKey() != null ? entry.getKey().toString() : "", jsonArray). - Impact: the
pipelineEdgesmap in the REST response is keyed by pipeline id (an integer). Emitting"": [...]silently changes the JSON contract — the SeaTunnel web UI and any automation parsing the job DAG will encounter a key that parses to no valid pipeline id, which is arguably worse than a loud failure because the edges get attached to a phantom pipeline. - A null key here can only come from an engine-side construction bug (pipeline ids are assigned by the LogicalDag/plan builder), so masking it hides the real defect.
- Fix: skip the entry with a WARN log (
log.warn("Skip pipeline edges with null pipeline id in job {}", jobId)) instead of inventing an empty key, keeping the JSON contract intact. - Suggested fix: Falling back to an empty-string key changes the JSON contract of
pipelineEdges(keys are pipeline ids consumed by the UI/REST clients). A"": [...]entry cannot be correlated to any pipeline and silently corrupts the payload. Since a null key can only come from an engine-side construction bug, please skip the entry with a WARN log instead of emitting an empty key. - Severity: Medium
Review conclusion
Conclusion: can merge after the blocking items are fixed
1. Blocking items
- Issue 1: NPE is still possible inside the newly guarded block: edge.getInputVertexId()/getTargetVertexId() can be null and .toString() will throw
- Issue 2: PR description promises null checks for jobId/vertexId/envOptions and nested type paths, but the diff only guards the pipelineEdges loop — most of the claimed NPE fixes are missing
2. Suggested (non-blocking) follow-ups
- Issue 3: A null pipeline id is serialized as an empty-string JSON key
"", producing a malformed DAG payload that REST/UI consumers cannot correlate to any pipeline.
Nice progress overall — once the blocking points above are addressed this should be in good shape. Happy to discuss.
|
Thanks @SEZ9. I rechecked the same head Your additional point about So from Daniel's side the current unchanged head still needs a code update before merge:
The But enabling CI alone will not clear the PR yet — the source-side blocker remains open on this head. |
|
Rechecked the current head Runtime path recheckedMerge conclusionConclusion: still needs fixes before merge
|
Purpose of this pull request
This pull request introduces comprehensive defensive null-checks in
JobDAGInfo.javainside thetoJsonObject()method to prevent potentialNullPointerException(NPE) runtime failures during internal job configuration serialization or cluster state reporting via the REST API.Specifically, this PR ensures that:
vertexInfo.getType().getType()) check for intermediary null references.jobId,vertexId, and list elements are safely handled with clean ternary operators rather than crashing the thread with unboxing or direct method access failures.envOptionsfallback safely to an empty JSON structure when absent.Does this PR introduce any user-facing change?
No. This is a purely internal stabilization fix that hardens runtime execution safety. It does not alter behavior or configuration formats for users.
How was this patch tested?
seatunnel-engine-coresub-module.mvn spotless:applylocally before final commit.Check list
New License Guide
incompatible-changes.mdto describe the incompatibility caused by this PR.