Skip to content

Commit

Permalink
Make connection return a thread-local instance of http.
Browse files Browse the repository at this point in the history
Fixes googleapis#926, and opens up the possibility of using an object pool later.
  • Loading branch information
Jon Wayne Parrott committed Dec 11, 2015
1 parent a04840b commit 8417e64
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 9 deletions.
29 changes: 21 additions & 8 deletions gcloud/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
"""Shared implementation of connections to API servers."""

import json
import threading

import httplib2
from pkg_resources import get_distribution
import six
from six.moves.urllib.parse import urlencode # pylint: disable=F0401

import httplib2

from gcloud.exceptions import make_exception


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

def __init__(self, credentials=None, http=None):
self._local = threading.local()
self._http = http
self._credentials = self._create_scoped_credentials(
credentials, self.SCOPE)
Expand All @@ -91,14 +95,23 @@ def credentials(self):
def http(self):
"""A getter for the HTTP transport used in talking to the API.
:rtype: :class:`httplib2.Http`
:returns: A Http object used to transport data.
This will return a thread-local :class:`httplib2.Http` instance unless
a custom transport has been provided to the `Connection` constructor.
:rtype: :class:`httplib2.Http` or the custom HTTP transport specifed
to the connection constructor.
:returns: An `Http` object used to transport data.
"""
if self._http is None:
self._http = httplib2.Http()
if self._http is not None:
return self._http

if not hasattr(self._local, 'http'):
self._local.http = httplib2.Http()
if self._credentials:
self._http = self._credentials.authorize(self._http)
return self._http
self._local.http = self._credentials.authorize(
self._local.http)

return self._local.http

@staticmethod
def _create_scoped_credentials(credentials, scope):
Expand Down
27 changes: 26 additions & 1 deletion gcloud/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import threading
import unittest2


Expand Down Expand Up @@ -66,6 +67,26 @@ def test_user_agent_format(self):
conn = self._makeOne()
self.assertEqual(conn.USER_AGENT, expected_ua)

def test_thread_local_http(self):
credentials = _Credentials(lambda x: object())
conn = self._makeOne(credentials)

self.assertTrue(conn.http is not None)

# Should return the same instance when called again.
self.assertTrue(conn.http is conn.http)

# Should return a different instance from a different thread.
http = conn.http

def test_thread():
self.assertTrue(conn.http is not None)
self.assertTrue(conn.http is not http)

thread = threading.Thread(target=test_thread)
thread.start()
thread.join()


class TestJSONConnection(unittest2.TestCase):

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

def authorize(self, http):
self._called_with = http
return self._authorized

if callable(self._authorized):
return self._authorized(http)
else:
return self._authorized

@staticmethod
def create_scoped_required():
Expand Down

0 comments on commit 8417e64

Please sign in to comment.