[SPARK-58386][SS] Restore backward compatibility of SupportsRealTimeRead.nextWithTimeout - #57580
Conversation
uros-b
left a comment
There was a problem hiding this comment.
Thank you @eason-yuchen-liu! Can you just please add automated tests / regression guards to covers the new delegation path?
|
Thanks. Added a test. |
HeartSaVioR
left a comment
There was a problem hiding this comment.
The code change looks OK. We now lost the ability of compile time check which is a major regression, but I understand compatibility breakage is maybe more severe.
My comments are mostly for documentation, especially the clear guidance for 3rd party. We now concern about backward compatibility, implying 3rd party - while we are here, let's avoid confusion to 3rd party and make this be clear to them.
There was a problem hiding this comment.
This is getting complicated for 3rd party data source implementations. Can we revisit the explanation from 3rd party perspective, and provide the high-level guide in classdoc? Is LowLatencyClock exposed to 3rd party? How they would have tested on their data source implementation?
I'd rather give more direct guidance about which one to use.
If 3rd party isn't expected to leverage LowLatencyClock, we should have guided them to use only RecordStatus nextWithTimeout(Long timeoutMs) throws IOException and describe default RecordStatus nextWithTimeout(Long startTimeMs, Long timeoutMs) throws IOException as internal usage.
If we think LowLatencyClock is a must for 3rd party to test their data source implementation, I'd rather guide RecordStatus nextWithTimeout(Long timeoutMs) throws IOException to be a legacy and encourage to implement default RecordStatus nextWithTimeout(Long startTimeMs, Long timeoutMs) throws IOException.
(Though the new test suite clarifies that LowLatencyClock isn't meant to be used from 3rd party data source implementations, so this is unlikely true.)
| * must keep compiling against these interfaces, and this test runs it end-to-end through a real | ||
| * RTM streaming query to prove an external-style source is still driven correctly. | ||
| */ | ||
| class StreamingRealTimeModeSourceSuite extends StreamRealTimeModeManualClockSuiteBase { |
There was a problem hiding this comment.
I'd add Compat (or Compatibility as full representation) in the test suite name explicitly; we may want to have some tests in RTM source in general in future.
| class StreamingRealTimeModeSourceSuite extends StreamRealTimeModeManualClockSuiteBase { | ||
| import testImplicits._ | ||
|
|
||
| test("RTM source built from only public connector APIs reads end-to-end") { |
There was a problem hiding this comment.
Don't we want to have two params as well? I know internal data sources use two params so it sounds like redundant, but maybe good to be explicit. Maybe we could reuse a lot of code for one param version of classes, via inheritance, or even just source option to dispatch the class.
|
|
||
| /** | ||
| * The top-level source, registered as a `TableProvider` + `DataSourceRegister`. Loaded by fully | ||
| * qualified class name via `spark.readStream.format(...)`, so it needs no `META-INF/services` |
There was a problem hiding this comment.
nit: it doesn't need sounds more natural
| } | ||
| // Exhausted this batch's data: keep waiting until the caller's timeout elapses, then report | ||
| // no record -- the same wait-until-timeout behavior a real source has. Measured against the | ||
| // wall clock, since this legacy overload is not given the engine's reference start time. |
There was a problem hiding this comment.
nit: this legacy overload this is totally dependent on which method would be the main entry for 3rd party. If they are expected to use one param, it's not a legacy thing.
HyukjinKwon
left a comment
There was a problem hiding this comment.
0 blocking, 1 non-blocking, 0 nits.
Correct, well-tested compatibility restoration; one non-blocking doc suggestion for third-party authors.
Suggestions (1)
- sql/catalyst/src/main/java/org/apache/spark/sql/connector/read/streaming/SupportsRealTimeRead.java:92: Add class-level guidance on which nextWithTimeout overload third parties should implement -- see inline
Verification
Confirmed there is no infinite recursion (2-arg delegates to 1-arg; 1-arg default throws rather than calling back) and that a source overriding neither fails loud with a clear message. The new CompatRealTimePartitionReader overrides only the single-arg overload and is exercised through the engine's two-arg call path, validating the delegation. The loss of compile-time enforcement (both methods now default) is a tradeoff the maintainer explicitly accepted for source compatibility.
| * @return {@link RecordStatus} describing whether a record is available and its arrival time | ||
| * @throws IOException | ||
| */ | ||
| default RecordStatus nextWithTimeout(Long timeoutMs) throws IOException { |
There was a problem hiding this comment.
A class-level note would help third-party source authors: implement nextWithTimeout(Long timeoutMs) for the common case, and only override the two-arg nextWithTimeout(Long startTimeMs, Long timeoutMs) if you need the engine's reference start time (LowLatencyClock is engine-internal, not part of the third-party contract). The per-method javadoc already says "override exactly one", but since both are now default the "must implement one" check only fails at runtime, so the high-level "which one and why" up front is worth it. (Raised by @HeartSaVioR as well.)
|
Thanks for the review. Addressed the comments. |
|
@eason-yuchen-liu |
…ead.nextWithTimeout ### What changes were proposed in this pull request? Restore the single-argument `nextWithTimeout(Long timeoutMs)` method on the `@Evolving` `SupportsRealTimeRead` interface, which SPARK-55699 replaced with `nextWithTimeout(Long startTimeMs, Long timeoutMs)`. Both overloads are now `default` methods: - `nextWithTimeout(Long)` throws `UnsupportedOperationException` by default. - `nextWithTimeout(Long, Long)` delegates to `nextWithTimeout(Long)` by default, ignoring `startTimeMs`. A source overrides whichever one it needs. The engine always invokes the two-argument overload, so a source that overrides only the single-argument variant (as external 4.1-era sources do) is driven through the delegation. ### Why are the changes needed? SPARK-55699 changed the sole abstract method on the public interface rather than adding an overload, which is a source- and binary-incompatible break. An external source (a custom MQTT Real-Time Mode connector) that implemented `nextWithTimeout(Long)` no longer compiles or links after upgrading from Spark 4.1 to 4.2. The `startTimeMs` parameter is only needed for the manual-clock/test path, so it should not be forced on external implementors. ### Does this PR introduce _any_ user-facing change? Yes. It restores the Spark 4.1 `nextWithTimeout(Long)` method on the `SupportsRealTimeRead` interface, so external sources written against Spark 4.1 compile and link again. ### How was this patch tested? Verified locally that a Real-Time Mode source implementing only the single-argument `nextWithTimeout(Long)` is driven correctly end-to-end by a streaming query through the two-argument default delegation, and that existing RTM suites (`StreamRealTimeModeSuite`, `StreamRealTimeModeWithManualClockSuite`, `StreamRealTimeModeE2ESuite`) continue to pass. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 4.8) Co-authored-by: Isaac
…RTM source SPI ### What changes were proposed in this pull request? Add `StreamingRealTimeModeSourceSuite`, which defines a self-contained Real-Time Mode (RTM) streaming source written exclusively against the public `@Evolving` connector APIs (`TableProvider`, `Table`/`SupportsRead`, `ScanBuilder`/`Scan`, `MicroBatchStream` + `SupportsRealTimeMode`, `PartitionReaderFactory`, `SupportsRealTimeRead`, and custom `Offset`/`PartitionOffset`). The source's partition reader intentionally overrides only the legacy single-argument `nextWithTimeout(Long)` -- the Spark 4.1 contract -- so it is exercised purely through the interface's two-argument default delegation. The test runs the frozen source end-to-end through a real RTM streaming query (via the manual-clock test harness) and asserts the rows are read. ### Why are the changes needed? SPARK-55699 broke source-level backward compatibility of `SupportsRealTimeRead` by replacing an abstract method rather than adding an overload (restored in SPARK-58386). This suite is a guard against future regressions of the same kind: because the frozen source uses only public APIs and overrides only the single-arg method, a future source-incompatible change to `SupportsRealTimeMode` / `SupportsRealTimeRead` will make this file fail to compile, and the end-to-end run verifies an external-style source is still driven correctly at runtime. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? New test suite `StreamingRealTimeModeSourceSuite`. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 4.8) Co-authored-by: Isaac
…oaden compat test ### What changes were proposed in this pull request? Follow-up to review feedback on the RTM connector backward-compatibility change: - `SupportsRealTimeRead`: add interface-level guidance on which `nextWithTimeout` overload third parties should implement, and reword the per-method Javadocs to frame `nextWithTimeout(Long timeoutMs)` as the recommended method and `nextWithTimeout(Long startTimeMs, Long timeoutMs)` as the engine-invoked overload meant for engine-internal sources (the reference clock is not part of the third-party contract). Dropped the "legacy" framing. Docs only; no behavior change. - Rename `StreamingRealTimeModeSourceSuite` to `StreamingRealTimeModeSourceCompatSuite`, reserving the generic name for future general RTM-source tests. - Broaden the guard: extract shared reader logic into a base class and add a second reader that overrides the two-arg `nextWithTimeout` directly, alongside the existing single-arg reader. The source dispatches on a `twoArg` option, and the suite runs both variants end-to-end so the guard tracks either entry point. ### Why are the changes needed? Reviewers asked for clearer third-party guidance on the two overloads and for test coverage of a source that implements the two-arg overload directly, not only the single-arg one reached via default delegation. ### Does this PR introduce _any_ user-facing change? Yes. It restores the Spark 4.1 `nextWithTimeout(Long)` method on the `SupportsRealTimeRead` interface, so external sources written against Spark 4.1 compile and link again. ### How was this patch tested? `StreamingRealTimeModeSourceCompatSuite` runs a public-API-only RTM source end-to-end for both `nextWithTimeout` entry points (single-arg via default delegation, and two-arg directly). ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 4.8) Co-authored-by: Isaac
0bd7887 to
8b559a6
Compare
|
Thanks! Merging to master/4.x/4.3. |
…ead.nextWithTimeout ### What changes were proposed in this pull request? Two changes for the public Real-Time Mode (RTM) connector SPI: 1. Restore the single-argument `nextWithTimeout(Long timeoutMs)` method on the `Evolving` `SupportsRealTimeRead` interface, which SPARK-55699 replaced with `nextWithTimeout(Long startTimeMs, Long timeoutMs)`. Both overloads are now `default` methods: - `nextWithTimeout(Long)` throws `UnsupportedOperationException` by default. - `nextWithTimeout(Long, Long)` delegates to `nextWithTimeout(Long)` by default, ignoring `startTimeMs`. A source overrides whichever one it needs. The engine always invokes the two-argument overload, so a source that overrides only the single-argument variant (as external Spark 4.1-era sources do) is driven through the delegation. Internal implementors (`LowLatencyMemoryStream`, Kafka) already override the two-argument variant and are unchanged. 2. Add `StreamingRealTimeModeSourceSuite`, a backward-compatibility guard. It defines a self-contained RTM streaming source written exclusively against the public `Evolving` connector APIs (`TableProvider`, `Table`/`SupportsRead`, `ScanBuilder`/`Scan`, `MicroBatchStream` + `SupportsRealTimeMode`, `PartitionReaderFactory`, `SupportsRealTimeRead`, and custom `Offset`/`PartitionOffset`). The source's partition reader intentionally overrides only the legacy single-argument `nextWithTimeout(Long)`, so it is exercised purely through the two-argument default delegation, and the suite runs it end-to-end through a real RTM streaming query. ### Why are the changes needed? SPARK-55699 changed the sole abstract method on the public interface rather than adding an overload, which is a source- and binary-incompatible break. An external source (a custom MQTT Real-Time Mode connector) that implemented `nextWithTimeout(Long)` no longer compiles or links after upgrading from Spark 4.1 to 4.2. The `startTimeMs` parameter is only needed for the manual-clock/test path, so it should not be forced on external implementors. The new guard suite protects against future regressions of the same kind: because the frozen source uses only public APIs and overrides only the single-arg method, a future source-incompatible change to `SupportsRealTimeMode` / `SupportsRealTimeRead` will make the suite fail to compile, and the end-to-end run verifies an external-style source is still driven correctly at runtime. ### Does this PR introduce _any_ user-facing change? Yes. It restores the Spark 4.1 `nextWithTimeout(Long)` method on the `SupportsRealTimeRead` interface, so external sources written against Spark 4.1 compile and link again. ### How was this patch tested? New test suite `StreamingRealTimeModeSourceSuite`, which drives a public-API-only RTM source end-to-end through the two-argument default delegation. Existing RTM suites (`StreamRealTimeModeSuite`, `StreamRealTimeModeWithManualClockSuite`, `StreamRealTimeModeE2ESuite`) continue to pass. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 4.8) Closes #57580 from eason-yuchen-liu/SC-234671-restore-nextWithTimeout-compat. Authored-by: Yuchen Liu <170372783+eason-yuchen-liu@users.noreply.github.com> Signed-off-by: Jungtaek Lim <kabhwan.opensource@gmail.com> (cherry picked from commit 041ed93) Signed-off-by: Jungtaek Lim <kabhwan.opensource@gmail.com>
…ead.nextWithTimeout ### What changes were proposed in this pull request? Two changes for the public Real-Time Mode (RTM) connector SPI: 1. Restore the single-argument `nextWithTimeout(Long timeoutMs)` method on the `Evolving` `SupportsRealTimeRead` interface, which SPARK-55699 replaced with `nextWithTimeout(Long startTimeMs, Long timeoutMs)`. Both overloads are now `default` methods: - `nextWithTimeout(Long)` throws `UnsupportedOperationException` by default. - `nextWithTimeout(Long, Long)` delegates to `nextWithTimeout(Long)` by default, ignoring `startTimeMs`. A source overrides whichever one it needs. The engine always invokes the two-argument overload, so a source that overrides only the single-argument variant (as external Spark 4.1-era sources do) is driven through the delegation. Internal implementors (`LowLatencyMemoryStream`, Kafka) already override the two-argument variant and are unchanged. 2. Add `StreamingRealTimeModeSourceSuite`, a backward-compatibility guard. It defines a self-contained RTM streaming source written exclusively against the public `Evolving` connector APIs (`TableProvider`, `Table`/`SupportsRead`, `ScanBuilder`/`Scan`, `MicroBatchStream` + `SupportsRealTimeMode`, `PartitionReaderFactory`, `SupportsRealTimeRead`, and custom `Offset`/`PartitionOffset`). The source's partition reader intentionally overrides only the legacy single-argument `nextWithTimeout(Long)`, so it is exercised purely through the two-argument default delegation, and the suite runs it end-to-end through a real RTM streaming query. ### Why are the changes needed? SPARK-55699 changed the sole abstract method on the public interface rather than adding an overload, which is a source- and binary-incompatible break. An external source (a custom MQTT Real-Time Mode connector) that implemented `nextWithTimeout(Long)` no longer compiles or links after upgrading from Spark 4.1 to 4.2. The `startTimeMs` parameter is only needed for the manual-clock/test path, so it should not be forced on external implementors. The new guard suite protects against future regressions of the same kind: because the frozen source uses only public APIs and overrides only the single-arg method, a future source-incompatible change to `SupportsRealTimeMode` / `SupportsRealTimeRead` will make the suite fail to compile, and the end-to-end run verifies an external-style source is still driven correctly at runtime. ### Does this PR introduce _any_ user-facing change? Yes. It restores the Spark 4.1 `nextWithTimeout(Long)` method on the `SupportsRealTimeRead` interface, so external sources written against Spark 4.1 compile and link again. ### How was this patch tested? New test suite `StreamingRealTimeModeSourceSuite`, which drives a public-API-only RTM source end-to-end through the two-argument default delegation. Existing RTM suites (`StreamRealTimeModeSuite`, `StreamRealTimeModeWithManualClockSuite`, `StreamRealTimeModeE2ESuite`) continue to pass. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 4.8) Closes #57580 from eason-yuchen-liu/SC-234671-restore-nextWithTimeout-compat. Authored-by: Yuchen Liu <170372783+eason-yuchen-liu@users.noreply.github.com> Signed-off-by: Jungtaek Lim <kabhwan.opensource@gmail.com> (cherry picked from commit 041ed93) Signed-off-by: Jungtaek Lim <kabhwan.opensource@gmail.com>
|
@HeartSaVioR Thanks! Should we also backport it to 4.2 to fix the regression on that version? |
|
@eason-yuchen-liu Makes sense. Would you mind helping to create a PR for branch-4.2? Thanks in advance! |
What changes were proposed in this pull request?
Two changes for the public Real-Time Mode (RTM) connector SPI:
Restore the single-argument
nextWithTimeout(Long timeoutMs)method on the@EvolvingSupportsRealTimeReadinterface, which SPARK-55699 replaced withnextWithTimeout(Long startTimeMs, Long timeoutMs). Both overloads are nowdefaultmethods:nextWithTimeout(Long)throwsUnsupportedOperationExceptionby default.nextWithTimeout(Long, Long)delegates tonextWithTimeout(Long)by default, ignoringstartTimeMs.A source overrides whichever one it needs. The engine always invokes the two-argument overload, so a source that overrides only the single-argument variant (as external Spark 4.1-era sources do) is driven through the delegation. Internal implementors (
LowLatencyMemoryStream, Kafka) already override the two-argument variant and are unchanged.Add
StreamingRealTimeModeSourceSuite, a backward-compatibility guard. It defines a self-contained RTM streaming source written exclusively against the public@Evolvingconnector APIs (TableProvider,Table/SupportsRead,ScanBuilder/Scan,MicroBatchStream+SupportsRealTimeMode,PartitionReaderFactory,SupportsRealTimeRead, and customOffset/PartitionOffset). The source's partition reader intentionally overrides only the legacy single-argumentnextWithTimeout(Long), so it is exercised purely through the two-argument default delegation, and the suite runs it end-to-end through a real RTM streaming query.Why are the changes needed?
SPARK-55699 changed the sole abstract method on the public interface rather than adding an overload, which is a source- and binary-incompatible break. An external source (a custom MQTT Real-Time Mode connector) that implemented
nextWithTimeout(Long)no longer compiles or links after upgrading from Spark 4.1 to 4.2. ThestartTimeMsparameter is only needed for the manual-clock/test path, so it should not be forced on external implementors.The new guard suite protects against future regressions of the same kind: because the frozen source uses only public APIs and overrides only the single-arg method, a future source-incompatible change to
SupportsRealTimeMode/SupportsRealTimeReadwill make the suite fail to compile, and the end-to-end run verifies an external-style source is still driven correctly at runtime.Does this PR introduce any user-facing change?
Yes. It restores the Spark 4.1
nextWithTimeout(Long)method on theSupportsRealTimeReadinterface, so external sources written against Spark 4.1 compile and link again.How was this patch tested?
New test suite
StreamingRealTimeModeSourceSuite, which drives a public-API-only RTM source end-to-end through the two-argument default delegation. Existing RTM suites (StreamRealTimeModeSuite,StreamRealTimeModeWithManualClockSuite,StreamRealTimeModeE2ESuite) continue to pass.Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8)