Skip to content

[fix] Complete KV storage async cleanup - #148

Open
ji-huazhong wants to merge 1 commit into
Ascend:mainfrom
ji-huazhong:fix-kv-async-close
Open

[fix] Complete KV storage async cleanup#148
ji-huazhong wants to merge 1 commit into
Ascend:mainfrom
ji-huazhong:fix-kv-async-close

Conversation

@ji-huazhong

Copy link
Copy Markdown
Collaborator

Background

  • KVStorageManager.get_data() and clear_data() invoked synchronous backend calls directly from async methods, blocking the caller's event loop. put_data() used the loop's default executor, which was outside the manager's lifecycle.
  • TensorDict reconstruction still occupied the caller's event loop after get(), which could stall unrelated coroutines in a Ray worker or async actor.
  • The executor finalizer captured None before the lazy executor was created, so it could not release the actual thread pool. KVStorageManager.close() also did not close backend-owned resources such as MooncakeStoreClient.

What this changes

  • Run synchronous put, get, and clear operations on a manager-owned storage executor, and keep get plus TensorDict reconstruction off the caller's event loop.
  • Keep storage I/O and reconstruction executors separate so concurrent get operations cannot deadlock through nested submissions to the same pool.
  • Add a default no-op StorageKVClient.close() lifecycle hook and make KVStorageManager.close() idempotently drain both executors, close the backend client, and then release the existing notify and ZMQ resources.
  • Remove the ineffective finalizer and reject new executor work after close.
  • Add regression coverage for non-blocking async dispatch, reconstruction thread placement, executor shutdown, backend cleanup, and idempotent close.

Tests

  • python -m compileall -q transfer_queue tutorial tests
  • python -m ruff check transfer_queue/storage/clients/base.py transfer_queue/storage/managers/base.py tests/test_kv_storage_manager.py
  • python -m pytest -q tests/test_kv_storage_manager.py tests/test_storage_client_factory.py Result: 11 passed, 2 skipped.
  • python -m pytest -q tests/e2e/test_kv_interface_e2e.py::TestRayWorkerKVInterfaceE2E Result: 2 passed for sync and async Ray remote-worker paths.
  • python -m pytest -q --ignore=tests/test_yuanrong_storage_client_e2e.py Result: 571 passed, 10 skipped.
  • The unfiltered suite reached 571 passed and 10 skipped, with 8 setup errors in the pre-existing Yuanrong mock test because the verl environment has no yr package and yuanrong_client therefore exposes no datasystem attribute.

Problem:
- KVStorageManager.get_data() and clear_data() invoked synchronous backend
  calls directly from async methods, blocking the caller's event loop. put_data()
  used the loop's default executor, which was outside the manager's lifecycle.
- TensorDict reconstruction still occupied the caller's event loop after get(),
  which could stall unrelated coroutines in a Ray worker or async actor.
- The executor finalizer captured None before the lazy executor was created, so
  it could not release the actual thread pool. KVStorageManager.close() also did
  not close backend-owned resources such as MooncakeStoreClient.

Changes:
- Run synchronous put, get, and clear operations on a manager-owned storage
  executor, and keep get plus TensorDict reconstruction off the caller's event
  loop.
- Keep storage I/O and reconstruction executors separate so concurrent get
  operations cannot deadlock through nested submissions to the same pool.
- Add a default no-op StorageKVClient.close() lifecycle hook and make
  KVStorageManager.close() idempotently drain both executors, close the backend
  client, and then release the existing notify and ZMQ resources.
- Remove the ineffective finalizer and reject new executor work after close.
- Add regression coverage for non-blocking async dispatch, reconstruction thread
  placement, executor shutdown, backend cleanup, and idempotent close.

Validation:
- python -m compileall -q transfer_queue tutorial tests
- python -m ruff check transfer_queue/storage/clients/base.py
  transfer_queue/storage/managers/base.py tests/test_kv_storage_manager.py
- python -m pytest -q tests/test_kv_storage_manager.py
  tests/test_storage_client_factory.py
  Result: 11 passed, 2 skipped.
- python -m pytest -q
  tests/e2e/test_kv_interface_e2e.py::TestRayWorkerKVInterfaceE2E
  Result: 2 passed for sync and async Ray remote-worker paths.
- python -m pytest -q --ignore=tests/test_yuanrong_storage_client_e2e.py
  Result: 571 passed, 10 skipped.
- The unfiltered suite reached 571 passed and 10 skipped, with 8 setup errors in
  the pre-existing Yuanrong mock test because the verl environment has no yr
  package and yuanrong_client therefore exposes no datasystem attribute.

Signed-off-by: ji-huazhong <hzji210@gmail.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3642f7eb29

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

)
return self._merge_tensors_to_tensordict(metadata, values)

return await self._run_storage_call(get_and_merge)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep GDR get work on the selected CUDA device

When MooncakeStore_GDR is used after the process selects a non-default GPU before tq.init(), this now runs MooncakeStoreClient.get() on a fresh executor thread. That GDR path uses torch.cuda.current_device() when lazily initializing/unpacking the staging buffer, and CUDA current device is thread-local, so the worker can fall back to cuda:0 instead of the device the caller selected, causing buffers/results to be allocated on the wrong GPU or failing on multi-GPU processes.

AGENTS.md reference: AGENTS.md:L17-L18

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant