Skip to content

fix(sentinel): SentinelFeignClientProperties#copy() should fail fast on Jackson errors#4329

Open
daguimu wants to merge 1 commit into
alibaba:2025.1.xfrom
daguimu:fix/sentinel-feign-properties-copy-fail-fast
Open

fix(sentinel): SentinelFeignClientProperties#copy() should fail fast on Jackson errors#4329
daguimu wants to merge 1 commit into
alibaba:2025.1.xfrom
daguimu:fix/sentinel-feign-properties-copy-fail-fast

Conversation

@daguimu

@daguimu daguimu commented May 14, 2026

Copy link
Copy Markdown
Contributor

Describe what this PR does / why we need it

SentinelFeignClientProperties#copy() is used by CircuitBreakerRuleChangeListener to take a deep snapshot of the live properties (afterSingletonsInstantiated() line 105 and updateBackup() line 190) so that the listener can detect whether circuit-breaker rules have changed when a refresh event arrives.

The current implementation does a Jackson-based serialize/deserialize round-trip but swallows every exception with catch (Exception ignored) { } and then returns a brand-new, default-valued SentinelFeignClientProperties() — i.e. on any failure the backup ends up containing none of the user's configured rules. Because the listener subsequently compares the live properties against this empty backup (Objects.equals(properties, propertiesBackup) returns false), every previously configured rule is treated as "removed" and an erroneous full-rule refresh is triggered.

In addition, since the module has already migrated to Jackson 3 (tools.jackson.databind.ObjectMapper), JacksonException is unchecked (extends RuntimeException). The original try / catch (Exception) was already a Jackson-2-era artefact and no longer serves any compile-time purpose.

Does this pull request fix one issue?

NONE

Describe how you did it

Replaced the silent-swallow + new-instance fallback with throw new IllegalStateException("Failed to deep-copy SentinelFeignClientProperties via Jackson", e); so that the original cause is preserved and surfaced fail-fast at the call site, instead of corrupting the diff baseline used by the rule-change listener.

Describe how to verify it

Added a new unit test SentinelFeignClientPropertiesTest with three cases:

  • copyReturnsEqualButDistinctInstance — confirms copy() returns a value .equals() to the original but not the same reference.
  • modifyingCopyDoesNotAffectOriginal — confirms the copy is a true deep copy (mutating the returned Map does not mutate the source).
  • copyFailsFastWhenJacksonCannotSerialize — uses an anonymous subclass whose getRules() throws to force Jackson to fail; asserts the call now propagates an IllegalStateException whose cause is the underlying failure (this test fails against the pre-fix code, confirming the bug).

Run:

mvn -pl spring-cloud-alibaba-starters/spring-cloud-circuitbreaker-sentinel -am test

Full module suite (18 tests including the integration tests) passes locally.

Special notes for reviews

  • ABI-compatible: IllegalStateException is unchecked, so the copy() signature is unchanged.
  • copy() is called in two places. At startup (afterSingletonsInstantiated) a Jackson failure now becomes a startup failure with a meaningful cause, which is strictly better than silently shipping an empty backup. At refresh time (updateBackup invoked from onApplicationEvent), Spring's SimpleApplicationEventMulticaster logs listener exceptions and continues to other listeners, so the worst case is "this refresh cycle is skipped" rather than "rules are silently corrupted".
  • No drive-by changes elsewhere; the diff is +3 / -2 in the production class plus one new test file.

… Jackson errors

The copy() method serializes/deserializes via Jackson to produce a deep
backup used by CircuitBreakerRuleChangeListener for change detection in
afterSingletonsInstantiated() and updateBackup(). On any serialization
failure the previous catch (Exception ignored) silently returned a
brand-new SentinelFeignClientProperties() with default values, dropping
every user-configured rule. The listener then compared the live
properties against that empty backup and treated every existing rule
as "removed", triggering an erroneous full-rule refresh.

Replace the silent fallback with throw new IllegalStateException(...)
that wraps the original cause. Since Jackson 3 (tools.jackson) throws
unchecked JacksonException, the try/catch was already legacy from the
Jackson 2 (com.fasterxml.jackson) era; fail-fast surfaces the real
issue at startup or refresh time instead of corrupting the diff baseline.

Adds three unit tests covering: equal-but-distinct deep copy, copy
independence from the original, and IllegalStateException propagation
when Jackson serialization fails.
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.

1 participant