GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix(bigtable): close accelerator daemon and unregister instance on Table GC by mutianf · Pull Request #19023 · googleapis/google-cloud-python

Conversation

@mutianf

@mutianf mutianf commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

When an unclosed Table, TableAsync, or AcceleratorDaemon is garbage-collected, subprocess.Popen.__del__ runs while proc.stdin is still open and the Go daemon's monitorStdin watchdog is blocked waiting for EOF. Popen.__del__ sees returncode is None and appends proc to subprocess._active, resurrecting proc and its open stdin pipe so the Go daemon subprocess, /tmp/bt-accel-* directory, and client._instance_owners entry are leaked for the lifetime of the Python process.

Register weakref.finalize callbacks on AcceleratorDaemon and _DataApiTarget/_DataApiTargetAsync (detached on explicit close()) so garbage collection closes stdin, reaps the daemon subprocess before Popen.__del__ runs, removes the UDS tempdir, and unregisters the instance from the client.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gemini-code-assist[bot] reacted with eyes emoji
All reactions
  • 👀 1 reaction
@mutianf
fix(bigtable): close accelerator daemon and unregister instance on Ta…
32915d3
…ble GC

Register weakref.finalize callbacks on AcceleratorDaemon and _DataApiTarget/_DataApiTargetAsync so garbage-collected tables and daemons close stdin, reap the Go daemon subprocess before Popen.__del__ runs, remove the UDS tempdir, and unregister the instance from the client.
@mutianf
mutianf requested a review from a team as a code owner November 9, 2026 15:54
gemini-code-assist[bot]
gemini-code-assist Bot reviewed Nov 9, 2026
View reviewed changes

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Code Review

This pull request implements automatic resource cleanup on garbage collection using weakref.finalize for AcceleratorDaemon, TableAsync, and Table instances, ensuring that subprocesses are reaped and registrations are cleaned up even if close() is not explicitly called. The review feedback highlights a critical reference cycle issue: passing self._register_instance_future directly to weakref.finalize prevents the table instances from being garbage-collected. To resolve this, the reviewer suggests passing a weak reference to the future, updating the finalizer to resolve it, and using a custom mock subclass in tests to avoid TypeError since standard mocks do not support weak references.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread packages/google-cloud-bigtable/google/cloud/bigtable/data/_async/client.py
Comment on lines +1423 to +1431
self._instance_finalizer = weakref.finalize(
self,
self._finalize_instance_registration,
self.client,
self.instance_id,
self.app_profile_id,
id(self),
self._register_instance_future,
)

@gemini-code-assist gemini-code-assist Bot Nov 9, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

critical

Passing self._register_instance_future directly to weakref.finalize creates a reference cycle that prevents the TableAsync instance from ever being garbage collected.

Why this happens:

  1. weakref.finalize objects are stored in a global registry (weakref.finalize._registry) to keep them alive.
  2. This creates a strong reference path: global registry -> finalize object -> args -> self._register_instance_future.
  3. Since self._register_instance_future is a Task/Future wrapping a coroutine of self, it holds a strong reference to self.
  4. Therefore, self is reachable from a global root, meaning its reference count will never drop to 0, and the cyclic garbage collector will never collect it.

Solution:

Pass a weak reference to the future (weakref.ref(self._register_instance_future)) instead of the future itself. This breaks the reference cycle and allows the instance to be garbage collected as expected.

Suggested change
self._instance_finalizer = weakref.finalize(
self,
self._finalize_instance_registration,
self.client,
self.instance_id,
self.app_profile_id,
id(self),
self._register_instance_future,
)
self._instance_finalizer = weakref.finalize(
self,
self._finalize_instance_registration,
self.client,
self.instance_id,
self.app_profile_id,
id(self),
weakref.ref(self._register_instance_future),
)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread packages/google-cloud-bigtable/google/cloud/bigtable/data/_async/client.py
Comment on lines +2524 to +2534
@staticmethod
def _finalize_instance_registration(
client: Any,
instance_id: str,
app_profile_id: str | None,
owner_id: int,
register_future: Any,
) -> None:
if register_future:
register_future.cancel()
client._remove_instance_registration(instance_id, app_profile_id, owner_id)

@gemini-code-assist gemini-code-assist Bot Nov 9, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

critical

Update _finalize_instance_registration to resolve the weak reference to the registration future before attempting to cancel it.

Suggested change
@staticmethod
def _finalize_instance_registration(
client: Any,
instance_id: str,
app_profile_id: str | None,
owner_id: int,
register_future: Any,
) -> None:
if register_future:
register_future.cancel()
client._remove_instance_registration(instance_id, app_profile_id, owner_id)
@staticmethod
def _finalize_instance_registration(
client: Any,
instance_id: str,
app_profile_id: str | None,
owner_id: int,
register_future_ref: Any,
) -> None:
register_future = register_future_ref() if register_future_ref else None
if register_future:
register_future.cancel()
client._remove_instance_registration(instance_id, app_profile_id, owner_id)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread packages/google-cloud-bigtable/google/cloud/bigtable/data/_sync_autogen/client.py
Comment on lines +1124 to +1132
self._instance_finalizer = weakref.finalize(
self,
self._finalize_instance_registration,
self.client,
self.instance_id,
self.app_profile_id,
id(self),
self._register_instance_future,
)

@gemini-code-assist gemini-code-assist Bot Nov 9, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

critical

Passing self._register_instance_future directly to weakref.finalize creates a reference cycle that prevents the Table instance from ever being garbage collected.

Why this happens:

  1. weakref.finalize objects are stored in a global registry (weakref.finalize._registry) to keep them alive.
  2. This creates a strong reference path: global registry -> finalize object -> args -> self._register_instance_future.
  3. Since self._register_instance_future is a Task/Future wrapping a coroutine of self, it holds a strong reference to self.
  4. Therefore, self is reachable from a global root, meaning its reference count will never drop to 0, and the cyclic garbage collector will never collect it.

Solution:

Pass a weak reference to the future (weakref.ref(self._register_instance_future)) instead of the future itself. This breaks the reference cycle and allows the instance to be garbage collected as expected.

Suggested change
self._instance_finalizer = weakref.finalize(
self,
self._finalize_instance_registration,
self.client,
self.instance_id,
self.app_profile_id,
id(self),
self._register_instance_future,
)
self._instance_finalizer = weakref.finalize(
self,
self._finalize_instance_registration,
self.client,
self.instance_id,
self.app_profile_id,
id(self),
weakref.ref(self._register_instance_future),
)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread packages/google-cloud-bigtable/google/cloud/bigtable/data/_sync_autogen/client.py
Comment on lines +2076 to +2086
@staticmethod
def _finalize_instance_registration(
client: Any,
instance_id: str,
app_profile_id: str | None,
owner_id: int,
register_future: Any,
) -> None:
if register_future:
register_future.cancel()
client._remove_instance_registration(instance_id, app_profile_id, owner_id)

@gemini-code-assist gemini-code-assist Bot Nov 9, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

critical

Update _finalize_instance_registration to resolve the weak reference to the registration future before attempting to cancel it.

Suggested change
@staticmethod
def _finalize_instance_registration(
client: Any,
instance_id: str,
app_profile_id: str | None,
owner_id: int,
register_future: Any,
) -> None:
if register_future:
register_future.cancel()
client._remove_instance_registration(instance_id, app_profile_id, owner_id)
@staticmethod
def _finalize_instance_registration(
client: Any,
instance_id: str,
app_profile_id: str | None,
owner_id: int,
register_future_ref: Any,
) -> None:
register_future = register_future_ref() if register_future_ref else None
if register_future:
register_future.cancel()
client._remove_instance_registration(instance_id, app_profile_id, owner_id)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread packages/google-cloud-bigtable/tests/unit/data/test_accelerator_dispatch.py
)
fake_health = mock.Mock(close=mock.AsyncMock() if is_async else mock.Mock())
fake_client = mock.Mock(close=mock.AsyncMock() if is_async else mock.Mock())
fake_future = mock.Mock()

@gemini-code-assist gemini-code-assist Bot Nov 9, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

medium

Since mock.Mock objects do not support weak references by default, passing a standard mock to weakref.ref will raise a TypeError. Use a custom subclass of mock.Mock that defines __slots__ with __weakref__ to allow weak referencing in tests.

            class WeakableMock(mock.Mock):
                __slots__ = ("__weakref__",)
            fake_future = WeakableMock()

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

1 more reviewer
@gemini-code-assist gemini-code-assist[bot] gemini-code-assist[bot] left review comments
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@mutianf
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.