Skip to content

fix: add recursion guard to StreamSlicerMeta.__instancecheck__#983

Draft
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1775527410-fix-recursion-error-stream-slicer-meta
Draft

fix: add recursion guard to StreamSlicerMeta.__instancecheck__#983
devin-ai-integration[bot] wants to merge 1 commit intomainfrom
devin/1775527410-fix-recursion-error-stream-slicer-meta

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

Summary

StreamSlicerMeta.__instancecheck__ could cause a RecursionError when isinstance() was called on objects that have a wrapped_slicer attribute combined with a custom __getattr__ that delegates to the wrapped object. The original implementation used hasattr(instance, "wrapped_slicer"), which invokes __getattr__—and if that __getattr__ triggers further isinstance() checks (directly or indirectly), infinite recursion results.

Two changes fix this:

  1. Recursion guard: A thread-local set tracks in-progress id(instance) values. On re-entry for the same object, we fall back to super().__instancecheck__().
  2. object.__getattribute__ instead of hasattr: Bypasses any custom __getattr__ on the instance, so only objects with a real wrapped_slicer attribute (not a delegated one) trigger the unwrap path.

Resolves https://github.com/airbytehq/oncall/issues/11900:

Review & Testing Checklist for Human

  • Verify object.__getattribute__ semantics: The old code used hasattr, which also matched attributes returned by __getattr__. The new code only matches attributes in the instance __dict__ or class hierarchy. Confirm this is the correct behavior—StreamSlicerTestReadDecorator stores wrapped_slicer as a dataclass field (in __dict__), so it should still be found.
  • Thread safety of _checking: _checking is a single threading.local instance on the metaclass, shared by all classes using StreamSlicerMeta. Verify that concurrent isinstance() calls from different threads cannot corrupt each other's in_progress sets.
  • Run the full test suite (not just the decorator tests) to confirm no regressions in isinstance behavior across ConcurrentDeclarativeSource and other callers that rely on this metaclass.

Notes

  • The three new tests cover: custom __getattr__ with wrapped_slicer (primary bug reproduction), nested/double-wrapped decorators, and a self-referencing cycle (pathological guard test).
  • All 8 existing + new tests pass locally; ruff and mypy clean.

Link to Devin session: https://app.devin.ai/sessions/36ab5b8f87bf4f0991dc9a05c10781fc

The metaclass __instancecheck__ method could recurse infinitely when
isinstance() was called on objects with wrapped_slicer attributes that
chain or cycle back. This adds a thread-safe recursion guard using a
set of in-progress object IDs and uses object.__getattribute__ to
bypass custom __getattr__ methods that could trigger further isinstance
calls.

Co-Authored-By: bot_apk <apk@cognition.ai>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 7, 2026

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

Testing This CDK Version

You can test this version of the CDK using the following:

# Run the CLI from this branch:
uvx 'git+https://github.com/airbytehq/airbyte-python-cdk.git@devin/1775527410-fix-recursion-error-stream-slicer-meta#egg=airbyte-python-cdk[dev]' --help

# Update a connector to use the CDK from this branch ref:
cd airbyte-integrations/connectors/source-example
poe use-cdk-branch devin/1775527410-fix-recursion-error-stream-slicer-meta

PR Slash Commands

Airbyte Maintainers can execute the following slash commands on your PR:

  • /autofix - Fixes most formatting and linting issues
  • /poetry-lock - Updates poetry.lock file
  • /test - Runs connector tests with the updated CDK
  • /prerelease - Triggers a prerelease publish with default arguments
  • /poe build - Regenerate git-committed build artifacts, such as the pydantic models which are generated from the manifest JSON schema in YAML.
  • /poe <command> - Runs any poe command in the CDK environment
📚 Show Repo Guidance

Helpful Resources

📝 Edit this welcome message.

_checking: threading.local = threading.local()

return super().__instancecheck__(instance)
def __instancecheck__(cls, instance: Any) -> bool:
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a false positive. __instancecheck__ is a method on a metaclass (ABCMeta subclass), so its first parameter is the class being checked against, not an instance — cls is the correct name here. Using self would be misleading. The original code (introduced in #567) also used cls.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 7, 2026

PyTest Results (Fast)

4 015 tests  +3   4 004 ✅ +3   7m 40s ⏱️ -3s
    1 suites ±0      11 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit 4a65149. ± Comparison against base commit 4aaafcf.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 7, 2026

PyTest Results (Full)

4 018 tests  +3   4 006 ✅ +3   11m 16s ⏱️ +17s
    1 suites ±0      12 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit 4a65149. ± Comparison against base commit 4aaafcf.

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.

0 participants