Skip to content

[Flink] Make Flink 2.x job submission work on REMOTE mode - #4500

Open
88fantasy wants to merge 2 commits into
apache:devfrom
88fantasy:fix/flink-2x-submission
Open

[Flink] Make Flink 2.x job submission work on REMOTE mode#4500
88fantasy wants to merge 2 commits into
apache:devfrom
88fantasy:fix/flink-2x-submission

Conversation

@88fantasy

@88fantasy 88fantasy commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What is the purpose of the change

Makes Flink 2.x job submission work on REMOTE mode. Today no Flink 2.x SQL job can be submitted at all, while Flink 1.x is unaffected — which is why this went unnoticed.

Closes #4499

Stacked on #4496. This branch contains that PR's commit as its base, so the diff to review here is the second commit only. If #4496 merges first this rebases away cleanly; if it is closed, this needs its setUserClassPaths change kept.

Brief change log

Five independent causes on one path — each only becomes visible once the previous is fixed:

  1. FlinkShimsProxy was not loading the per-version shims jar. It matches a name shaped streampark-flink-shims_flink-<major>_<scala>, which those artifacts carried until e770d2e8e renamed them without the Scala suffix. Both spellings are accepted now.

  2. The same rename silenced the rule that pulls in the rest of StreamPark's Flink jars (^streampark-.*_<scala>.*$), so the whole client stack was loaded by the console's own classloader and resolved org.apache.flink.* from the console's fixed baseline Flink rather than the registered target version. That, I believe, is the actual mechanism behind [Bug] PipelineExecutorFactory ServiceConfigurationError when submitting a Flink job (ChildFirstClassLoader / console baseline Flink version conflict) #4483 — the ServiceLoader mismatch it reports is what a half-populated shims classloader looks like from the outside.

  3. shims-base and shims-base-v2 share twelve class names (v2 redeclares them for Flink 2.x, inheriting the rest) yet both were added to every shims classloader in listFiles() order — so which Flink version a class was compiled for was decided by the filesystem. A 1.x target no longer sees v2 at all; a 2.x target gets v2 ahead of the base.

  4. API removed in Flink 2.x. SavepointConfigOptions is gone (2.x declares the same keys in StateRecoveryOptions), and Configuration's typed accessors over a ConfigOption went with it. Since this module is compiled once against a single baseline but submits to whichever version the user registered, both are addressed portably: the savepoint options are declared from their keys — byte-identical across 1.17–2.3, verified against the distributions — and the generic get/set replace the typed accessors.

  5. ClusterClient#submitJob widened its parameter from JobGraph to ExecutionPlan in 2.x. The instance satisfies either signature, so the call is made reflectively.

Additionally, a FLINK_SQL program's classloader is told to resolve org.apache.streampark.* parent-first: the fat jar bundles whichever shims it was built against, while the parent is now the shims classloader for the version actually registered, and loading both ends in a LinkageError as soon as one references the other.

Verifying this change

Verified against real standalone clusters by driving StreamPark's own submission path out-of-process against the deployed console's lib/, so each fix could be confirmed against the actual artifacts:

  • A Flink SQL job submits and reaches FINISHED on Flink 2.2.1 — previously impossible.
  • The same job on Flink 1.20.4, which worked before this change, still does. This is the important one: cause (3) surfaced as a regression on 1.20 while fixing (1) and (2), and was only caught because 1.x was re-tested at every step.
  • The classloader behaviour underlying (1)/(2) was checked directly: with the target version's jars reachable, org.apache.flink.util.ParameterTool resolves from flink-dist-2.2.1.jar; without them it fails exactly as reported.
  • The savepoint option keys were read out of both distributions' bytecode and are identical.

Then confirmed through the console itself, not just the extracted path: a deployed build submits a
Flink SQL job (mysql-cdc -> Paimon, two sinks) to the 2.2.1 cluster, it reaches FINISHED, and the
job appears downstream exactly as a Flink 1.x job does.

Does this pull request potentially affect one of the following parts

  • Dependencies (does it add or upgrade a dependency): no
  • The public API: no
  • The runtime per-record code paths (performance sensitive): no — client-side submission only
  • Anything that affects deployment: yes, in effect — the shims classloader is now populated as it was designed to be, so the submission stack binds to the registered Flink version rather than the console's baseline. That is the intended behaviour, but it does change which classes a submission runs against, which is why 1.x was re-verified end to end.

Touches FlinkShimsProxy, which AGENTS.md marks high-sensitivity. No static state is introduced; the changes are to which jars go into the classloader and in what order.

Not addressed: LocalClient and KubernetesNativeSessionClient use the same removed Configuration accessors and will fail the same way on Flink 2.x. Neither is reachable in the environment this was verified in, so I have left them for a change that can be tested rather than guessed at.

Documentation

  • Does this pull request introduce a new feature? no — it makes an advertised one (Flink 2.x support, added in e770d2e8e) actually work.

Six defects on the path that turns a saved Flink SQL application into a
submitted job. Each was found by submitting a real Flink SQL job to a
standalone cluster and fixing whatever failed next; they are independent
of each other but all sit on this one path.

1. ClassLoaderUtils.runAsClassLoader restored the context classloader
   captured in a static field when the class was first initialized —
   whichever thread happened to load it — instead of the one the calling
   thread had on entry. On pooled threads that silently replaces an
   unrelated thread's context classloader.

2. FlinkClientTrait.getCustomCommandLines and
   RemoteClient.getStandAloneClusterDescriptor call into Flink classes
   bound to the Flink version bundled with this module, while the calling
   thread's context classloader is FlinkShimsProxy's target-version shims
   classloader. Their internal ServiceLoader lookups therefore resolved
   providers from a different Flink version than the interfaces bundled
   here, failing with ServiceConfigurationError ("not a subtype"). Both
   call sites now run under their own class's classloader. Closes apache#4483.

3. The build-response getters (workspacePath, pass, shadedJarPath,
   flinkBaseImage, mainJarPath, extraLibJarPaths, flinkImageTag,
   podTemplatePaths, dockerInnerMainJarPath) do not follow JavaBean
   getter naming and carried no @JsonProperty, so Jackson silently
   skipped them: every build result persisted to t_flink_app's
   buildResultJson lost its paths, and only pass survived — by the
   coincidence that its field default is already true. A later submit
   then read back shadedJarPath == null and failed with an NPE, an
   "entry point class not found", or "flinkJobJar is null", depending on
   which downstream path consumed it.

4. SubmitRequest.userJarFile() passed shadedJarPath() straight to
   new File(...), which throws NPE when it is legitimately null.

5. streampark-console-service declared a compile dependency on
   streampark-flink-shims-base but not on streampark-flink-shims-base-v2,
   so FlinkTableInitializerV2 never reached the console's lib/ and every
   Flink 2.x SQL job failed with NoClassDefFoundError. Flink 1.x was
   unaffected, which is why this went unnoticed.

6. PackagedProgram's setUserClassPaths, disabled wholesale for apache#3761, is
   re-enabled for FLINK_SQL jobs only, so a SQL job's connector jars reach
   the client classpath. Verified against a real cluster not to reproduce
   the ClassCastException apache#3761 describes, and it leaves every other job
   type on the existing behaviour.
Submitting a Flink SQL job to a Flink 2.x cluster failed with a
NoClassDefFoundError long before reaching the cluster. Flink 1.x was
unaffected, which is why this went unnoticed. Five independent causes,
each of which only becomes visible after the previous one is fixed:

1. FlinkShimsProxy stopped putting the version-specific shims jar into
   the shims classloader. It matches on a name shaped
   "streampark-flink-shims_flink-<ver>_<scala>", which those artifacts
   carried until e770d2e renamed them without the Scala suffix. Both
   spellings are accepted now.

2. The same rename silenced the rule that pulls in the rest of
   StreamPark's Flink jars ("has a _<scala> suffix"), so the client stack
   was loaded by the console's own classloader and resolved
   org.apache.flink.* from the console's fixed baseline Flink instead of
   the target version's jars. That is the actual mechanism behind apache#4483:
   the ServiceLoader mismatch it reports is what a half-populated shims
   classloader looks like from the outside.

3. shims-base and shims-base-v2 share twelve class names — v2 redeclares
   them for Flink 2.x and inherits the rest — but both were added to
   every shims classloader regardless of the target version, in directory
   listing order. Which Flink version a class had been compiled for was
   therefore decided by the filesystem. A 1.x target no longer sees v2 at
   all, and a 2.x target gets v2 ahead of the base.

4. SavepointConfigOptions was removed in Flink 2.x, and Configuration's
   typed accessors (getBoolean/setBoolean/getInteger over a ConfigOption)
   went with it. Since this module is compiled once against a single
   baseline but submits to whichever version the user registered, both
   are now addressed portably: the savepoint options are declared from
   their keys, which are byte-identical across every supported version,
   and the generic get/set replace the typed accessors.

5. ClusterClient#submitJob widened its parameter from JobGraph to
   ExecutionPlan in 2.x. The instance satisfies either signature, only
   the declared type moved, so the call is made reflectively.

A FLINK_SQL program's classloader is also told to resolve
org.apache.streampark.* parent-first. The fat jar bundles whichever shims
it was built against, while the parent is the shims classloader for the
version actually registered; loading both ends in a LinkageError as soon
as one references the other.

Verified against real standalone clusters by driving StreamPark's own
submission path out-of-process: a Flink SQL job now submits and reaches
FINISHED on Flink 2.2.1, and the same job on Flink 1.20.4 — which worked
before this change — still does.

Not addressed: LocalClient and KubernetesNativeSessionClient use the same
removed Configuration accessors and will fail the same way on Flink 2.x.
Neither is reachable in the environment this was verified in, so they are
left for a change that can be tested.
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] No Flink 2.x job can be submitted: the shims classloader is built without the per-version jars

1 participant