Skip to content

Commit 6836a46

Browse files
author
Jon Wayne Parrott
committed
Make connection return a thread-local instance of http.
Fixes #926, and opens up the possibility of using an object pool later.
1 parent a04840b commit 6836a46

File tree

2 files changed

+48
-9
lines changed

2 files changed

+48
-9
lines changed

gcloud/connection.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
"""Shared implementation of connections to API servers."""
1616

1717
import json
18+
import threading
19+
20+
import httplib2
1821
from pkg_resources import get_distribution
1922
import six
2023
from six.moves.urllib.parse import urlencode # pylint: disable=F0401
2124

22-
import httplib2
23-
2425
from gcloud.exceptions import make_exception
2526

2627

@@ -55,6 +56,8 @@ class Connection(object):
5556
object will also need to be able to add a bearer token to API
5657
requests and handle token refresh on 401 errors.
5758
59+
A custom ``http`` object will also need to ensure its own thread safety.
60+
5861
:type credentials: :class:`oauth2client.client.OAuth2Credentials` or
5962
:class:`NoneType`
6063
:param credentials: The OAuth2 Credentials to use for this connection.
@@ -73,6 +76,7 @@ class Connection(object):
7376
"""
7477

7578
def __init__(self, credentials=None, http=None):
79+
self._local = threading.local()
7680
self._http = http
7781
self._credentials = self._create_scoped_credentials(
7882
credentials, self.SCOPE)
@@ -91,14 +95,24 @@ def credentials(self):
9195
def http(self):
9296
"""A getter for the HTTP transport used in talking to the API.
9397
94-
:rtype: :class:`httplib2.Http`
95-
:returns: A Http object used to transport data.
98+
This will return a thread-local :class:`httplib2.Http` instance unless
99+
a custom transport has been provided to the :class:`Connection`
100+
constructor.
101+
102+
:rtype: :class:`httplib2.Http` or the custom HTTP transport specifed
103+
to the connection constructor.
104+
:returns: An ``Http`` object used to transport data.
96105
"""
97-
if self._http is None:
98-
self._http = httplib2.Http()
106+
if self._http is not None:
107+
return self._http
108+
109+
if not hasattr(self._local, 'http'):
110+
self._local.http = httplib2.Http()
99111
if self._credentials:
100-
self._http = self._credentials.authorize(self._http)
101-
return self._http
112+
self._local.http = self._credentials.authorize(
113+
self._local.http)
114+
115+
return self._local.http
102116

103117
@staticmethod
104118
def _create_scoped_credentials(credentials, scope):

gcloud/test_connection.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import threading
1516
import unittest2
1617

1718

@@ -66,6 +67,26 @@ def test_user_agent_format(self):
6667
conn = self._makeOne()
6768
self.assertEqual(conn.USER_AGENT, expected_ua)
6869

70+
def test_thread_local_http(self):
71+
credentials = _Credentials(lambda http: object())
72+
conn = self._makeOne(credentials)
73+
74+
self.assertTrue(conn.http is not None)
75+
76+
# Should return the same instance when called again.
77+
self.assertTrue(conn.http is conn.http)
78+
79+
# Should return a different instance from a different thread.
80+
http = conn.http
81+
82+
def test_thread():
83+
self.assertTrue(conn.http is not None)
84+
self.assertTrue(conn.http is not http)
85+
86+
thread = threading.Thread(target=test_thread)
87+
thread.start()
88+
thread.join()
89+
6990

7091
class TestJSONConnection(unittest2.TestCase):
7192

@@ -374,7 +395,11 @@ def __init__(self, authorized=None):
374395

375396
def authorize(self, http):
376397
self._called_with = http
377-
return self._authorized
398+
399+
if callable(self._authorized):
400+
return self._authorized(http)
401+
else:
402+
return self._authorized
378403

379404
@staticmethod
380405
def create_scoped_required():

0 commit comments

Comments
 (0)