Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(grpc): Return proper metadata object instead of list in… #3205

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

fdellekart
Copy link
Contributor

Fix wrong metadata type in async gRPC interceptor

Fixes #2509

The metadata of gRPC calls was transformed to a list in the client interceptor.
The list was compatible with gRPC itself as it is the format for metadata in the sync version, however it lead to downstream interceptors crashing as they expected a grpc.aio.Metadata object.

This PR now uses the expected type.

@fdellekart fdellekart changed the title fix(grpc): Return propagate proper metadata object instead of list in… fix(grpc): Return proper metadata object instead of list in… Jun 26, 2024
Copy link

codecov bot commented Jun 26, 2024

Codecov Report

Attention: Patch coverage is 60.00000% with 2 lines in your changes missing coverage. Please review.

Project coverage is 79.39%. Comparing base (531f8f7) to head (0d66f35).
Report is 51 commits behind head on master.

Files Patch % Lines
sentry_sdk/integrations/grpc/aio/client.py 60.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3205      +/-   ##
==========================================
- Coverage   79.47%   79.39%   -0.09%     
==========================================
  Files         133      133              
  Lines       14290    14292       +2     
  Branches     3003     3005       +2     
==========================================
- Hits        11357    11347      -10     
- Misses       2089     2103      +14     
+ Partials      844      842       -2     
Files Coverage Δ
sentry_sdk/integrations/grpc/aio/client.py 94.59% <60.00%> (-5.41%) ⬇️

... and 4 files with indirect coverage changes

Copy link
Member

@szokeasaurusrex szokeasaurusrex left a comment

Choose a reason for hiding this comment

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

Please see inline comment

metadata = (
list(client_call_details.metadata) if client_call_details.metadata else []
)
client_call_details.metadata = client_call_details.metadata or Metadata()
Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately, setting this attribute is not possible; see the following from our test logs:

tests/integrations/grpc/test_grpc_aio.py:76: in test_grpc_server_starts_transaction
    await stub.TestServe(gRPCTestMessage(text="test"))
.tox/py3.8-grpc-latest/lib/python3.8/site-packages/grpc/aio/_interceptor.py:470: in __await__
    call = yield from self._interceptors_task.__await__()
.tox/py3.8-grpc-latest/lib/python3.8/site-packages/grpc/aio/_interceptor.py:697: in _invoke
    return await _run_interceptor(
.tox/py3.8-grpc-latest/lib/python3.8/site-packages/grpc/aio/_interceptor.py:671: in _run_interceptor
    call_or_response = await interceptors[0].intercept_unary_unary(
sentry_sdk/integrations/grpc/aio/client.py:47: in intercept_unary_unary
    client_call_details = self._update_client_call_details_metadata_from_scope(
sentry_sdk/integrations/grpc/aio/client.py:24: in _update_client_call_details_metadata_from_scope
    client_call_details.metadata = client_call_details.metadata or Metadata()
E   AttributeError: can't set attribute

Perhaps, ClientCallDetails provides some other way to set this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for pointing this out. I remembered there was a problem with setting some attribute and thought it was in add itself.

ClienCallDetails is a subclass of collections.namedtuple, which is immutable so it needs to be reconstructed to change a field value. This can be done with ._replace. Was hesitating to use it before as it is prefixed with _, however, according to the docs, this is just to avoid name clashes with fields.

Pushed a fixup, please have a look.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tests failing due to something else, will have a look.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The metadata was not guaranteed to be an Optional[Metadata] object, as it is typed, up until grpcio 1.65.0 and a tuple was mistakenly passed on through the interceptors. This was fixed on the gRPC side.

Do you want to keep support for versions before that?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, we should keep support for anything we currently support, as this would be a breaking change that requires a major version bump if we drop support for any gRPC versions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added the check as it is part of grpcio itself for newer versions 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the relevant change in the grpc package

Up until version 1.65.0 of grpcio, the metadata was not guaranteed to
arrive as the type specified in annotations but could be a tuple.
To support versions before that we check and transform it here.
client_call_details = client_call_details._replace(metadata=Metadata())
elif not isinstance(client_call_details.metadata, Metadata):
client_call_details = client_call_details._replace(
metadata=Metadata.from_tuple(client_call_details.metadata)
Copy link
Member

Choose a reason for hiding this comment

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

What happens here if client_call_details.metadata is neither a tuple nor a Metadata object? Does Metadata.from_tuple handle the situation gracefully, or could we have an error in this situation?

If an error is possible, we need to make sure we handle it gracefully, even if this is a situation that is unlikely to occur. We need to be very careful with these kinds of edge cases in the SDK, since we never want to crash a user's application.

Copy link
Contributor Author

@fdellekart fdellekart Aug 12, 2024

Choose a reason for hiding this comment

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

Sorry for the late response, I've been distracted and forgot about this.

Looking at its implementation, the from_tuple method would work with any Iterable[Tuple[str, str]], although its name would suggest differently.

However, this could change any time with a new version of grpcio, so if we want to be save we could add an additional check if the input actually is a tuple. The same thing is done in the fix inside grpcio, so I think this would make sense to have here also to not break with future versions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Didn't yet push anything bc. I wanted to wait for your feedback, do you agree or have any further suggestions?

@getsantry
Copy link

getsantry bot commented Aug 9, 2024

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you remove the label Waiting for: Community, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

@getsantry getsantry bot added the Stale label Aug 9, 2024
@getsantry
Copy link

getsantry bot commented Sep 3, 2024

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you remove the label Waiting for: Community, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

3 similar comments
@getsantry
Copy link

getsantry bot commented Sep 30, 2024

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you remove the label Waiting for: Community, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

@getsantry
Copy link

getsantry bot commented Oct 22, 2024

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you remove the label Waiting for: Community, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

@getsantry
Copy link

getsantry bot commented Nov 12, 2024

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you remove the label Waiting for: Community, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Python grpc.aio metadata is transformed to list
3 participants