Skip to content

Commit b5cf45f

Browse files
author
Chris Rossi
authored
fix: move stub (grpc communication channel) to client (#362)
This brings our practice in line with `google.cloud.datastore`, which also creates one channel per client. This works around a resource leak issue by not requiring the channel to clean up after itself properly in normal usage. The root cause of that issue seems to lie somewhere in `google.auth`, which is where I will follow up. Fixes #343
1 parent e804c51 commit b5cf45f

File tree

8 files changed

+51
-80
lines changed

8 files changed

+51
-80
lines changed

packages/google-cloud-ndb/google/cloud/ndb/_datastore_api.py

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,8 @@
1717
import itertools
1818
import logging
1919

20-
import grpc
21-
22-
from google.cloud import _helpers
2320
from google.cloud.datastore import helpers
2421
from google.cloud.datastore_v1.proto import datastore_pb2
25-
from google.cloud.datastore_v1.proto import datastore_pb2_grpc
2622
from google.cloud.datastore_v1.proto import entity_pb2
2723

2824
from google.cloud.ndb import context as context_module
@@ -54,28 +50,7 @@ def stub():
5450
The stub instance.
5551
"""
5652
context = context_module.get_context()
57-
return context.stub
58-
59-
60-
def make_stub(client):
61-
"""Create the stub for the `Google Datastore` API.
62-
63-
Args:
64-
client (client.Client): The NDB client.
65-
66-
Returns:
67-
:class:`~google.cloud.datastore_v1.proto.datastore_pb2_grpc.DatastoreStub`:
68-
The stub instance.
69-
"""
70-
if client.secure:
71-
user_agent = client.client_info.to_user_agent()
72-
channel = _helpers.make_secure_channel(
73-
client._credentials, user_agent, client.host
74-
)
75-
else:
76-
channel = grpc.insecure_channel(client.host)
77-
78-
return datastore_pb2_grpc.DatastoreStub(channel)
53+
return context.client.stub
7954

8055

8156
def make_call(rpc_name, request, retries=None, timeout=None):

packages/google-cloud-ndb/google/cloud/ndb/client.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""A client for NDB which manages credentials, project, namespace."""
1616

1717
import contextlib
18+
import grpc
1819
import os
1920
import requests
2021

@@ -23,10 +24,12 @@
2324
from google.cloud import _helpers
2425
from google.cloud import client as google_client
2526
from google.cloud.datastore_v1.gapic import datastore_client
27+
from google.cloud.datastore_v1.proto import datastore_pb2_grpc
2628

2729
from google.cloud.ndb import __version__
2830
from google.cloud.ndb import context as context_module
2931

32+
3033
_CLIENT_INFO = client_info.ClientInfo(
3134
user_agent="google-cloud-ndb/{}".format(__version__)
3235
)
@@ -114,6 +117,17 @@ def __init__(self, project=None, namespace=None, credentials=None):
114117
project=project, credentials=credentials
115118
)
116119

120+
if emulator:
121+
channel = grpc.insecure_channel(self.host)
122+
123+
else:
124+
user_agent = _CLIENT_INFO.to_user_agent()
125+
channel = _helpers.make_secure_channel(
126+
self._credentials, user_agent, self.host
127+
)
128+
129+
self.stub = datastore_pb2_grpc.DatastoreStub(channel)
130+
117131
@contextlib.contextmanager
118132
def context(
119133
self,

packages/google-cloud-ndb/google/cloud/ndb/context.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ def policy(key):
146146
[
147147
"client",
148148
"eventloop",
149-
"stub",
150149
"batches",
151150
"commit_batches",
152151
"transaction",
@@ -179,7 +178,6 @@ def __new__(
179178
cls,
180179
client,
181180
eventloop=None,
182-
stub=None,
183181
batches=None,
184182
commit_batches=None,
185183
transaction=None,
@@ -194,14 +192,10 @@ def __new__(
194192
):
195193
# Prevent circular import in Python 2.7
196194
from google.cloud.ndb import _cache
197-
from google.cloud.ndb import _datastore_api
198195

199196
if eventloop is None:
200197
eventloop = _eventloop.EventLoop()
201198

202-
if stub is None:
203-
stub = _datastore_api.make_stub(client)
204-
205199
if batches is None:
206200
batches = {}
207201

@@ -218,7 +212,6 @@ def __new__(
218212
cls,
219213
client=client,
220214
eventloop=eventloop,
221-
stub=stub,
222215
batches=batches,
223216
commit_batches=commit_batches,
224217
transaction=transaction,

packages/google-cloud-ndb/google/cloud/ndb/key.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,13 @@ class Key(object):
147147
148148
from unittest import mock
149149
from google.cloud.ndb import context as context_module
150-
client = mock.Mock(project="testing", spec=("project",), namespace="")
151-
context = context_module.Context(client, stub=mock.Mock(spec=())).use()
150+
client = mock.Mock(
151+
project="testing",
152+
namespace="",
153+
stub=mock.Mock(spec=()),
154+
spec=("project", "namespace", "stub"),
155+
)
156+
context = context_module.Context(client).use()
152157
context.__enter__()
153158
kind1, id1 = "Parent", "C"
154159
kind2, id2 = "Child", 42

packages/google-cloud-ndb/google/cloud/ndb/model.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,13 @@
2020
from google.cloud import ndb
2121
from google.cloud.ndb import context as context_module
2222
23-
client = mock.Mock(project="testing", spec=("project",), namespace="")
24-
context = context_module.Context(client, stub=mock.Mock(spec=())).use()
23+
client = mock.Mock(
24+
project="testing",
25+
namespace="",
26+
stub=mock.Mock(spec=()),
27+
spec=("project", "namespace", "stub"),
28+
)
29+
context = context_module.Context(client).use()
2530
context.__enter__()
2631
2732
.. testcleanup:: *

packages/google-cloud-ndb/tests/conftest.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,13 @@ def initialize_environment(request, environ):
8787
@pytest.fixture
8888
def context():
8989
client = mock.Mock(
90-
project="testing", namespace=None, spec=("project", "namespace")
90+
project="testing",
91+
namespace=None,
92+
spec=("project", "namespace"),
93+
stub=mock.Mock(spec=()),
9194
)
9295
context = context_module.Context(
9396
client,
94-
stub=mock.Mock(spec=()),
9597
eventloop=TestingEventLoop(),
9698
datastore_policy=True,
9799
legacy_data=False,

packages/google-cloud-ndb/tests/unit/test__datastore_api.py

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -46,43 +46,20 @@ def future_result(result):
4646

4747
class TestStub:
4848
@staticmethod
49-
@mock.patch("google.cloud.ndb._datastore_api._helpers")
50-
@mock.patch("google.cloud.ndb._datastore_api.datastore_pb2_grpc")
51-
def test_secure_channel(datastore_pb2_grpc, _helpers):
52-
channel = _helpers.make_secure_channel.return_value
49+
def test_it():
5350
client = mock.Mock(
5451
_credentials="creds",
5552
secure=True,
5653
host="thehost",
57-
spec=("_credentials", "secure", "host"),
54+
stub=object(),
55+
spec=("_credentials", "secure", "host", "stub"),
5856
client_info=client_info.ClientInfo(
5957
user_agent="google-cloud-ndb/{}".format(__version__)
6058
),
6159
)
6260
context = context_module.Context(client)
6361
with context.use():
64-
stub = _api.stub()
65-
assert _api.stub() is stub # one stub per context
66-
assert stub is datastore_pb2_grpc.DatastoreStub.return_value
67-
datastore_pb2_grpc.DatastoreStub.assert_called_once_with(channel)
68-
_helpers.make_secure_channel.assert_called_once_with(
69-
"creds", client.client_info.to_user_agent(), "thehost"
70-
)
71-
72-
@staticmethod
73-
@mock.patch("google.cloud.ndb._datastore_api.grpc")
74-
@mock.patch("google.cloud.ndb._datastore_api.datastore_pb2_grpc")
75-
def test_insecure_channel(datastore_pb2_grpc, grpc):
76-
channel = grpc.insecure_channel.return_value
77-
client = mock.Mock(
78-
secure=False, host="thehost", spec=("secure", "host")
79-
)
80-
context = context_module.Context(client)
81-
with context.use():
82-
stub = _api.stub()
83-
assert stub is datastore_pb2_grpc.DatastoreStub.return_value
84-
datastore_pb2_grpc.DatastoreStub.assert_called_once_with(channel)
85-
grpc.insecure_channel.assert_called_once_with("thehost")
62+
assert _api.stub() is client.stub
8663

8764

8865
class Test_make_call:
@@ -510,10 +487,13 @@ def key_pb(key):
510487

511488
@mock.patch("google.cloud.ndb._datastore_api.datastore_pb2")
512489
def test__datastore_lookup(datastore_pb2, context):
513-
client = mock.Mock(project="theproject", spec=("project",))
514-
stub = mock.Mock(spec=("Lookup",))
515-
with context.new(client=client, stub=stub).use() as context:
516-
context.stub.Lookup = Lookup = mock.Mock(spec=("future",))
490+
client = mock.Mock(
491+
project="theproject",
492+
stub=mock.Mock(spec=("Lookup",)),
493+
spec=("project", "stub"),
494+
)
495+
with context.new(client=client).use() as context:
496+
client.stub.Lookup = Lookup = mock.Mock(spec=("future",))
517497
future = tasklets.Future()
518498
future.set_result("response")
519499
Lookup.future.return_value = future
@@ -524,7 +504,7 @@ def test__datastore_lookup(datastore_pb2, context):
524504
datastore_pb2.LookupRequest.assert_called_once_with(
525505
project_id="theproject", keys=["foo", "bar"], read_options=None
526506
)
527-
context.stub.Lookup.future.assert_called_once_with(
507+
client.stub.Lookup.future.assert_called_once_with(
528508
datastore_pb2.LookupRequest.return_value,
529509
timeout=_api._DEFAULT_TIMEOUT,
530510
)

packages/google-cloud-ndb/tests/unit/test_context.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,31 +37,28 @@ def test___all__():
3737
class TestContext:
3838
def _make_one(self, **kwargs):
3939
client = mock.Mock(
40-
namespace=None, project="testing", spec=("namespace", "project")
40+
namespace=None,
41+
project="testing",
42+
spec=("namespace", "project"),
43+
stub=mock.Mock(spec=()),
4144
)
42-
stub = mock.Mock(spec=())
43-
return context_module.Context(client, stub=stub, **kwargs)
45+
return context_module.Context(client, **kwargs)
4446

45-
@mock.patch("google.cloud.ndb._datastore_api.make_stub")
46-
def test_constructor_defaults(self, make_stub):
47+
def test_constructor_defaults(self):
4748
context = context_module.Context("client")
4849
assert context.client == "client"
49-
assert context.stub is make_stub.return_value
50-
make_stub.assert_called_once_with("client")
5150
assert isinstance(context.eventloop, _eventloop.EventLoop)
5251
assert context.batches == {}
5352
assert context.transaction is None
5453

5554
def test_constructor_overrides(self):
5655
context = context_module.Context(
5756
client="client",
58-
stub="stub",
5957
eventloop="eventloop",
6058
batches="batches",
6159
transaction="transaction",
6260
)
6361
assert context.client == "client"
64-
assert context.stub == "stub"
6562
assert context.eventloop == "eventloop"
6663
assert context.batches == "batches"
6764
assert context.transaction == "transaction"

0 commit comments

Comments
 (0)