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 usage of a single object instances for multiple concurrent requests #6746

Merged
merged 5 commits into from
Aug 31, 2023

Conversation

dfangl
Copy link
Contributor

@dfangl dfangl commented Aug 31, 2023

Fixes #6745

Motivation

Currently, for the following services:

amp
apigateway
apigatewayv2
appconfig
appsync
awslambda
cloudfront
cognitoidp
databrew
ebs
elastictranscoder
glacier
greengrass
guardduty
instance_metadata
iot
managedblockchain
mq
pinpoint
quicksight
route53
s3
s3control
eventbridgescheduler

we use a single instance of the respective ...Response class to handle all the requests.

In LocalStack, this leads to issues with parallel requests influencing each others state, most notably of region and account.

There are a couple of different ways to fix this, we chose one that was minimally intrusive to the existing logic, but ensured a separate instance of the Response class for every request.

Unaffected services use the dispatch method. There are some services which use the classmethod dispatch on an instance, but since that does not make a difference (even though it can be confusing), we did not change it with this PR.

Changes

  • Create method_dispatch class method in the BaseResponse class, which will take an unbound function as input, and execute the corresponding method on a new instance on the class. This ensures proper typing and having the type checker catch potential errors, while being able to keep the current pattern of dispatching.
  • Refactor affected services to use this new function instead of calling a method on a singleton instance of the Response class.
  • Some minor typing changes to avoid typing errors

Comment on lines +246 to +262
@classmethod
def method_dispatch( # type: ignore[misc]
cls, to_call: Callable[[ResponseShape, Any, str, Any], TYPE_RESPONSE]
) -> Callable[[Any, str, Any], TYPE_RESPONSE]:
"""
Takes a given unbound function (part of a Response class) and executes it for a new instance of this
response class.
Can be used wherever we want to specify different methods for dispatching in urls.py
:param to_call: Unbound method residing in this Response class
:return: A wrapper executing the given method on a new instance of this class
"""

@functools.wraps(to_call) # type: ignore
def _inner(request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE:
return getattr(cls(), to_call.__name__)(request, full_url, headers)

return _inner
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Main logic. We might want to rethink the functools.wraps here, but it is necessary for tests like test_core.test_server.test_domain_dispatched_with_service, although it still needed a change, due to the different function name.

"{0}/workspaces/(?P<workspace_id>[^/]+)/rulegroupsnamespaces/(?P<name>[^/]+)$": PrometheusServiceResponse.dispatch,
"{0}/tags/(?P<resource_arn>[^/]+)$": PrometheusServiceResponse.dispatch,
"{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)$": PrometheusServiceResponse.method_dispatch(
PrometheusServiceResponse.tags # type: ignore
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 method returns a string, which is why the type ignore has to be here. There are some which do not match the type we now check, but most are fine.

@@ -43,4 +43,4 @@ def test_domain_dispatched_with_service():
dispatcher = DomainDispatcherApplication(create_backend_app, service="s3")
backend_app = dispatcher.get_application({"HTTP_HOST": "s3.us-east1.amazonaws.com"})
keys = set(backend_app.view_functions.keys())
assert "S3Response.key_response" in keys
assert "moto.s3.responses.key_response" in keys
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 needed to be change since it is now a function, and not a method anymore. This test also necessitates the functools.wrap in the BaseResponse method_dispatch method.

@@ -62,6 +62,7 @@ def _vault_response_delete(self, vault_name: str, headers: Any) -> TYPE_RESPONSE
def vault_archive_response(
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers, use_raw_body=True)
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 was missing here, I assume it never occurred, since there are other calls using glacier and setting up the class before. Now it is a new one, which leads to this new line.

@codecov
Copy link

codecov bot commented Aug 31, 2023

Codecov Report

Merging #6746 (4bc4e85) into master (2ddcb8e) will decrease coverage by 0.01%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##           master    #6746      +/-   ##
==========================================
- Coverage   96.20%   96.20%   -0.01%     
==========================================
  Files         808      808              
  Lines       79576    79565      -11     
==========================================
- Hits        76557    76546      -11     
  Misses       3019     3019              
Flag Coverage Δ
servertests 36.96% <72.72%> (-0.01%) ⬇️
unittests 96.15% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Changed Coverage Δ
moto/amp/urls.py 100.00% <ø> (ø)
moto/apigateway/urls.py 100.00% <ø> (ø)
moto/apigatewayv2/urls.py 100.00% <ø> (ø)
moto/appconfig/urls.py 100.00% <ø> (ø)
moto/appsync/urls.py 100.00% <ø> (ø)
moto/awslambda/urls.py 100.00% <ø> (ø)
moto/cloudfront/urls.py 100.00% <ø> (ø)
moto/cognitoidp/urls.py 100.00% <ø> (ø)
moto/databrew/urls.py 100.00% <ø> (ø)
moto/ebs/urls.py 100.00% <ø> (ø)
... and 18 more

Copy link
Collaborator

@bblommers bblommers left a comment

Choose a reason for hiding this comment

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

Makes sense to me - thank you for the PR @dfangl!

@bblommers bblommers added this to the 4.2.1 milestone Aug 31, 2023
@bblommers bblommers merged commit de44a85 into getmoto:master Aug 31, 2023
@github-actions
Copy link
Contributor

This is now part of moto >= 4.2.1.dev29

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.

Moto uses a single instance of the ...Response class for all requests for some services
2 participants