[fix][fn] Support deadLetterTopic and maxMessageRetries for Python functions - #26400
[fix][fn] Support deadLetterTopic and maxMessageRetries for Python functions#26400david-streamlio wants to merge 9 commits into
Conversation
|
A gap in this PR's reasoning that I found while auditing windowing support, and that a reviewer should weigh before merging. This PR makes the Python runtime honour // FunctionConfigUtils.doPythonChecks()
if (functionConfig.getMaxMessageRetries() != null && functionConfig.getMaxMessageRetries() >= 0) {
throw new IllegalArgumentException("Message retries not yet supported in python");
}So on master, That changes what this PR is worth on its own:
Options, and I do not have a strong view on which is right:
I would lean toward 1, since it keeps the runtime change reviewable on its own merits and makes the enabling change an explicit, visible decision rather than a side effect. Happy to open the follow-up either way. The same relationship exists for the Go runtime: |
…runtime FunctionConfig accepts maxMessageRetries and deadLetterTopic, both are carried into the instance as FunctionDetails.retryDetails, and the Java runtime applies them. The Python runtime ignored them entirely: nothing in python_instance.py referenced retryDetails, so a function created with --dead-letter-topic was accepted, reported back faithfully by functions get, and then routed nothing to the DLQ at runtime. Build a ConsumerDeadLetterPolicy from retryDetails in a new get_dead_letter_policy() and pass it to all three subscribe() call sites. The rules mirror the Java runtime, which guards on hasRetryDetails() in JavaInstanceRunnable and applies the policy in PulsarSource, setting the dead letter topic only when it is non-empty so the client can derive its "<topic>-<subscription>-DLQ" default. Two cases cannot mirror Java exactly, and both warn rather than failing the instance or silently doing nothing: - Java accepts maxMessageRetries >= 0, but the Python client's ConsumerDeadLetterPolicy rejects a redelivery count below 1, so zero cannot be expressed. Attaching no policy is the only option; a warning names the dead letter topic that will not receive messages. - A dead letter policy only takes effect on Shared and KeyShared subscriptions. retainOrdering and EFFECTIVELY_ONCE both select Failover, where the policy would be silently ineffective, so that combination warns too. Silently ineffective configuration is the bug this fixes, and it should not be reintroduced by the fix. Fixes apache#26397
bf193d3 to
5cc6207
Compare
…nctions ### Motivation `doPythonChecks` refuses any `maxMessageRetries >= 0`, so `pulsar-admin functions create --py ... --max-message-retries 3` fails at creation with "Message retries not yet supported in python". That guard is now the only thing standing between the Python runtime and a working dead letter queue. The runtime honours `FunctionDetails.retryDetails`, but nothing can reach it through the cluster path, because the two gates line up exactly: - `FunctionConfigUtils.convert` only populates `retryDetails` when `maxMessageRetries != null && >= 0` -- the condition `doPythonChecks` rejects. So `--dead-letter-topic` on its own never produces a `retryDetails` message at all, and the runtime sees nothing to honour. - `--max-message-retries` with any value the runtime could use is refused before it gets that far. `validateNonJavaFunction` has one caller, the worker REST API (`FunctionsImpl`), so the refusal applies to cluster submission only; `LocalRunner` never calls it, which is why the runtime path is reachable under `localrun` today and nowhere else. ### Modifications Replace the blanket refusal in `doPythonChecks` with a narrow one on zero. Zero asks for no redelivery at all before the dead letter topic, and the Python client cannot express it: `ConsumerDeadLetterPolicy` requires a `maxRedeliverCount` of at least 1. Accepting it would create a function whose dead letter topic never receives anything -- the silently ineffective configuration this support exists to remove -- so it is rejected at creation, where the mistake is still cheap to fix, rather than warned about in an instance log nobody reads. A negative value leaves retries unset, as on the Java path. `doGolangChecks` keeps its guard: the Go runtime does not honour `retryDetails` yet, and a test now pins that so this change cannot be widened to Go by accident. Four tests: Python accepts retries with and without an explicit dead letter topic, rejects zero with the new message, and Go still refuses retries.
Every other method in python_instance.py is preceded by a blank line; get_dead_letter_policy, added earlier in this branch, ran on directly from the end of get_record_class.
…n-capable The @option descriptions in CmdFunctions carry a runtime marker that the docs sync parses into the "Support" column of the published pulsar-admin CLI reference, and that `functions create --help` prints verbatim. Both flags were marked #Java, which this branch makes untrue.
|
Cross-linking for reviewers: the documentation for this change is apache/pulsar-site#1214 ("Note runtime support for The |
freeznet
left a comment
There was a problem hiding this comment.
The positive retry mapping and default DLQ topic handling look sound, but the two guards below introduce behavior and ownership differences from the Java runtime. Please align these paths before merging.
| # The Java runtime accepts maxMessageRetries >= 0, but the Python client rejects a | ||
| # maxRedeliverCount below 1, so zero cannot be expressed here. Warn rather than fail the | ||
| # instance, and rather than dropping it silently - silent drops are the bug this fixes. | ||
| if max_message_retries < 1: |
There was a problem hiding this comment.
Java does not start successfully with this value: PulsarSource forwards 0 to ConsumerBuilder.deadLetterPolicy(), and ConsumerBuilderImpl rejects maxRedeliverCount <= 0. Since LocalRunner bypasses validateNonJavaFunction, returning None here makes Python localrun start with retries silently disabled while Java fails fast. Please let ConsumerDeadLetterPolicy reject zero (or move the validation to a path shared by cluster and localrun) instead of swallowing it.
There was a problem hiding this comment.
Agreed, and fixed in 47df72c. I verified the Java path rather than taking it on trust, and it is exactly as you describe:
PulsarSource.java:97guards ongetMaxMessageRetries() != null && >= 0, so0is forwarded rather than skipped;ConsumerBuilderImpl.java:530then doescheckArgument(deadLetterPolicy.getMaxRedeliverCount() > 0, "MaxRedeliverCount must be > 0.");validateNonJavaFunctionhas exactly one non-test caller,FunctionsImpl.java:829, soLocalRunnerdoes bypass it.
I took the first of your two options: the value is now passed straight through and ConsumerDeadLetterPolicy rejects it. Confirmed against the pinned client that it raises ValueError: max_redeliver_count must be greater than 0 for both 0 and negatives, so localrun now fails the instance the same way Java does instead of starting with retries quietly disabled.
The cluster path keeps its earlier, friendlier diagnostic — validateNonJavaFunction still rejects maxMessageRetries == 0 for Python before submission — so this only changes what happens when that check is bypassed.
There was a problem hiding this comment.
Closing the loop for the record: @lhotari independently re-checked this one rather than taking the reply on trust, and confirmed both halves — passing maxMessageRetries through so ConsumerDeadLetterPolicy rejects it matches PulsarSource.java:98-104 (>= 0 builds the policy, ConsumerBuilderImpl then rejects <= 0), and ConsumerDeadLetterPolicy.__init__ does raise ValueError: max_redeliver_count must be greater than 0 for both 0 and negatives.
So localrun now fails fast the same way Java does. Thanks for catching it.
| return None | ||
|
|
||
| # A dead letter policy only takes effect on Shared and KeyShared subscriptions. | ||
| if consumer_type not in (pulsar._pulsar.ConsumerType.Shared, pulsar._pulsar.ConsumerType.KeyShared): |
There was a problem hiding this comment.
Could we remove this runtime-level subscription-type gate and the consumer_type parameter? The Java runtime always forwards a configured DeadLetterPolicy; Shared/KeyShared support is a client concern. Encoding the current native-client limitation here makes Python diverge from Java and adds a second support matrix that can become stale. Passing the policy through is simpler; any validation or warning should live at the common config or client boundary.
There was a problem hiding this comment.
Agreed, removed in 47df72c along with the consumer_type parameter; the caller is now get_dead_letter_policy().
Your reasoning is the part I found persuasive: the Java runtime always forwards a configured DeadLetterPolicy, so gating here created a second support matrix in the runtime that would drift from the client as native-client support changes. Passing the policy through is both simpler and one less thing to keep in sync.
The three subscription-type test cases are replaced by a single one asserting the policy is not gated on subscription type, so the intent is pinned rather than just untested.
There was a problem hiding this comment.
@lhotari independently confirmed the removal is the right call, for the reason you gave: the Java runtime forwards a configured policy unconditionally, so gating in the runtime created a second support matrix that would drift from the client.
One correction to my reply above, since it was overstated at the time. I said the replacement test left the intent "pinned rather than just untested" — it did not. @lhotari re-applied the old gate to a copy of get_dead_letter_policy() and the whole suite stayed green, because every FunctionDetails the tests build leaves retainOrdering, retainKeyOrdering, processingGuarantees and source.subscriptionType at their proto defaults, so the derived consumer type was always Shared — the one value the old gate allowed.
That is now genuinely fixed, in c3b683f: the assertion lives in TestConsumerSubscribeArgs, where the consumer type is actually derived, and drives it to both Failover and KeyShared. Mutation-tested in both directions — a gate sparing Shared/KeyShared and a gate blocking KeyShared only — and in each case it is the only failing test in the suite.
Motivation: Review of apache#26400 found that the two guards in get_dead_letter_policy made the Python runtime behave differently from Java and put a support matrix in the runtime that belongs to the client. Modifications: - Pass maxMessageRetries through instead of returning None below 1. Java does not start with that value either: PulsarSource builds a policy for any maxMessageRetries >= 0 and ConsumerBuilderImpl.deadLetterPolicy then rejects "MaxRedeliverCount must be > 0". Because LocalRunner bypasses validateNonJavaFunction, the previous guard let localrun start with retries silently disabled while Java failed fast. ConsumerDeadLetterPolicy raises ValueError for 0 and for negatives, so both now fail the instance. - Drop the Shared/KeyShared gate and the consumer_type parameter. The Java runtime always forwards a configured DeadLetterPolicy; which subscription types can act on one is a client concern, and encoding the current native client limitation here would drift from the client over time. Verification: - Reworked TestDeadLetterPolicy: the zero and negative cases now assert the fail-fast, the three subscription-type cases are replaced by one asserting the policy is not gated on subscription type - run_python_instance_tests.sh equivalent passes: 10 tests, all green The cluster path keeps its earlier, friendlier diagnostic - validateNonJavaFunction still rejects maxMessageRetries == 0 for Python before submission, so this only changes what happens when that check is bypassed.
|
@freeznet both addressed in
I checked Both changes made the PR smaller, which is a good sign — thanks for pushing on the consistency argument. |
# Conflicts: # pulsar-functions/instance/src/main/python/python_instance.py # pulsar-functions/instance/src/test/python/test_python_instance.py
Merging master brought in apache#26421, which had independently added a test named testGoFunctionStillRejectsMessageRetries. Both copies landed in different regions of the file, so the merge was clean and the collision only surfaced as a compile error: method testGoFunctionStillRejectsMessageRetries() is already defined Keep upstream's copy, which sits with the other Go tests and builds its config from minimalGoFunctionConfig() rather than routing through a Python one, and carry the explanatory comment onto it. Also cover three retry edges that had no test: - a negative maxMessageRetries means "unset" on the Python path, as it does on the Java path, and convert() emits no retryDetails for it - a dead letter topic with maxMessageRetries unset is rejected by doCommonChecks; nothing asserted that message - doGolangChecks refuses every count >= 0, not only a positive one, unlike Python which refuses only 0 Verified locally under JDK 25: 48 tests, 0 failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DmEAdjzyB9hJthimd3TZf
|
@freeznet both points are addressed in
Since then I also pushed CI is green on Could you take another look when you have a moment? |
lhotari
left a comment
There was a problem hiding this comment.
The direction here is right and the two points @freeznet raised are genuinely fixed in 47df72c — I checked both rather than taking the replies on trust. Passing maxMessageRetries through so ConsumerDeadLetterPolicy rejects it matches PulsarSource.java:98-104 (>= 0 builds the policy, ConsumerBuilderImpl then rejects <= 0), and ConsumerDeadLetterPolicy.__init__ does raise ValueError: max_redeliver_count must be greater than 0 for both 0 and negatives. Removing the subscription-type gate is also the right call for the reason given: the Java runtime forwards a configured policy unconditionally.
What I do not think holds up is the claim, made in reply to the second thread, that the replacement test leaves the intent "pinned rather than just untested". I re-applied the exact gate that 5cc6207b061 introduced to a copy of get_dead_letter_policy() (deriving the consumer mode from function_details the way run() does) and all 33 tests in test_python_instance still passed. A positive control (get_dead_letter_policy() always returning None) fails 5 tests, so the harness works — the gate simply is not covered. The same is true of the or None on deadLetterTopic: dropping it leaves the suite green.
One thing worth writing down rather than coding around. dead_letter_policy is now passed to subscribe() on both consumer paths unconditionally, and that keyword only exists from pulsar-client-python 3.3.0, so this raises the Python runtime's effective client floor. The images we ship are already fine: docker/pulsar/Dockerfile:187 and docker/pulsar/Dockerfile.wolfi:111 install pulsar-client[all] at the version gradle/libs.versions.toml:22 pins (3.13.0), and run_python_instance_tests.sh:34 pins the same for CI. What is missing is that we never state the requirement anywhere — the process runtime launches the host's python3 (RuntimeUtils.java:420), so a self-managed worker runs whatever client the operator happens to have installed. A short note telling users to run a recent pulsar-client-python (>= 3.3.0) in pulsar-functions/instance/src/main/python/README.md, and in the pulsar-site page you already have open (apache/pulsar-site#1214), would close it.
The rest is small: a @SuppressWarnings("deprecation") that the insertion point silently moved off createFunctionConfig(), a duplicated import pulsar, and the fact that neither modified subscribe() call is exercised by any test.
On the broader question raised in the first PR comment (merge as-is vs. relax doPythonChecks here vs. hold): the PR as it stands has taken option 2, and I think that is the right choice — shipping the runtime support without opening the config surface would leave it unreachable dead code.
Motivation: Review feedback on apache#26400 raised six points: the runtime's minimum pulsar-client-python is now 3.3.0 and is undocumented, nothing exercises either subscribe() call site, two tests do not pin what they claim to, and two smaller slips. Modifications: - Document the client requirement. Client.subscribe() grew its dead_letter_policy parameter in 3.3.0, and the runtime passes the keyword on every subscription, so 3.3.0 is the floor. Every other argument the runtime passes is already present in 3.2.0, so this is the only thing setting it. README.md now states the requirement, notes that it is only visible on a self-managed worker, and shows where to pin it for a zip-packaged function. - Extract setup_consumers() from run(), giving the consumer path the same testable seam setup_producer() already has, and cover both subscribe() call sites - the topicsToSerDeClassName path, the inputSpecs path and its regex variant - with assertions on the kwargs a mocked client receives. - Drop the "or None" on deadLetterTopic. It could not change behaviour: ConsumerDeadLetterPolicy skips the builder call only for None, and DeadLetterPolicyBuilder.deadLetterTopic("") leaves getDeadLetterTopic() == "", which is what an unset policy also reports. The test that claimed to pin it now pins the runtime's actual half of the contract, and its comment no longer misdescribes the client. - Make test_policy_is_not_gated_on_subscription_type exercise the subscription types the earlier gate suppressed. It used a default instance, which selects Shared - the one case the gate allowed - so it passed with the gate fully restored. - Remove a duplicate "import pulsar" and move @SuppressWarnings("deprecation") back onto createFunctionConfig(), the method it was suppressing for.
|
@lhotari all six points are addressed in
I mutation-tested each rewritten test rather than just checking it passes; the specific mutations and Verification: One question for you in the client-floor thread: the README line is the first declared minimum The matching note for apache/pulsar-site#1214 is next — that PR has no reviewers assigned, so if you @freeznet when you have a chance — your two points were fixed back in |
lhotari
left a comment
There was a problem hiding this comment.
All six points from the previous round are addressed in 94b4fcd, and I checked each one rather than taking the replies on trust.
What I verified independently:
- The 3.3.0 floor is right.
pulsar/__init__.pyat tagv3.2.0contains neitherdead_letter_policynorConsumerDeadLetterPolicy;v3.3.0adds both and guards withif dead_letter_policy:. I also diffed thesubscribe()andcreate_producer()signatures betweenv3.2.0andv3.13.0against every keyword this runtime actually passes —dead_letter_policyis the only one 3.2.0 lacks, so the README's "this is the only thing setting the floor" holds. - The
or Noneremoval is safe.ConsumerDeadLetterPolicy.__init__forwards the topic only when it is notNone,DeadLetterPolicyBuilder::deadLetterTopicis a plain assignment, andbuild()validates onlymaxRedeliverCount— so""and unset really are indistinguishable, and the rewritten docstring now says so. - The
setup_consumers()extraction is behaviour-preserving. Diffing the moved block against the previous head gives zero non-blank differences, and none of the locals it takes with it (mode,position,properties,subscription_name,nack_args) are read later inrun(). - The new tests have teeth. I ran
test_python_instanceagainst the CI-pinned client (37 tests, green) and reproduced the mutations described in the threads. Restoring the old gate insideget_dead_letter_policy()fails exactly 3 of the 4 subTests, withretainKeyOrderingcorrectly still passing. Dropping the policy from thetopicsToSerDeClassNamecall site fails only that path's test; dropping it from theinputSpecscall site fails only theinputSpecsand regex tests. The two call sites are independently covered.
That leaves one residual and two small notes, all inline and none of them blocking: a subscription-type gate reintroduced in setup_consumers() — which is where it would go now — is still invisible to the suite, and closing that is one parameter on a helper you already have.
Thanks for the mutation testing in the replies; it made this round much faster to check.
|
Thanks — this was a genuinely easy round to check, precisely because you named the mutation for each claim instead of just saying the tests pass. I reproduced them rather than taking them on trust, and they hold: the restored gate fails 3 of the 4 subTests with On the minimum version: keep 3.3.0. It is the technical floor, it is checkable against the client sources, and that is the kind of statement a bug fix should be making. A declared supported minimum is a project commitment rather than a fact about this change; if we want one it belongs on dev@, not in a runtime README. I have said the same in the thread. Three inline comments this round, all small: one residual test gap where a subscription-type gate could come back in |
Pin the subscription-type intent where it can actually be seen, and correct the README's account of how the client version reaches CI. - test_python_instance: TestDeadLetterPolicy's tests cannot see a gate in setup_consumers(), because get_dead_letter_policy() no longer receives a consumer type. Every FunctionDetails TestConsumerSubscribeArgs builds also leaves retainOrdering, retainKeyOrdering, processingGuarantees and source.subscriptionType at their proto defaults, so mode is always Shared - the one value such a gate would let through. Give _subscribe_kwargs() a configure= hook and add test_policy_reaches_subscribe_for_a_non_shared_ consumer_type, driving mode to both Failover and KeyShared. Mutation-tested. A gate sparing Shared and KeyShared fails three of the four subTests and passes retainKeyOrdering; a gate blocking only KeyShared fails that subTest alone. In both cases nothing else in the suite fails, and a single Failover case would have missed the second shape. - README: the version catalog drives the Docker images, but not CI. The images derive their version through docker/pulsar/build.gradle.kts, which resolves the catalog and passes it as PULSAR_CLIENT_PYTHON_VERSION; CI runs run_python_instance_tests.sh, which carries its own literal default and is invoked with no override. Say so rather than implying one mechanism feeds both. - python_instance: add the missing blank line before setup_consumers().
lhotari
left a comment
There was a problem hiding this comment.
All six open points are closed in c3b683f5, and I checked each at that commit rather than reading the replies.
The one that mattered was the invisible subscription-type gate. test_policy_reaches_subscribe_for_a_non_shared_consumer_type now lives in TestConsumerSubscribeArgs, where setup_consumers() actually derives the consumer type, and it drives that type over all four selectors that decide it. I re-ran the module against pulsar-client[all]==3.13.0, grpcio==1.78.0, protobuf==6.33.6: 38 tests green at head; a gate sparing Shared/KeyShared fails 3 of the 4 subTests; a gate blocking KeyShared only fails the fourth. In both runs it is the only failing test in the module. Covering both non-default modes rather than the single case I suggested is the better call — a one-case version would have sailed past the second gate shape.
The other two were a README sentence and a blank line, both correct at head. The README now matches the mechanism it describes: the Docker images derive their version from the catalog, CI pins the same number separately in run_python_instance_tests.sh:34, and the two agree by maintenance. I re-checked each half.
Nothing open from me. The review decision on the PR is still in the "changes requested" state from the 26 August review, which an approval from me would not clear on its own — the author has since replied on both of those threads. Perhaps the "changes requested" review can be dismissed?
Fixes #26397
Motivation
FunctionConfigacceptsmaxMessageRetriesanddeadLetterTopic, both are carried into the instance asFunctionDetails.retryDetails(Function.protoL58-61, L91), and the Java runtime applies them. The Python runtime ignored them entirely —grep -i "dead_letter\|retryDetails" python_instance.pyreturned nothing on master.The failure mode is silent, which is the damaging part. This is accepted without warning:
functions getreports the configuration back faithfully, and at runtime nothing is ever routed to the DLQ.Teaching the runtime to honour
retryDetailsis necessary but not sufficient. Two gates inFunctionConfigUtilsline up exactly and, between them, keep a Python function from ever reaching aretryDetailsmessage on the cluster path:doPythonChecksrefuses anymaxMessageRetries >= 0outright ("Message retries not yet supported in python").convertonly populatesretryDetailswhenmaxMessageRetries != null && >= 0(L304) — the very conditiondoPythonChecksrejects. So--dead-letter-topicon its own never produces aretryDetailsmessage at all, and the runtime has nothing to honour.validateNonJavaFunctionhas a single caller, the worker REST API (FunctionsImpl), so this refusal applies to cluster submission only.LocalRunnernever calls it, which is why the runtime path is reachable underlocalruntoday and nowhere else. Fixing only the runtime would ship a fix that no user could invoke.Modifications
Runtime (
python_instance.py). Addget_dead_letter_policy()toPythonInstanceand pass its result to all threesubscribe()call sites inrun()— thetopicsToSerDeClassNameloop and both branches of theinputSpecsloop.The rules follow the Java runtime:
HasField("retryDetails"), matchingJavaInstanceRunnable'shasRetryDetails()check.HasFieldis already the idiom in this file (used forreceiverQueueSize).PulsarSource, so the client derives its<topic>-<subscription>-DLQdefault rather than receiving an empty name.Validation (
FunctionConfigUtils.doPythonChecks). Replace the blanket refusal with a narrow one on zero.Zero asks for no redelivery at all before the dead letter topic, and the Python client cannot express it:
ConsumerDeadLetterPolicyrequires amaxRedeliverCountof at least 1 (pulsar/__init__.pyL761-762). Accepting it would create a function whose dead letter topic never receives anything — precisely the silently ineffective configuration this change exists to remove — so it is rejected at creation, where the mistake is still cheap to fix, rather than warned about in an instance log nobody reads. A negative value leaves retries unset, as on the Java path.CLI metadata (
CmdFunctions). The@Optiondescriptions carry a runtime marker that the docs sync parses into the Support column of the published pulsar-admin CLI reference, and thatpulsar-admin functions create --helpprints verbatim.--max-message-retriesand--dead-letter-topicwere both marked#Java; they are now#Java, Python.doGolangCheckskeeps its guard: the Go runtime does not honourretryDetailsyet, and a test now pins that so this cannot be widened to Go by accident.The pre-existing cross-cutting checks in
doCommonChecksstill apply to Python and cover the rest of the space: a dead letter topic with retries unset is refused ("Dead Letter Topic specified, however max retries is set to infinity"), as ismaxMessageRetriescombined withEFFECTIVELY_ONCE.Two runtime cases still cannot mirror Java exactly, and both warn rather than failing the instance or silently doing nothing:
maxMessageRetries == 0. Now rejected at creation on the cluster path, but still reachable underlocalrun, which skipsvalidateNonJavaFunction. Attaching no policy is the only available behaviour there; the warning names the dead letter topic that will not receive messages. Raising would take down a function the Java runtime would have started.SharedandKeyShared.retainOrderingandEFFECTIVELY_ONCEboth selectFailover, where the policy would be silently ineffective — the same class of bug as this issue — so that combination warns as well.I did not change the Java-side
>= 0behaviour or the client's>= 1constraint; reconciling them is a larger discussion than this fix.Verifying this change
This change added tests and can be verified as follows:
test_python_instance.py(TestDeadLetterPolicy), covering: noretryDetails→ no policy; a policy built fromretryDetails; an empty dead letter topic deferring to the client default;maxMessageRetriesof 0 and of -1 attaching no policy rather than raising;KeySharedreceiving a policy;FailoverandExclusivenot.FunctionConfigUtilsTest.java: Python accepts retries with and without an explicit dead letter topic, rejects zero with the new message, and Go still refuses retries.pulsar-functions/instance/src/scripts/run_python_instance_tests.sh.FunctionConfigUtilsTest: 41/41 pass.get_dead_letter_policy()returnNoneunconditionally fails 3 of them.Does this pull request potentially affect one of the following parts:
A function that does not set
retryDetailsis unaffected:get_dead_letter_policy()returnsNoneanddead_letter_policy=Noneis what the client already defaults to.The validation change is relaxing only: configurations that were accepted before are still accepted. The one newly-rejected input,
maxMessageRetries == 0on a Python function, was previously rejected too, by the broader guard this replaces — no configuration that used to be creatable stops being creatable.Documentation
doc-requireddocs/functions-cli.mdin apache/pulsar-site documentsmaxMessageRetriesanddeadLetterTopicwith no runtime qualification, so the table needs a note that Java and Python honour them (Python requiring at least1) while Go does not. That table is hand-maintained, so it needs its own PR there; the#Java, Pythonmarker above covers the generated CLI reference from this side.For the record, the note added when #6084 was closed in 2020 -- "This parameter is not supported in Python Functions" -- is no longer present in the current docs. It survives only in
versioned_docs/version-2.3.0throughversion-2.10.x, where it was accurate for those releases and should stay; it was dropped from the live docs in the 2.11functions-cli.mdrewrite. So there is no incorrect statement to retract, only a missing caveat to add.I have not opened the apache/pulsar-site PR yet -- flagging it so it is not lost, and happy to open it once the behaviour here is settled in review.