Skip to content

Commit 33e7702

Browse files
author
Joel Tetrault
committed
changes to make app.current_request thread safe when running chalice locally
- fixes race conditions that can occur when chalice is being run locally and it handling multiple concurrent requests
1 parent e2eac21 commit 33e7702

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

chalice/local.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,16 @@
1616
from six.moves.BaseHTTPServer import HTTPServer
1717
from six.moves.BaseHTTPServer import BaseHTTPRequestHandler
1818
from six.moves.socketserver import ThreadingMixIn
19-
from typing import List, Any, Dict, Tuple, Callable, Optional, Union # noqa
19+
from typing import (
20+
List,
21+
Any,
22+
Dict,
23+
Tuple,
24+
Callable,
25+
Optional,
26+
Union,
27+
cast,
28+
) # noqa
2029

2130
from chalice.app import Chalice # noqa
2231
from chalice.app import CORSConfig # noqa
@@ -47,7 +56,9 @@ def time(self):
4756

4857
def create_local_server(app_obj, config, host, port):
4958
# type: (Chalice, Config, str, int) -> LocalDevServer
50-
return LocalDevServer(app_obj, config, host, port)
59+
local_app_obj = LocalChalice(app_obj)
60+
casted_local_app_obj = cast(Chalice, local_app_obj)
61+
return LocalDevServer(casted_local_app_obj, config, host, port)
5162

5263

5364
class LocalARNBuilder(object):
@@ -661,3 +672,32 @@ def shutdown(self):
661672
# type: () -> None
662673
if self._server is not None:
663674
self._server.shutdown()
675+
676+
677+
class LocalChalice(object):
678+
def __init__(self, chalice):
679+
# type: (Chalice) -> None
680+
self._current_request_lookup = {} # type: Dict[int, Optional[Request]]
681+
self._chalice = chalice
682+
683+
@property
684+
def current_request(self): # noqa
685+
# type: () -> Optional[Request]
686+
thread_id = threading.current_thread().ident
687+
assert thread_id is not None
688+
return self._current_request_lookup.get(thread_id, None)
689+
690+
@current_request.setter
691+
def current_request(self, value): # noqa
692+
# type: (Optional[Request]) -> None
693+
thread_id = threading.current_thread().ident
694+
assert thread_id is not None
695+
self._current_request_lookup[thread_id] = value
696+
697+
def __getattr__(self, name):
698+
# type: (str) -> Any
699+
return getattr(self._chalice, name)
700+
701+
def __call__(self, *args, **kwargs):
702+
# type: (Any, Any) -> Any
703+
return self._chalice(*args, **kwargs)

tests/unit/test_local.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from chalice.local import ForbiddenError
2121
from chalice.local import InvalidAuthorizerError
2222
from chalice.local import LocalDevServer
23+
from chalice.local import LocalChalice
2324

2425

2526
AWS_REQUEST_ID_PATTERN = re.compile(
@@ -667,6 +668,21 @@ def test_can_provide_host_to_local_server(sample_app):
667668
assert dev_server.host == '0.0.0.0'
668669

669670

671+
def test_wraps_sample_app_with_local_chalice(sample_app):
672+
dev_server = local.create_local_server(
673+
sample_app, None, "127.0.0.1", 23456
674+
)
675+
assert isinstance(dev_server.app_object, LocalChalice)
676+
assert dev_server.app_object._chalice is sample_app
677+
assert dev_server.app_object.app_name is sample_app.app_name
678+
dev_server.app_object.current_request = "foo"
679+
assert dev_server.app_object.current_request == "foo"
680+
assert (
681+
dev_server.app_object.current_request
682+
is not sample_app.current_request
683+
)
684+
685+
670686
class TestLambdaContext(object):
671687
def test_can_get_remaining_time_once(self, lambda_context_args):
672688
time_source = FakeTimeSource([0, 5])

0 commit comments

Comments
 (0)