Skip to content

Conversation

@YassinNouh21
Copy link
Contributor

Change Summary

This PR fixes an issue with infinite recursion when comparing BaseModel instances that contain recursive references (instances that reference themselves either directly or indirectly). It implements a solution using ContextVar to track object pairs being compared during equality checks, which prevents the comparison from getting stuck in an infinite loop.

The changes include:

  • Adding a module-level _eq_recursion_tracker ContextVar to track objects being compared
  • Implementing recursive comparison detection in the __eq__ method
  • Adding comprehensive test cases for both direct and complex recursive models

Related issue number

fix #10630

Checklist

  • The pull request title is a good summary of the changes - it will be used in the changelog
  • Unit tests for the changes exist
  • Tests pass on CI
  • Documentation reflects the changes where applicable
  • My PR is ready to review, please review

@github-actions github-actions bot added the relnotes-fix Used for bugfixes. label Mar 19, 2025
@codspeed-hq
Copy link

codspeed-hq bot commented Mar 19, 2025

CodSpeed Performance Report

Merging #11581 will not alter performance

Comparing YassinNouh21:fix/prevent-infinite-recursion-in (901b43c) with main (a2846da)

Summary

✅ 46 untouched benchmarks

@github-actions
Copy link
Contributor

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  pydantic
  main.py 1180-1188
Project Total  

This report was generated by python-coverage-comment-action

@Viicos
Copy link
Member

Viicos commented Mar 19, 2025

Thanks for the contribution, I still need to think on this one. As mentioned in the CPython issue, we might want to consider the recursion error as expected.

I also won't be surprised if the approach here doesn't work as expected for more complex recursive cases.

@YassinNouh21
Copy link
Contributor Author

@Viicos Thanks for reviewing the PR. While Python's core behavior treats the RecursionError as expected for circular references, I believe Pydantic should offer more robust handling as a high-level library:

  1. The solution uses ContextVar to efficiently track object pairs during comparison, breaking recursion cycles.
  2. It returns True when cycles are detected, consistent with Golang's approach

I've tested this with various recursive scenarios (self-references, mutual references, and deep cycles) and found it works reliably.

@MarkusSintonen
Copy link
Contributor

MarkusSintonen commented Mar 23, 2025

Can you add CodSpeed benchmarks in a separate PR (eq checks for different nestings)? To then see in this PR that this wont degrade eq checking perf. This is now adding a bunch of set allocs and lookups to every eq operation. It might not be worth fixing the issue if the perf takes a hit for everyone. Considering how corner case it is which also exists in Python itself.

@Viicos
Copy link
Member

Viicos commented Mar 31, 2025

Discussed today with the team, indeed would be great to assess the potential performance hit here. The linked issue also doesn't have much demand, and it feels like using context vars for this is a bit too much.

@YassinNouh21
Copy link
Contributor Author

@Viicos I understand the concern about performance. Can I add CodSpeed benchmarks to measure the impact—would that help in evaluating this further? Also, would making this behavior configurable be a good compromise? Let me know your thoughts!

majiayu000 added a commit to majiayu000/pydantic that referenced this pull request Dec 14, 2025
Handle circular self-references in BaseModel equality comparison by
catching RecursionError and returning True. This has zero performance
overhead for normal (non-circular) comparisons since the try-except
only triggers on actual recursion.

When comparing models with circular references:
- Same structure circular refs → True
- Different types → False (detected before recursion)
- Different values in non-circular fields → False (detected before recursion)

This approach addresses the performance concerns raised in PR pydantic#11581
which used ContextVar tracking for every comparison.

Fixes pydantic#10630

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

relnotes-fix Used for bugfixes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(🐞) RecursionError in == when object has a reference to itself

3 participants