Skip to content

[SPARK-58616][CORE][PYTHON] Attribute a reason for context-wide job cancellation in cancelAllJobs - #57824

Open
ganeshashree wants to merge 1 commit into
apache:masterfrom
ganeshashree:SPARK-58616
Open

[SPARK-58616][CORE][PYTHON] Attribute a reason for context-wide job cancellation in cancelAllJobs#57824
ganeshashree wants to merge 1 commit into
apache:masterfrom
ganeshashree:SPARK-58616

Conversation

@ganeshashree

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

cancelAllJobs is the only cancellation entry point with no way to say why the cancel happened. Every job it aborts fails with the same fixed message:

[SPARK_JOB_CANCELLED] Job 7 cancelled as part of cancellation of all jobs

This threads an optional reason through the path SparkContext.cancelAllJobs -> DAGScheduler.cancelAllJobs -> AllJobsCancelled -> doCancelAllJobs -> handleJobCancellation, so it lands in the SPARK_JOB_CANCELLED error of every cancelled job. This follows the shape SPARK-48900 established for cancelJobGroup and cancelJobsWithTag.

  • AllJobsCancelled gains reason: Option[String] (default None), matching the sibling events JobCancelled / JobGroupCancelled / JobTagCancelled.
  • New cancelAllJobs(reason: String) overloads on SparkContext and JavaSparkContext; PySpark's cancelAllJobsgains an optionalreason`. The existing no-arg forms are untouched, so the change is purely additive.
  • When no reason is supplied, doCancelAllJobs falls back to DAGScheduler.DEFAULT_CANCEL_ALL_JOBS_REASON — the historical wording verbatim, so the default-path message is byte-identical.
  • cancelAllJobs now logs that it was invoked, which its siblings already did.
  • Internal callers now pass a reason: the REPL SIGINT handler, the Spark SQL CLI SIGINT handler, and the DAGScheduler event-loop onError shutdown path.

The message template is already Job <jobId> cancelled <reason>, so error-conditions.json is unchanged.

Why are the changes needed?

The fixed message says what happened but not who triggered it or why, and it is reported identically on jobs that were perfectly healthy and are only being aborted as collateral of someone else's cancel. When reading logs there is no way to tell a context-wide cancellation from a genuine failure of the job in question, which makes this class of failure slow to investigate.

Does this PR introduce any user-facing change?

Yes, additively:

  1. New cancelAllJobs(reason) in Scala and Java, and a new optional reason parameter in PySpark.
  2. The reason in SPARK_JOB_CANCELLED becomes caller-dependent for the internal callers that now supply one. A shell interrupt previously produced Job 7 cancelled as part of cancellation of all jobs; it now produces Job 7 cancelled because the driver process received an interrupt signal (SIGINT).

Callers that supply no reason still produce the original text, and the error condition (SPARK_JOB_CANCELLED) and SQLSTATE (XXKDA) are unchanged, so consumers matching on the error class are unaffected. A consumer string-matching the full message of an attributed cancel would see the new wording.

How was this patch tested?

  • Two new cases in DAGSchedulerSuite covering both branches of the fallback, asserting via checkError on condition, SQLSTATE, and message parameters. DAGSchedulerSuite and JobCancellationSuite pass in full (234 tests).
  • A new case in python/pyspark/tests/test_context.py that cancels a real running job with a reason and asserts it reaches the job's error while the generic wording does not appear, this covers the py4j round trip that unit-level stubs cannot. Confirmed to fail when the reason is not forwarded to the JVM.

Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 5)

…ancellation in cancelAllJobs

### What changes were proposed in this pull request?

When a context-wide cancellation fires, every job caught by it fails with the fixed
message `Job <id> cancelled as part of cancellation of all jobs`. That says what
happened but not who triggered it or why, and it is reported identically on jobs
that were healthy and are only being aborted as collateral.

This threads an optional reason through the context-wide cancel path
(`SparkContext.cancelAllJobs` -> `DAGScheduler.cancelAllJobs` -> `AllJobsCancelled`
-> `doCancelAllJobs` -> `handleJobCancellation`) so it surfaces in the
`SPARK_JOB_CANCELLED` error of every cancelled job. This mirrors what SPARK-48900
already did for `cancelJobGroup` and `cancelJobsWithTag`.

- `AllJobsCancelled` gains `reason: Option[String]` (default `None`), matching the
  sibling events `JobCancelled` / `JobGroupCancelled` / `JobTagCancelled`.
- `doCancelAllJobs` falls back to `DAGScheduler.DEFAULT_CANCEL_ALL_JOBS_REASON`,
  the historical wording verbatim, so the default-path message is byte-identical.
- New `cancelAllJobs(reason: String)` overloads on `SparkContext` and
  `JavaSparkContext`, following the shape of the existing `cancelJobGroup` /
  `cancelJobsWithTag` reason overloads. The no-arg versions are unchanged, so this
  is additive and binary-compatible.
- PySpark's `SparkContext.cancelAllJobs` gains an optional `reason` parameter that
  delegates to the new JVM overload, so Python callers can attribute a cancel too.
- `cancelAllJobs` now also logs that it was invoked, which its siblings already did
  and it did not.
- Internal callers now pass a reason: the REPL SIGINT handler, the Spark SQL CLI
  SIGINT handler, and the DAGScheduler event-loop `onError` shutdown path.

The error-message template is already `Job <jobId> cancelled <reason>`, so no
`error-conditions.json` change is needed.

### Why are the changes needed?

A cancellation with no attribution cannot be told apart from a genuine query
failure when reading logs, which makes investigating this class of failure slow.

### Does this PR introduce _any_ user-facing change?

Yes, additively. New `cancelAllJobs(reason)` overloads in Scala/Java and a new
optional `reason` parameter in PySpark. The error condition
(`SPARK_JOB_CANCELLED`) and SQLSTATE (`XXKDA`) are unchanged, and callers that
supply no reason still produce the original message text.

### How was this patch tested?

- Two new cases in `DAGSchedulerSuite` covering both branches of the fallback,
  asserting via `checkError` on condition, SQLSTATE, and message parameters.
  `DAGSchedulerSuite` and `JobCancellationSuite` pass in full (234 tests).
- A new end-to-end case in `python/pyspark/tests/test_context.py` that cancels a
  real running job and asserts the supplied reason reaches the job's error while
  the generic "as part of cancellation of all jobs" does not appear. Confirmed to
  fail when the reason is not forwarded to the JVM.

Co-authored-by: Isaac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants