Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ async def _restart_on_unavailable(
if item.resume_token:
resume_token = item.resume_token
break
else:
stream_finished = True

except ServiceUnavailable:
del item_buffer[:]
Expand Down Expand Up @@ -238,12 +240,21 @@ async def _restart_on_unavailable(
iterator = None
break

if stream_finished and metrics_tracer is not None:
try:
metrics_tracer.record_operation_completion()
except Exception:
pass
finally:
metrics_tracer = None

for item in item_buffer:
yield item

del item_buffer[:]

if stream_finished:
iterator = None
break
finally:
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,13 +272,16 @@ def __next__(self):
if self._iterator is None:
self._iterator = iter(self._response)
try:
return next(self._iterator)
item = next(self._iterator)
except StopIteration:
self._record_metrics()
raise
except Exception:
self._record_metrics()
raise
if getattr(item, "last", False) is True:
self._record_metrics()
return item
Comment on lines +282 to +284

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

In production, the item yielded by the gRPC response iterator is a PartialResultSet protobuf message (or a proto-plus wrapper). The PartialResultSet proto definition does not contain a last field. Therefore, getattr(item, "last", False) will always return False in production, making this check dead code that never executes.

The integration tests pass only because they fully consume the stream (e.g., using list(results) or for _ in results), which raises StopIteration and triggers _record_metrics() in the except StopIteration block.

Since there is no last field in the Spanner PartialResultSet proto, we should remove this check to avoid dead code and false assumptions about the protobuf structure.

        return item


def _record_metrics(self):
with self._lock:
Expand Down Expand Up @@ -495,10 +498,13 @@ async def __anext__(self):
else:
self._iterator = self._response
try:
return await self._iterator.__anext__()
item = await self._iterator.__anext__()
except StopAsyncIteration:
await self._record_metrics()
raise
except Exception:
await self._record_metrics()
raise
if getattr(item, "last", False) is True:
await self._record_metrics()
return item
Comment on lines +508 to +510

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Similarly to the synchronous version, the item yielded by the async iterator is a PartialResultSet protobuf message, which does not have a last field. Thus, getattr(item, "last", False) will always return False in production. We should remove this check.

        return item

Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ def _restart_on_unavailable(
if item.resume_token:
resume_token = item.resume_token
break
else:
stream_finished = True
except ServiceUnavailable:
del item_buffer[:]
request.resume_token = resume_token
Expand Down Expand Up @@ -211,10 +213,18 @@ def _restart_on_unavailable(
if len(item_buffer) == 0:
iterator = None
break
if stream_finished and metrics_tracer is not None:
try:
metrics_tracer.record_operation_completion()
except Exception:
pass
finally:
metrics_tracer = None
for item in item_buffer:
yield item
del item_buffer[:]
if stream_finished:
iterator = None
break
finally:
try:
Expand Down
Loading
Loading