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 — Merge dev 1029 by 0oshowero0 · Pull Request #94 · TransferQueue/TransferQueue

This repository was archived by the owner on Jan 21, 2026. It is now read-only.

Conversation

@0oshowero0

@0oshowero0 0oshowero0 commented Oct 29, 2025 •
edited by coderabbitai Bot
Loading

Uh oh!

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

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Pluggable storage backend added with an async simple storage manager as the default.
    • Context manager to limit PyTorch automatic parallel threads for performance control.
  • Refactor

    • Simplified orchestration to a single-controller flow and streamlined client/controller interactions.
    • Storage subsystem reorganized and public storage types renamed for clarity.
  • Tests

    • Added comprehensive tests covering the new storage manager and storage unit behaviors.
  • Chores

    • Project metadata updated; pytest-asyncio and psutil added to dependencies.

Sorry, something went wrong.

Uh oh!

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

All reactions
0oshowero0 and others added 5 commits October 25, 2025 15:42
@0oshowero0
Merge pull request #66 from 0oshowero0/han/unified_storage_abstract
fc92f03
[BREAKING] Refactor: Provide a general storage abstraction layer
@0oshowero0
perf:add auto parallel limit function (#88)
9ff2fa5
Signed-off-by: 0oshowero0 <[email protected]>
@baymax591
[Perf] Fix low efficient loops for large batchs (#91)
c889427
* accelerate _add_field_data

* accelerate

* fix

* fix len(index)=1

Co-authored-by: Copilot <[email protected]>

---------

Co-authored-by: Copilot <[email protected]>
@jianjunzhong
refactor: refactor TransferQueue client and storage manager to use a …
26b4791
…single controller. (#89)

* refactor: refactor TransferQueue client and storage manager to use a single controller.

Signed-off-by: jianjunzhong <[email protected]>

* refactor: simplify process_zmq_server_info to handle single handler objects

Signed-off-by: jianjunzhong <[email protected]>

* fix: update mock socket to use recv instead of recv_multipart in tests

Signed-off-by: jianjunzhong <[email protected]>

* fix: fix comments in pull request

Signed-off-by: jianjunzhong <[email protected]>

---------

Signed-off-by: jianjunzhong <[email protected]>
@0oshowero0
[Misc] Refactor: a better storage folder structure (#93)
3332482
* refactor: storage folder struncture

Signed-off-by: 0oshowero0 <[email protected]>

* fix

Signed-off-by: 0oshowero0 <[email protected]>

---------

Signed-off-by: 0oshowero0 <[email protected]>
Copilot AI review requested due to automatic review settings October 29, 2025 13:37
@coderabbitai

coderabbitai Bot commented Oct 29, 2025 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Refactors storage to a single-controller model and introduces a pluggable storage-manager layer. Moves storage code into transfer_queue/storage, renames TransferQueueStorageSimpleUnit → SimpleStorageUnit, adds TransferQueueStorageManager/factory/AsyncSimpleStorageManager, simplifies metadata shapes, and updates clients/tests to use a single controller_info.

Changes

Cohort / File(s) Change Summary
CI & Packaging
\.github/workflows/python-package.yml, pyproject.toml, requirements.txt
CI pip install adds pytest_asyncio; pyproject README enabled, pytest ini options and mypy overrides added, optional test deps include pytest-asyncio; appended psutil to requirements.
Top-level exports
transfer_queue/__init__.py
Replaced exported TransferQueueStorageSimpleUnit with SimpleStorageUnit in __all__.
Client API
transfer_queue/client.py
Clients now take a single controller_info; added initialize_storage_manager(manager_type, config) and close(); put/get/clear delegate to storage manager; dynamic socket logic simplified; process_zmq_server_info broadened.
Controller
transfer_queue/controller.py
Removed num_storage_units param and global-index→storage mapping; controller id shortened; replaced handshake with poll-based connection tracking and persistent HANDSHAKE_ACK behavior.
Metadata model
transfer_queue/metadata.py
Field- and sample-centric model: FieldMeta includes dtype/shape/production_status; SampleMeta drops storage_id/local_index and gains global_step/global_index/fields; BatchMeta removes storage groups and related APIs.
Storage package (new)
transfer_queue/storage/__init__.py, transfer_queue/storage/clients/*
New package exposing SimpleStorageUnit, StorageUnitData, StorageMetaGroup, TransferQueueStorageManager, TransferQueueStorageManagerFactory, AsyncSimpleStorageManager; placeholder clients files added.
Storage backend (new)
transfer_queue/storage/simple_backend.py
New StorageUnitData, Ray-like SimpleStorageUnit with ZMQ put/get/clear server, and StorageMetaGroup utilities.
Storage managers (new)
transfer_queue/storage/managers/base.py, .../factory.py, .../simple_backend_manager.py
Added TransferQueueStorageManager abstract base with handshake/notify; factory registry TransferQueueStorageManagerFactory; implemented AsyncSimpleStorageManager coordinating multi-unit async transfers, grouping, and socket lifecycle.
Removed legacy module
transfer_queue/storage.py
Deleted monolithic storage implementation (StorageUnitData, TransferQueueStorageSimpleUnit, ZMQ orchestration).
Utils
transfer_queue/utils/zmq_utils.py, transfer_queue/utils/utils.py
ZMQServerInfo changed from dataclass to plain class with to_dict/__str__; request IDs shortened to uuid4().hex[:8]; added limit_pytorch_auto_parallel_threads contextmanager (uses psutil).
Demos / Recipes
recipe/simple_use_case/async_demo.py, recipe/simple_use_case/sync_demo.py
Reworked to single-controller flow, use SimpleStorageUnit, updated constructors/signatures (servers/workers/managers), call initialize_storage_manager, merge config changes, and ensure client cleanup (close()).
Tests
tests/test_async_simple_storage_manager.py, tests/test_client.py, tests/test_controller.py, tests/test_simple_storage_unit.py
Added tests for AsyncSimpleStorageManager; updated client/controller tests to single-controller API and enum-based roles; storage unit tests moved to Ray-backed SimpleStorageUnit and use MockStorageClient; payloads and metadata assertions updated.

Sequence Diagram(s)

sequenceDiagram
    participant App as App/Trainer
    participant Client as AsyncTransferQueueClient
    participant Manager as AsyncSimpleStorageManager
    participant Controller as TransferQueueController
    participant Storage as SimpleStorageUnit[N]

    rect rgb(240,248,255)
    App->>Client: __init__(client_id, controller_info)
    Client->>Controller: HANDSHAKE_REQUEST
    Controller-->>Client: HANDSHAKE_ACK
    App->>Client: initialize_storage_manager("AsyncSimpleStorageManager", config)
    Client->>Manager: __init__(config)
    Manager->>Controller: HANDSHAKE_REQUEST
    Controller-->>Manager: HANDSHAKE_ACK
    end

    rect rgb(228,255,228)
    App->>Client: async_put(data, metadata)
    Client->>Manager: put_data(data, metadata)
    Manager->>Manager: build_storage_meta_groups()
    loop per storage unit
      Manager->>Storage: PUT_DATA_REQUEST
      Storage-->>Manager: PUT_DATA_RESPONSE
    end
    Manager->>Controller: NOTIFY_DATA_UPDATE
    Controller-->>Manager: NOTIFY_DATA_UPDATE_ACK
    end

    rect rgb(255,245,230)
    App->>Client: async_get_data(metadata)
    Client->>Manager: get_data(metadata)
    loop per storage unit
      Manager->>Storage: GET_DATA_REQUEST
      Storage-->>Manager: GET_DATA_RESPONSE
    end
    Manager-->>Client: merged TensorDict
    Client-->>App: TensorDict
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Areas needing focused review:

  • transfer_queue/storage/managers/simple_backend_manager.py — grouping, async I/O, socket decorator correctness, error propagation and merging of per-storage results.
  • transfer_queue/metadata.py — ensure all consumers handle removed storage_id/local_index and new FieldMeta/SampleMeta semantics.
  • transfer_queue/client.py — lifecycle of storage_manager, single-controller routing, initialize_storage_manager and close interactions.
  • transfer_queue/controller.py — poll-based handshake, connection tracking, concurrency and replay/duplicate-handling behavior.
  • tests — verify mocks/fixtures align with new handshake/notify semantics and enum-based roles.

Possibly related PRs

  • refactor: refactor TransferQueue client and storage manager to use a single controller. #89 — similar single-controller client refactor (client constructor/signatures and storage-manager API changes).
  • [Misc] Refactor: a better storage folder structure #93 — related storage subsystem refactor into transfer_queue/storage, new manager/factory and ID shortening.

Poem

🐰
I hopped from many controllers to one,
Storages dance in round-robin fun,
Managers knock, controllers reply,
Metadata trimmed — neat under the sky,
I close my socket, nibble a carrot — bye! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.97% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The PR title "Merge dev 1029" is extremely vague and generic, using only non-descriptive terms that fail to convey any meaningful information about the changeset. While the PR contains substantial architectural changes—including a storage backend restructuring (SimpleStorageUnit, AsyncSimpleStorageManager), a shift from multi-controller to single-controller design, significant client API signature updates, and major metadata refactoring—the title provides no indication of these changes. A teammate scanning the repository history would have no way to understand what this PR accomplishes based solely on the title. Consider using a more descriptive title that summarizes the primary change. For example: "Refactor storage backend abstraction and simplify to single-controller architecture" or "Implement AsyncSimpleStorageManager and unify storage manager abstraction" would clearly communicate the scope and purpose of these changes to reviewers.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Comment @coderabbitai help to get the list of available commands and usage tips.

All reactions

Sorry, something went wrong.

Uh oh!

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

Copilot AI reviewed Oct 29, 2025
View reviewed changes

Copilot AI 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.

Pull Request Overview

This PR implements a storage abstraction layer by refactoring the storage architecture:

  • Introduces TransferQueueStorageManager as an abstract base class for managing distributed storage units
  • Implements AsyncSimpleStorageManager as the default storage backend
  • Refactors TransferQueueStorageSimpleUnit to SimpleStorageUnit with simplified responsibilities
  • Removes controller-to-storage registration complexity by eliminating the need for multiple controllers
  • Moves metadata grouping logic (StorageMetaGroup) from metadata.py to storage/simple_backend.py

Reviewed Changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
transfer_queue/utils/zmq_utils.py Simplified imports and changed ZMQServerInfo from dataclass to regular class, modified request_id generation to use 8-char hex
transfer_queue/utils/utils.py Added limit_pytorch_auto_parallel_threads context manager for PyTorch performance optimization
transfer_queue/storage/simple_backend.py New file implementing StorageUnitData, SimpleStorageUnit, and StorageMetaGroup
transfer_queue/storage/managers/*.py New storage manager abstraction with base class, factory, and async implementation
transfer_queue/metadata.py Removed storage-related fields from SampleMeta and StorageMetaGroup class, simplified code
transfer_queue/controller.py Removed storage mapping logic and multi-controller support, simplified handshake mechanism
transfer_queue/client.py Refactored to use single controller and storage manager abstraction
tests/*.py Updated tests to match new architecture
recipe/*.py Updated demo scripts to use new API

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread transfer_queue/utils/zmq_utils.py Resolved

Uh oh!

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

Comment thread transfer_queue/storage/simple_backend.py Resolved

Uh oh!

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

Comment thread transfer_queue/storage/simple_backend.py Resolved

Uh oh!

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

Comment thread transfer_queue/metadata.py Resolved

Uh oh!

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

Comment thread transfer_queue/metadata.py Resolved

Uh oh!

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

Comment thread transfer_queue/controller.py Resolved

Uh oh!

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

Comment thread transfer_queue/storage/managers/base.py Resolved

Uh oh!

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

Comment thread transfer_queue/storage/managers/simple_backend_manager.py Resolved

Uh oh!

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

Comment thread transfer_queue/storage/managers/simple_backend_manager.py Resolved

Uh oh!

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

coderabbitai[bot]
coderabbitai Bot reviewed Oct 29, 2025
View reviewed changes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (11)
transfer_queue/metadata.py (2)

211-229: Validate TensorDict/sample length before per‑sample add_fields

Potential IndexError if len(self.samples) != len(fields). Validate and error early.

         fields = _extract_field_metas(tensor_dict, set_all_ready)
-        if len(fields) > 0:
-            for idx, sample in enumerate(self.samples):
-                sample.add_fields(fields=fields[idx])
+        if fields:
+            if len(self.samples) != len(fields):
+                raise ValueError(f"add_fields length mismatch: samples={len(self.samples)} vs fields={len(fields)}")
+            for idx, sample in enumerate(self.samples):
+                sample.add_fields(fields=fields[idx])

234-240: Fix type violation and inconsistent error handling in getitem

The current implementation has a type safety bug: when self.samples is empty, sample_meta becomes [], resulting in samples=[[]] which violates the type hint samples: list[SampleMeta]. Additionally, error handling is inconsistent—the empty case lacks bounds checking while the non-empty case implicitly relies on list's __getitem__ behavior.

The proposed fix is correct. It adds explicit bounds checking with proper IndexError, ensures type correctness by accessing self.samples[int(item)] directly (which is a SampleMeta object), and provides consistent behavior for all cases.

     def __getitem__(self, item):
-        if isinstance(item, int | np.integer):
-            sample_meta = self.samples[item] if self.samples else []
-            return BatchMeta(samples=[sample_meta], extra_info=self.extra_info)
+        if isinstance(item, (int, np.integer)):
+            if not (0 <= int(item) < len(self.samples)):
+                raise IndexError("BatchMeta index out of range")
+            return BatchMeta(samples=[self.samples[int(item)]], extra_info=self.extra_info)
         else:
             raise TypeError(f"Indexing with {type(item)} is not supported now!")
transfer_queue/utils/zmq_utils.py (1)

116-131: Unsafe pickle deserialization on network data remains a critical security risk

Remote code execution via malicious pickle is possible when an attacker gains access to the communication channel. The codebase deserializes untrusted ZMQ network data in 18+ locations without any validation or safe codec. Python's pickle documentation explicitly warns: "The pickle module is not secure. Only unpickle data you trust."

Replace pickle-based serialization with safer alternatives such as JSON, YAML, or MessagePack. The codebase already demonstrates msgpack usage in serial_utils.py for data serialization—extend this pattern to ZMQMessage. Additionally, libzmq provides four levels of security including authenticated NULL, PLAIN, CURVE, and GSSAPI mechanisms that should be enforced on all sockets accepting external connections.

Affected files:

  • transfer_queue/utils/zmq_utils.py (lines 116-131): Replace pickle.dumps/loads with msgpack or JSON with explicit schema validation
  • 18+ calling sites receiving ZMQ data (identified in script output): Verify all depend on the fixed serialize/deserialize methods
transfer_queue/controller.py (2)

601-674: Handle unknown request types; avoid unbound response_msg.

If an unexpected request_type arrives, response_msg is undefined and the thread crashes.

@@ def _process_request(self):
-            if request_msg.request_type == ZMQRequestType.GET_META:
+            if request_msg.request_type == ZMQRequestType.GET_META:
@@
-            elif request_msg.request_type == ZMQRequestType.CHECK_CONSUMPTION:
+            elif request_msg.request_type == ZMQRequestType.CHECK_CONSUMPTION:
@@
-            self.request_handle_socket.send_multipart([identity, response_msg.serialize()])
+            else:
+                response_msg = ZMQMessage.create(
+                    request_type=ZMQRequestType.PUT_GET_OPERATION_ERROR,
+                    sender_id=self.controller_id,
+                    receiver_id=request_msg.sender_id,
+                    body={"message": f"Unknown request type: {request_msg.request_type}"},
+                )
+            self.request_handle_socket.send_multipart([identity, response_msg.serialize()])

681-728: Protect _update_data_status loop; prevent thread crash.

A malformed message or deserialize error will crash the thread. Wrap recv/handle in try/except and continue.

@@ def _update_data_status(self):
-        while True:
-            logger.debug("Preparing _update_data_status...")
-            identity, serialized_msg = self.data_status_update_socket.recv_multipart()
-            logger.debug("Controller received update_data_status request!")
-            request_msg = ZMQMessage.deserialize(serialized_msg)
+        while True:
+            try:
+                logger.debug("Preparing _update_data_status...")
+                identity, serialized_msg = self.data_status_update_socket.recv_multipart()
+                logger.debug("Controller received update_data_status request!")
+                request_msg = ZMQMessage.deserialize(serialized_msg)
@@
-            elif request_msg.request_type == ZMQRequestType.NOTIFY_DATA_UPDATE_ERROR:
+            elif request_msg.request_type == ZMQRequestType.NOTIFY_DATA_UPDATE_ERROR:
@@
-                self.data_status_update_socket.send_multipart([identity, response_msg.serialize()])
+                self.data_status_update_socket.send_multipart([identity, response_msg.serialize()])
+            else:
+                logger.warning(f"Unexpected update request type: {getattr(request_msg,'request_type',None)}")
+            except Exception as e:
+                logger.error(f"_update_data_status error: {e}")
+                continue
recipe/simple_use_case/async_demo.py (2)

66-73: Fix NonTensorData stacking (runtime error).

torch.stack on NonTensorData is invalid. Use NonTensorStack for non-tensor fields.

-        output = TensorDict(
-            {
-                "generate_sequences_ids": output,
-                "non_tensor_data": torch.stack([NonTensorData("test_str") for _ in range(output.size(0))]),
-                "nested_tensor": torch.nested.as_nested_tensor([torch.randn(1, 2) for _ in range(output.size(0))]),
-            },
-            batch_size=output.size(0),
-        )
+        output = TensorDict(
+            {
+                "generate_sequences_ids": output,
+                "non_tensor_data": NonTensorStack(*["test_str" for _ in range(output.size(0))]),
+                "nested_tensor": torch.nested.as_nested_tensor([torch.randn(1, 2) for _ in range(output.size(0))]),
+            },
+            batch_size=output.size(0),
+        )

118-125: Same fix for async server generate().

-        output = TensorDict(
-            {
-                "generate_sequences_ids": data,
-                "non_tensor_data": torch.stack([NonTensorData("test_str") for _ in range(data.size(0))]),
-                "nested_tensor": torch.nested.as_nested_tensor([torch.randn(1, 2) for _ in range(data.size(0))]),
-            },
-            batch_size=data.size(0),
-        )
+        output = TensorDict(
+            {
+                "generate_sequences_ids": data,
+                "non_tensor_data": NonTensorStack(*["test_str" for _ in range(data.size(0))]),
+                "nested_tensor": torch.nested.as_nested_tensor([torch.randn(1, 2) for _ in range(data.size(0))]),
+            },
+            batch_size=data.size(0),
+        )
tests/test_client.py (1)

164-171: Use enum for ZMQServerInfo.role for consistency.

One instance still uses string; switch to TransferQueueRole.STORAGE.

-        self.zmq_server_info = ZMQServerInfo(
-            role="TransferQueueStorage",
+        self.zmq_server_info = ZMQServerInfo(
+            role=TransferQueueRole.STORAGE,
             id=storage_id,
             ip="127.0.0.1",
             ports={
                 "put_get_socket": self.data_port,
             },
         )
recipe/simple_use_case/sync_demo.py (1)

104-111: Fix NonTensorData stacking in sync path.

Use NonTensorStack for non-tensor fields.

-    output = TensorDict(
-        {
-            "generate_sequences_ids": output,
-            "non_tensor_data": torch.stack([NonTensorData("test_str") for _ in range(output.size(0))]),
-            "nested_tensor": torch.nested.as_nested_tensor([torch.randn(1, 2) for _ in range(output.size(0))]),
-        },
-        batch_size=output.size(0),
-    )
+    output = TensorDict(
+        {
+            "generate_sequences_ids": output,
+            "non_tensor_data": NonTensorStack(*["test_str" for _ in range(output.size(0))]),
+            "nested_tensor": torch.nested.as_nested_tensor([torch.randn(1, 2) for _ in range(output.size(0))]),
+        },
+        batch_size=output.size(0),
+    )
transfer_queue/client.py (2)

134-142: Remove redundant socket close call.

The socket cleanup contains duplicate close() calls. Line 137 already closes the socket if not closed, and line 138 attempts to close it again unconditionally, which will operate on an already-closed socket.

Apply this diff to fix the redundant close:

                 try:
                     if not sock.closed:
                         sock.setsockopt(zmq.LINGER, -1)
                         sock.close()
-                    sock.close(linger=0)
                 except Exception as e:
                     logger.warning(f"[{self.client_id}]: Error closing socket to Controller {server_info.id}: {e}")

236-307: Add existence check for storage_manager.

The method delegates to self.storage_manager.put_data() (line 303) without verifying that storage_manager was initialized. If a user forgets to call initialize_storage_manager(), this will raise an AttributeError.

Add a validation check at the beginning of the method:

     async def async_put(
         self,
         data: TensorDict,
         metadata: Optional[BatchMeta] = None,
         global_step: Optional[int] = None,
     ):
         """Asynchronously write data to storage units based on metadata.
 
         If metadata is not provided, it will be created automatically using insert mode
         with the provided data fields and global_step.
 
         Args:
             data: Data to write as TensorDict
             metadata: Records the metadata of a batch of data samples, containing index and
                       storage unit information. If None, metadata will be auto-generated.
             global_step: Current processing step (required if metadata is not provided)
 
         Raises:
             ValueError: If metadata is None or empty, or if global_step is None when metadata is not provided
             RuntimeError: If storage operation fails
 
         Example:
             >>> batch_size = 4
             >>> seq_len = 16
             >>> current_step = 0
             >>> # Example 1: Normal usage with existing metadata
             >>> batch_meta = asyncio.run(client.async_get_meta(
             ...     data_fields=["prompts", "attention_mask"],
             ...     batch_size=batch_size,
             ...     global_step=current_step,
             ...     mode="fetch",
             ...     get_n_samples=False,
             ...     task_name="generate_sequences",
             ... ))
             >>> batch = asyncio.run(client.async_get_data(batch_meta))
             >>> output = TensorDict({"response": torch.randn(batch_size, seq_len)})
             >>> asyncio.run(client.async_put(data=output, metadata=batch_meta))
             >>>
             >>> # Example 2: Initial data insertion without pre-existing metadata
             >>> # BE CAREFUL: this usage may overwrite any unconsumed data in the given global_step!
             >>> # Please make sure the corresponding global_step is empty before calling the async_put()
             >>> # without metadata.
             >>> # Now we only support put all the data of the corresponding global step in once. You should repeat with
             >>> # interleave the initial data if n_sample > 1 before calling the async_put().
             >>> original_prompts = torch.randn(batch_size, seq_len)
             >>> n_samples = 4
             >>> prompts_repeated = torch.repeat_interleave(original_prompts, n_samples, dim=0)
             >>> prompts_repeated_batch = TensorDict({"prompts": prompts_repeated})
             >>> # This will create metadata in "insert" mode internally.
             >>> asyncio.run(client.async_put(data=prompts_repeated_batch, global_step=current_step))
 
         """
+        if not hasattr(self, "storage_manager") or self.storage_manager is None:
+            raise RuntimeError(
+                f"[{self.client_id}]: Storage manager not initialized. "
+                "Call initialize_storage_manager() before performing storage operations."
+            )
+
         if metadata is None:
             assert global_step is not None, "global_steps must be provided if metadata is not given"
🧹 Nitpick comments (40)
transfer_queue/utils/utils.py (1)

106-106: Consider expanding the docstring.

The docstring is quite brief and doesn't describe parameters, return behavior, or usage examples. Consider adding more detail about when and why this should be used.

Example enhancement:

-    """Prevent PyTorch from overdoing the automatic parallelism during torch.stack() operation"""
+    """
+    Temporarily limit PyTorch's thread count to prevent excessive parallelism.
+    
+    Useful during operations like torch.stack() where PyTorch's automatic 
+    parallelism may spawn too many threads on high-core-count machines.
+    
+    Args:
+        target_num_threads: Maximum threads to use. If None, auto-determined 
+                          based on physical cores (16 if ≥16 cores, else physical core count).
+    
+    Yields:
+        None
+    
+    Raises:
+        RuntimeError: If target_num_threads exceeds logical CPU cores.
+    """
transfer_queue/storage/clients/factory.py (1)

1-13: Placeholder module: add a minimal stub or remove until used

This file only contains a license header. Either remove it for now or add a TODO and a minimal Factory interface to avoid confusion during discovery.

transfer_queue/utils/zmq_utils.py (4)

78-85: Ensure JSON-serializable role in to_dict

role is an enum; return role.value (or str(role)) to make this dict safe for JSON/log serialization.

-        return {
-            "role": self.role,
+        return {
+            "role": self.role.value,
             "id": self.id,
             "ip": self.ip,
             "ports": self.ports,
         }

86-88: Typo: class name in str

The string says ZMQSocketInfo; class is ZMQServerInfo. Fix for clarity.

-        return f"ZMQSocketInfo(role={self.role}, id={self.id}, ip={self.ip}, ports={self.ports})"
+        return f"ZMQServerInfo(role={self.role}, id={self.id}, ip={self.ip}, ports={self.ports})"

112-114: Short request_id (8 hex) increases collision risk under load

8 hex chars (~32 bits) can collide in busy systems. Prefer 16–32 hex for safety.

-            request_id=str(uuid4().hex[:8]),
+            request_id=uuid4().hex[:16],  # or omit slicing

69-76: Align get_free_port return type with ports typing

get_free_port returns an int but is annotated as str; fix typing and minor naming nits.

-def get_free_port() -> str:
-    with socket.socket() as sock:
-        sock.bind(("", 0))
-        return sock.getsockname()[1]
+def get_free_port() -> int:
+    with socket.socket() as s:
+        s.bind(("", 0))
+        return int(s.getsockname()[1])

Also ensure ZMQServerInfo.ports uses dict[str, int] (see prior comment).

Also applies to: 134-137

transfer_queue/metadata.py (4)

98-123: union() returns self but doc promises a new object/None

Doc says “New SampleMeta (None if validation fails)”; implementation mutates and returns self, and raises on validation failure. Align doc with behavior.

-        Returns:
-            New SampleMeta with unioned fields (None if validation fails)
+        Returns:
+            SampleMeta (self) with unioned fields. Raises ValueError on validation failure.

153-160: field_names cache may not represent “all unique field names”

You cache field names from the first sample but the property doc says “all unique field names”. Either compute the union across samples or update the doc.

Possible fix:

-            object.__setattr__(self, "_field_names", sorted(self.samples[0].field_names))
+            union = sorted({name for s in self.samples for name in s.field_names})
+            object.__setattr__(self, "_field_names", union)

Also applies to: 171-175


295-299: extra_info merge semantics differ from BatchMeta.union

concat uses ChainMap (first-wins), union uses dict unpacking (last-wins). Pick one policy for consistency; recommend last-wins.

-        merged_extra_info = dict(ChainMap(*(chunk.extra_info for chunk in data)))
+        merged_extra_info = {}
+        for chunk in data:
+            merged_extra_info |= chunk.extra_info  # last-wins

441-455: Prefer len(tensor_dict) for batch size; minor clarity

batch_size = tensor_dict.batch_size[0] works but len(tensor_dict) is clearer and resilient.

-    batch_size = tensor_dict.batch_size[0]
+    batch_size = len(tensor_dict)
.github/workflows/python-package.yml (1)

30-31: Use canonical package name pytest-asyncio

The canonical PyPI project name is "pytest-asyncio" (dash), even though pip normalizes both forms. Switch to the official name for clarity.

-        python -m pip install flake8 pytest build pytest_asyncio
+        python -m pip install flake8 pytest build pytest-asyncio
transfer_queue/storage/managers/__init__.py (1)

15-17: Prefer lazy re‑exports to reduce import-time cost and cycle risk

Replace eager imports with on-demand attribute loading via getattr.

Apply:

+from importlib import import_module
+
-from .base import TransferQueueStorageManager
-from .factory import TransferQueueStorageManagerFactory
-from .simple_backend_manager import AsyncSimpleStorageManager
+_EXPORTS = {
+    "TransferQueueStorageManager": (".base", "TransferQueueStorageManager"),
+    "TransferQueueStorageManagerFactory": (".factory", "TransferQueueStorageManagerFactory"),
+    "AsyncSimpleStorageManager": (".simple_backend_manager", "AsyncSimpleStorageManager"),
+}
+
+def __getattr__(name: str):
+    if name in _EXPORTS:
+        mod_path, attr = _EXPORTS[name]
+        return getattr(import_module(mod_path, __package__), attr)
+    raise AttributeError(name)
transfer_queue/storage/__init__.py (1)

15-17: Consider lazy re‑exports for the storage package, mirroring managers/init.py

Helps avoid importing heavy deps at module import time.

README.md (4)

55-56: Align terminology with code (“StorageManager” + “SimpleStorageUnit”)

Replace “written to the TransferQueueStorage” with “written to the storage backend (e.g., SimpleStorageUnit via TransferQueueStorageManager)” for consistency.


24-25: Add alt text to images for accessibility

Provide descriptive alt attributes for all tags.

Example:

-<img src=".../1761356010763-....png" width="70%">
+<img src=".../1761356010763-....png" width="70%" alt="TransferQueue architecture overview">

Also applies to: 32-33, 61-62, 125-126, 144-146, 178-179


152-174: Document test extras in Quick Start

Since pyproject defines [project.optional-dependencies].test, add:

  • pip install -e ".[test]"
  • pytest -q

10-13: Review third‑party embed

The DeepWiki badge references devin.ai assets; consider replacing with a static badge or project-hosted asset to avoid external tracking/fragility.

pyproject.toml (3)

83-86: Set pytest asyncio mode to avoid deprecation warnings

Add asyncio_mode = "auto".

 [tool.pytest.ini_options]
 filterwarnings = [
     "ignore:.*PyTorch API of nested tensors.*prototype.*:UserWarning",
 ]
+asyncio_mode = "auto"

71-79: Avoid blanket mypy ignore_errors

Prefer targeted silences; set ignore_errors = false and use per-module overrides.

-[tool.mypy]
+[tool.mypy]
 pretty            = true
 ignore_missing_imports = true
 explicit_package_bases = true
 follow_imports = "skip"
-
-# Blanket silence
-ignore_errors = true
+ignore_errors = false

110-114: Expose test extras in docs and CI

Leverage the new test extra: pip install -e ".[test]".

tests/test_async_simple_storage_manager.py (6)

25-33: Avoid sys.path hacks in tests

Rely on editable install or pytest config. Suggest pip install -e ".[test]" and remove manual path injection.

-parent_dir = Path(__file__).resolve().parent.parent
-sys.path.append(str(parent_dir))
+# Prefer: install package in editable mode for tests or configure pytest to run from repo root.

35-89: Prefer constructing the class normally vs new

Using new risks partial initialization. Patch handshake like in other tests and call AsyncSimpleStorageManager(config).


198-227: Add resource cleanup to avoid lingering ZMQ contexts

Call manager.close() in a finally or use addfinalizer.

-        manager = AsyncSimpleStorageManager(config)
+        manager = AsyncSimpleStorageManager(config)
+        try:
+            # assertions...
+            assert manager.global_index_storage_unit_mapping(0) == "storage_0"
+            # ...
+        finally:
+            manager.close()

246-301: Also close manager in error-handling test

Avoid leaking sockets/contexts across tests.

-        manager = AsyncSimpleStorageManager(config)
+        manager = AsyncSimpleStorageManager(config)
+        try:
+            # mocked operations and assertions...
+            pass
+        finally:
+            manager.close()

147-150: Strengthen assertions

Assert notify_data_update arguments and that _put/_get/_clear were called with expected shard targets.

Example:

manager.notify_data_update.assert_awaited_once()
manager._put_to_single_storage_unit.assert_awaited()
calls = [c.kwargs.get("target_storage_unit") for c in manager._put_to_single_storage_unit.await_args_list]
assert set(calls) == {"storage_0", "storage_1"}

Also applies to: 151-157


1-223: Sanity-check: pickle over ZMQ

ZMQMessage uses pickle serialization; ensure tests cover round-trip for nontrivial payloads and consider documenting the security trade-offs.

tests/test_controller.py (3)

26-27: Avoid importing internals in tests unless necessary

Importing TQ_INIT_FIELD_NUM from transfer_queue.controller couples tests to internals. Prefer asserting behaviors via public APIs or expose a stable constant through the package root if needed.

If TQ_INIT_FIELD_NUM must be asserted, consider re-exporting it in transfer_queue/init.py.


97-129: Add invariant checks to strengthen metadata test

The reorder/global_indexes assertions look right. Add simple invariants to catch regressions (size and field_names unchanged by reorder).

     metadata = ray.get(
         tq_controller._get_metadata.remote(
             data_fields=data_fields,
             batch_size=global_batch_size * n_samples,
             global_step=global_step,
             mode="insert",
         )
     )
+    # Invariants
+    assert metadata.size == global_batch_size * n_samples
+    assert metadata.field_names == sorted(data_fields)
     metadata.reorder([15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0])

65-84: Tests rely on private controller methods—consider adding public accessors or test helper methods

Both _update_production_status (line 78) and _get_consumption_status (line 93) are internal methods that lack public alternatives. While the .remote() calls are valid (TransferQueueController is properly decorated with @ray.remote), testing through private methods risks breakage during future refactors. Either add public wrapper methods for testing, create dedicated test helper methods in the controller, or add comments explaining why private access is necessary.

transfer_queue/storage/managers/factory.py (1)

25-41: Harden factory: normalize keys, guard duplicates, and default to class name

Make manager_type case-insensitive, prevent accidental overwrite, and use the class name by default during registration.

@@
-    def register(cls, manager_type: str, manager_cls: type[TransferQueueStorageManager]):
+    def register(cls, manager_type: str | None, manager_cls: type[TransferQueueStorageManager]):
         if not issubclass(manager_cls, TransferQueueStorageManager):
             raise TypeError(
                 f"manager_cls {getattr(manager_cls, '__name__', repr(manager_cls))} must be "
                 f"a subclass of TransferQueueStorageManager"
             )
-        cls._registry[manager_type] = manager_cls
+        key = (manager_type or manager_cls.__name__).lower()
+        if key in cls._registry and cls._registry[key] is not manager_cls:
+            raise ValueError(f"Duplicate registration for manager_type '{key}'")
+        cls._registry[key] = manager_cls
@@
-    def create(cls, manager_type: str, config: dict[str, Any]) -> TransferQueueStorageManager:
-        if manager_type not in cls._registry:
+    def create(cls, manager_type: str, config: dict[str, Any]) -> TransferQueueStorageManager:
+        key = manager_type.lower()
+        if key not in cls._registry:
             raise ValueError(
-                f"Unknown manager_type: {manager_type}. Supported managers include: {list(cls._registry.keys())}"
+                f"Unknown manager_type: {manager_type}. Supported: {list(cls._registry.keys())}"
             )
-        return cls._registry[manager_type](config)
+        return cls._registry[key](config)
@@
-TransferQueueStorageManagerFactory.register("AsyncSimpleStorageManager", AsyncSimpleStorageManager)
+TransferQueueStorageManagerFactory.register(None, AsyncSimpleStorageManager)

Also applies to: 43-44

tests/test_simple_storage_unit.py (3)

34-51: Set SNDTIMEO and client identity for stability and debuggability

Avoid indefinite send on backpressure and add an identity to ease ROUTER-side tracing.

 class MockStorageClient:
@@
-    def __init__(self, storage_put_get_address):
+    def __init__(self, storage_put_get_address, identity: str | None = None):
         self.context = zmq.Context()
-        self.socket = self.context.socket(zmq.DEALER)
-        self.socket.setsockopt(zmq.RCVTIMEO, 5000)  # 5 second timeout
+        self.socket = self.context.socket(zmq.DEALER)
+        if identity:
+            self.socket.setsockopt(zmq.IDENTITY, identity.encode())
+        self.socket.setsockopt(zmq.RCVTIMEO, 5000)  # 5s
+        self.socket.setsockopt(zmq.SNDTIMEO, 5000)  # 5s
         self.socket.connect(storage_put_get_address)

92-99: Replace fixed sleep with readiness probe to reduce flakiness

Actively probe the ROUTER by issuing a harmless CLEAR with empty indexes; retry a few times instead of sleeping.

-    put_get_address = zmq_info.to_addr("put_get_socket")
-    time.sleep(1)  # Wait for socket to be ready
+    put_get_address = zmq_info.to_addr("put_get_socket")
+    # Probe readiness: send a no-op CLEAR ([]) with short timeout
+    ready = False
+    for attempt in range(10):
+        try:
+            client = MockStorageClient(put_get_address, identity=f"probe_{attempt}")
+            resp = client.send_clear("probe", [])
+            # Accept either CLEAR_DATA_RESPONSE or CLEAR_DATA_ERROR (server up)
+            if resp.request_type in {ZMQRequestType.CLEAR_DATA_RESPONSE, ZMQRequestType.CLEAR_DATA_ERROR}:
+                ready = True
+                client.close()
+                break
+        except Exception:
+            time.sleep(0.1)
+    assert ready, "StorageUnit put_get_socket not ready in time"

202-256: Gate performance test to avoid CI flakiness

Mark as slow and allow opt-in via env var to keep PR signal clean.

-def test_performance_basic(storage_setup):
+@pytest.mark.slow
+def test_performance_basic(storage_setup):
@@
-    assert avg_put_latency < 1500, f"Avg PUT latency {avg_put_latency}ms exceeds threshold"
-    assert avg_get_latency < 1500, f"Avg GET latency {avg_get_latency}ms exceeds threshold"
+    if os.getenv("TQ_RUN_PERF_TESTS", "0") == "1":
+        assert avg_put_latency < 1500, f"Avg PUT latency {avg_put_latency}ms exceeds threshold"
+        assert avg_get_latency < 1500, f"Avg GET latency {avg_get_latency}ms exceeds threshold"
transfer_queue/storage/managers/base.py (2)

65-76: Set DEALER socket timeouts to avoid accidental indefinite blocks

Add conservative SNDTIMEO/RCVTIMEO on controller_handshake_socket and data_status_update_socket.

             self.controller_handshake_socket = create_zmq_socket(
                 self.zmq_context,
                 zmq.DEALER,
                 identity=f"{self.storage_manager_id}-controller_handshake_socket-{uuid4().hex[:8]}".encode(),
             )
+            self.controller_handshake_socket.setsockopt(zmq.RCVTIMEO, 10_000)
+            self.controller_handshake_socket.setsockopt(zmq.SNDTIMEO, 10_000)
             self.data_status_update_socket = create_zmq_socket(
                 self.zmq_context,
                 zmq.DEALER,
                 identity=f"{self.storage_manager_id}-data_status_update_socket-{uuid4().hex[:8]}".encode(),
             )
+            self.data_status_update_socket.setsockopt(zmq.RCVTIMEO, 10_000)
+            self.data_status_update_socket.setsockopt(zmq.SNDTIMEO, 10_000)
             self.data_status_update_socket.connect(self.controller_info.to_addr("data_status_update_socket"))

218-230: Don’t wait for ACK after emitting NOTIFY_DATA_UPDATE_ERROR

If sending the update fails, you emit an error message and still wait for an ACK, which will likely never arrive. Return early.

         except Exception as e:
             request_msg = ZMQMessage.create(
                 request_type=ZMQRequestType.NOTIFY_DATA_UPDATE_ERROR,
                 sender_id=self.storage_manager_id,
                 body={
                     "message": f"Failed to notify data status update information from "
                     f"storage manager id #{self.storage_manager_id}, "
                     f"detail error message: {str(e)}"
                 },
             ).serialize()
 
             self.data_status_update_socket.send(request_msg)
+            return  # Avoid waiting for ACK after error
 
         # Make sure controller successfully receives data status update information.

Also applies to: 252-257

transfer_queue/storage/managers/simple_backend_manager.py (1)

322-325: Reduce log verbosity for GET responses

Logging entire response bodies at info can be noisy and expensive; use debug or log minimal identifiers.

-            logger.info(
-                f"[{self.storage_manager_id}]: get data response from storage unit "
-                f"{target_storage_unit}: {response_msg}"
-            )
+            logger.debug(
+                "[%s]: GET response from %s (type=%s, size=%s)",
+                self.storage_manager_id,
+                target_storage_unit,
+                getattr(response_msg, "request_type", None),
+                len(getattr(response_msg.body, "get", lambda *_: [])("data", {})) if hasattr(response_msg, "body") else None,
+            )
transfer_queue/storage/simple_backend.py (3)

178-185: Add graceful shutdown; set socket LINGER and close Context.

Provide close() to avoid resource leaks and hanging ROUTER sockets on actor teardown.

@@ class SimpleStorageUnit:
     def _init_zmq_socket(self) -> None:
@@
         self.put_get_socket = create_zmq_socket(self.zmq_context, zmq.ROUTER)
         self.put_get_socket.bind(self.zmq_server_info.to_addr("put_get_socket"))
+
+    def close(self) -> None:
+        try:
+            if hasattr(self, "put_get_socket") and not self.put_get_socket.closed:
+                self.put_get_socket.setsockopt(zmq.LINGER, 0)
+                self.put_get_socket.close()
+        finally:
+            if hasattr(self, "zmq_context"):
+                self.zmq_context.term()
@@
     def get_zmq_server_info(self) -> ZMQServerInfo:

Also applies to: 195-197, 345-352


178-183: Port type inconsistency.

ZMQServerInfo.ports is typed dict[str, str], but get_free_port returns int; you pass ints. Either cast to str here or update the annotation to int.

-            ports={"put_get_socket": get_free_port()},
+            ports={"put_get_socket": str(get_free_port())},

382-389: Deterministic field order for reproducibility.

get_field_names returns an arbitrary set order. Sort to stabilize downstream behavior and tests.

-        return list(all_fields)
+        return sorted(all_fields)
transfer_queue/controller.py (1)

512-521: Ports typing consistency.

As with storage, cast port ints to str or change ZMQServerInfo annotation.

-                "handshake_socket": self._handshake_socket_port,
-                "request_handle_socket": self._request_handle_socket_port,
-                "data_status_update_socket": self._data_status_update_socket_port,
+                "handshake_socket": str(self._handshake_socket_port),
+                "request_handle_socket": str(self._request_handle_socket_port),
+                "data_status_update_socket": str(self._data_status_update_socket_port),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bed8ce3 and 3332482.

📒 Files selected for processing (25)
  • .github/workflows/python-package.yml (1 hunks)
  • README.md (3 hunks)
  • pyproject.toml (3 hunks)
  • recipe/simple_use_case/async_demo.py (7 hunks)
  • recipe/simple_use_case/sync_demo.py (5 hunks)
  • requirements.txt (1 hunks)
  • tests/test_async_simple_storage_manager.py (1 hunks)
  • tests/test_client.py (10 hunks)
  • tests/test_controller.py (2 hunks)
  • tests/test_simple_storage_unit.py (12 hunks)
  • transfer_queue/__init__.py (2 hunks)
  • transfer_queue/client.py (11 hunks)
  • transfer_queue/controller.py (6 hunks)
  • transfer_queue/metadata.py (7 hunks)
  • transfer_queue/storage.py (0 hunks)
  • transfer_queue/storage/__init__.py (1 hunks)
  • transfer_queue/storage/clients/__init__.py (1 hunks)
  • transfer_queue/storage/clients/factory.py (1 hunks)
  • transfer_queue/storage/managers/__init__.py (1 hunks)
  • transfer_queue/storage/managers/base.py (1 hunks)
  • transfer_queue/storage/managers/factory.py (1 hunks)
  • transfer_queue/storage/managers/simple_backend_manager.py (1 hunks)
  • transfer_queue/storage/simple_backend.py (1 hunks)
  • transfer_queue/utils/utils.py (2 hunks)
  • transfer_queue/utils/zmq_utils.py (3 hunks)
💤 Files with no reviewable changes (1)
  • transfer_queue/storage.py
🧰 Additional context used
🧬 Code graph analysis (17)
transfer_queue/storage/managers/__init__.py (3)
transfer_queue/storage/managers/base.py (1)
  • TransferQueueStorageManager (40-268)
transfer_queue/storage/managers/factory.py (1)
  • TransferQueueStorageManagerFactory (20-40)
transfer_queue/storage/managers/simple_backend_manager.py (1)
  • AsyncSimpleStorageManager (37-415)
tests/test_controller.py (1)
transfer_queue/controller.py (1)
  • TransferQueueController (55-750)
tests/test_async_simple_storage_manager.py (5)
transfer_queue/metadata.py (3)
  • BatchMeta (136-417)
  • FieldMeta (28-45)
  • SampleMeta (49-132)
transfer_queue/storage/managers/simple_backend_manager.py (7)
  • AsyncSimpleStorageManager (37-415)
  • _put_to_single_storage_unit (205-241)
  • _get_from_single_storage_unit (306-337)
  • _clear_single_storage_unit (366-388)
  • put_data (163-202)
  • get_data (243-303)
  • clear_data (339-363)
transfer_queue/utils/utils.py (1)
  • TransferQueueRole (36-39)
transfer_queue/utils/zmq_utils.py (5)
  • ZMQMessage (91-131)
  • ZMQRequestType (31-65)
  • ZMQServerInfo (68-87)
  • create (100-114)
  • serialize (116-118)
transfer_queue/storage/managers/base.py (5)
  • _connect_to_controller (55-82)
  • notify_data_update (172-256)
  • put_data (259-260)
  • get_data (263-264)
  • clear_data (267-268)
transfer_queue/storage/__init__.py (4)
transfer_queue/storage/managers/simple_backend_manager.py (1)
  • AsyncSimpleStorageManager (37-415)
transfer_queue/storage/managers/base.py (1)
  • TransferQueueStorageManager (40-268)
transfer_queue/storage/managers/factory.py (1)
  • TransferQueueStorageManagerFactory (20-40)
transfer_queue/storage/simple_backend.py (3)
  • SimpleStorageUnit (150-351)
  • StorageMetaGroup (355-449)
  • StorageUnitData (41-146)
transfer_queue/storage/managers/base.py (5)
transfer_queue/metadata.py (2)
  • BatchMeta (136-417)
  • global_indexes (167-169)
transfer_queue/utils/zmq_utils.py (8)
  • ZMQMessage (91-131)
  • ZMQRequestType (31-65)
  • ZMQServerInfo (68-87)
  • create_zmq_socket (140-170)
  • to_addr (75-76)
  • deserialize (121-131)
  • create (100-114)
  • serialize (116-118)
transfer_queue/storage/managers/factory.py (2)
  • register (26-32)
  • create (35-40)
transfer_queue/storage/managers/simple_backend_manager.py (3)
  • put_data (163-202)
  • get_data (243-303)
  • clear_data (339-363)
transfer_queue/storage/simple_backend.py (2)
  • put_data (105-126)
  • get_data (66-103)
transfer_queue/storage/managers/factory.py (3)
transfer_queue/storage/managers/base.py (1)
  • TransferQueueStorageManager (40-268)
transfer_queue/storage/managers/simple_backend_manager.py (1)
  • AsyncSimpleStorageManager (37-415)
transfer_queue/utils/zmq_utils.py (1)
  • create (100-114)
transfer_queue/storage/simple_backend.py (3)
transfer_queue/metadata.py (2)
  • SampleMeta (49-132)
  • batch_index (70-72)
transfer_queue/utils/utils.py (1)
  • TransferQueueRole (36-39)
transfer_queue/utils/zmq_utils.py (9)
  • ZMQMessage (91-131)
  • ZMQRequestType (31-65)
  • ZMQServerInfo (68-87)
  • create_zmq_socket (140-170)
  • get_free_port (134-137)
  • to_addr (75-76)
  • deserialize (121-131)
  • create (100-114)
  • serialize (116-118)
transfer_queue/storage/managers/simple_backend_manager.py (5)
transfer_queue/metadata.py (3)
  • BatchMeta (136-417)
  • global_indexes (167-169)
  • batch_index (70-72)
transfer_queue/storage/managers/base.py (5)
  • TransferQueueStorageManager (40-268)
  • put_data (259-260)
  • notify_data_update (172-256)
  • get_data (263-264)
  • clear_data (267-268)
transfer_queue/storage/simple_backend.py (5)
  • StorageMetaGroup (355-449)
  • put_data (105-126)
  • get_transfer_data (389-428)
  • get_data (66-103)
  • add_sample_meta (365-368)
transfer_queue/utils/utils.py (1)
  • limit_pytorch_auto_parallel_threads (105-132)
transfer_queue/utils/zmq_utils.py (7)
  • ZMQMessage (91-131)
  • ZMQRequestType (31-65)
  • ZMQServerInfo (68-87)
  • create_zmq_socket (140-170)
  • create (100-114)
  • serialize (116-118)
  • deserialize (121-131)
transfer_queue/utils/zmq_utils.py (1)
transfer_queue/utils/utils.py (1)
  • TransferQueueRole (36-39)
tests/test_simple_storage_unit.py (2)
transfer_queue/storage/simple_backend.py (2)
  • SimpleStorageUnit (150-351)
  • StorageUnitData (41-146)
transfer_queue/utils/zmq_utils.py (6)
  • ZMQMessage (91-131)
  • ZMQRequestType (31-65)
  • create (100-114)
  • serialize (116-118)
  • deserialize (121-131)
  • to_addr (75-76)
transfer_queue/__init__.py (1)
transfer_queue/storage/simple_backend.py (1)
  • SimpleStorageUnit (150-351)
tests/test_client.py (4)
transfer_queue/utils/utils.py (1)
  • TransferQueueRole (36-39)
transfer_queue/utils/zmq_utils.py (1)
  • ZMQServerInfo (68-87)
transfer_queue/client.py (3)
  • initialize_storage_manager (73-89)
  • get_data (527-536)
  • TransferQueueClient (465-544)
transfer_queue/storage/managers/base.py (3)
  • put_data (259-260)
  • get_data (263-264)
  • clear_data (267-268)
transfer_queue/controller.py (1)
transfer_queue/utils/zmq_utils.py (6)
  • ZMQServerInfo (68-87)
  • ZMQMessage (91-131)
  • deserialize (121-131)
  • ZMQRequestType (31-65)
  • create (100-114)
  • serialize (116-118)
transfer_queue/client.py (7)
transfer_queue/controller.py (2)
  • clear (737-750)
  • get_zmq_server_info (729-735)
transfer_queue/metadata.py (2)
  • BatchMeta (136-417)
  • size (162-164)
transfer_queue/storage/simple_backend.py (5)
  • put_data (105-126)
  • size (431-433)
  • get_data (66-103)
  • clear (128-146)
  • get_zmq_server_info (345-351)
transfer_queue/storage/managers/base.py (4)
  • TransferQueueStorageManager (40-268)
  • put_data (259-260)
  • get_data (263-264)
  • clear_data (267-268)
transfer_queue/storage/managers/factory.py (2)
  • TransferQueueStorageManagerFactory (20-40)
  • create (35-40)
transfer_queue/utils/zmq_utils.py (5)
  • ZMQServerInfo (68-87)
  • create (100-114)
  • create_zmq_socket (140-170)
  • ZMQMessage (91-131)
  • ZMQRequestType (31-65)
transfer_queue/storage/managers/simple_backend_manager.py (7)
  • decorator (110-159)
  • wrapper (112-157)
  • put_data (163-202)
  • get_data (243-303)
  • clear_data (339-363)
  • close (398-415)
  • get_zmq_server_info (390-396)
recipe/simple_use_case/sync_demo.py (3)
transfer_queue/storage/simple_backend.py (1)
  • SimpleStorageUnit (150-351)
transfer_queue/client.py (4)
  • TransferQueueClient (465-544)
  • process_zmq_server_info (547-577)
  • initialize_storage_manager (73-89)
  • close (455-462)
transfer_queue/utils/utils.py (1)
  • get_placement_group (49-63)
transfer_queue/metadata.py (1)
transfer_queue/utils/utils.py (1)
  • ProductionStatus (43-46)
recipe/simple_use_case/async_demo.py (3)
transfer_queue/storage/simple_backend.py (1)
  • SimpleStorageUnit (150-351)
transfer_queue/client.py (5)
  • AsyncTransferQueueClient (47-462)
  • initialize_storage_manager (73-89)
  • process_zmq_server_info (547-577)
  • async_clear (340-363)
  • close (455-462)
transfer_queue/utils/utils.py (1)
  • get_placement_group (49-63)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Cleanup artifacts
  • GitHub Check: pre-commit (3.10)
  • GitHub Check: build (3.11)
  • GitHub Check: build (3.11)
🔇 Additional comments (19)
transfer_queue/storage/clients/__init__.py (1)

1-15: LGTM!

This package initializer with the Apache 2.0 license header is appropriately structured for the new transfer_queue/storage/clients subpackage introduced as part of the storage architecture refactoring. The placeholder comment correctly indicates this is reserved for future client implementations.

transfer_queue/utils/utils.py (1)

15-15: LGTM!

The imports are appropriate for the new context manager functionality.

Also applies to: 17-17, 19-19

requirements.txt (1)

7-7: LGTM! The psutil dependency is secure.

The security advisory check found a historical "Double Free in psutil" vulnerability (HIGH severity) affecting versions <= 5.6.5, which was patched in version 5.6.6 (March 2020). Since no version is pinned, pip will install the latest stable version of psutil, which is well beyond the vulnerable range and includes the security patch.

transfer_queue/__init__.py (1)

24-25: Storage package properly exports SimpleStorageUnit—verification passed.

The import at transfer_queue/init.py:24 is correctly supported: transfer_queue/storage/init.py re-exports SimpleStorageUnit from simple_backend.py and includes it in all. Public API is stable.

transfer_queue/storage/managers/__init__.py (1)

19-23: Public surface looks good

all matches the intended API.

transfer_queue/storage/__init__.py (1)

18-25: API consolidation LGTM

Exports are coherent and discoverable.

README.md (1)

69-72: Good clarity on the new storage abstraction

The Data Plane section accurately reflects TransferQueueStorageManager and AsyncSimpleStorageManager.

Also applies to: 85-86

tests/test_async_simple_storage_manager.py (1)

1-223: Stale symbol verification passed

Confirmed: No references to old symbol names (TransferQueueStorageSimpleUnit, TransferQueueStorageSimple, TQStorageSimple) found in the codebase. Rename was completed cleanly without broken imports.

pyproject.toml (1)

105-109: Files verified and correctly configured

Both transfer_queue/version/version and requirements.txt exist at the paths specified in pyproject.toml:

  • Version file contains valid version string: 0.0.1.dev
  • Requirements file contains valid dependencies
  • Both files are part of the package source tree and will be included in builds

The dynamic version and dependencies configuration is correct.

recipe/simple_use_case/sync_demo.py (1)

166-173: Same fix for compute_old_log_prob path.

-    output = TensorDict({"old_log_prob": output}, batch_size=output.size(0))
+    output = TensorDict({"old_log_prob": output}, batch_size=output.size(0))

Note: No change needed here; included to confirm NonTensorStack not required in this path. If other non-tensor fields are added later, prefer NonTensorStack.

transfer_queue/client.py (9)

25-35: LGTM!

The import updates correctly reflect the new storage manager architecture, including SimpleStorageUnit, TransferQueueStorageManager, and TransferQueueStorageManagerFactory.


54-71: LGTM!

The constructor correctly implements the single-controller architecture with proper validation of the controller_info parameter.


73-89: LGTM!

The storage manager initialization correctly uses the factory pattern. However, note that this method is optional, which means self.storage_manager may not exist when storage operations are called (see related comments on Lines 303, 336, 359).


148-234: LGTM!

The method correctly routes metadata requests to the single controller, and the comprehensive docstring with examples greatly improves usability.


365-431: LGTM!

The private helper methods for clearing operations are well-structured and correctly use the dynamic_socket decorator for controller communication.


433-453: LGTM!

The placeholder methods are appropriately marked with TODO comments and have the necessary decorator in place for future implementation.


455-462: LGTM!

The close() method provides proper resource cleanup with appropriate error handling to prevent resource leaks.


465-544: LGTM!

The synchronous wrapper class correctly delegates to async methods and maintains consistency with the single-controller architecture. Docstrings are clear and helpful.


547-577: LGTM!

The utility function correctly handles both single handler and dictionary cases, with appropriate type hints reflecting the expanded set of supported handler types.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread tests/test_client.py Resolved

Uh oh!

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

Comment thread tests/test_client.py Resolved

Uh oh!

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

Comment thread transfer_queue/client.py Resolved

Uh oh!

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

Comment thread transfer_queue/client.py Resolved

Uh oh!

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

Comment thread transfer_queue/controller.py
Comment on lines 523 to 579
def _wait_connection(self):
"""Wait for all storage instances to complete handshake.
"""
Wait for storage instances to complete handshake with retransmission support.

This method runs continuously in a dedicated thread to handle handshake responses for
new TransferQueueStorageManager connections. It listens for handshake requests from
storage managers and responds with acknowledgments, ensuring that new storage units can
connect and be tracked even after the initial startup phase.

Clients don't need handshake to support dynamic scaling. Continuously
listens for handshake messages until all expected storage units connect.
The method includes a retransmission mechanism - if a storage manager resends a
HANDSHAKE message (e.g., due to network issues or timeout), the controller will
detect it as a duplicate and resend the ACK response without treating it as a new
connection.
"""
# TODO(zjj): Consider if retransmission is needed (assuming cases where Storage doesn't receive ACK)
connected_storage_units = set()
while len(connected_storage_units) < self.num_storage_units:
identity, serialized_msg = self.handshake_socket.recv_multipart()
request_msg = ZMQMessage.deserialize(serialized_msg)
if request_msg.request_type == ZMQRequestType.HANDSHAKE:
connected_storage_units.add(request_msg.sender_id)
response_msg = ZMQMessage.create(
request_type=ZMQRequestType.HANDSHAKE_ACK,
sender_id=self.controller_id,
body={},
).serialize()
self.handshake_socket.send_multipart([identity, response_msg])
logger.info("Controller sent handshake ack successfully!")
self.global_index_storage_id_mapping = np.array(sorted(list(connected_storage_units)))[
self._global_index_storage_rank_mapping
]
self.handshake_done.set()
poller = zmq.Poller()
poller.register(self.handshake_socket, zmq.POLLIN)

logger.info(f"Controller {self.controller_id} started waiting for storage connections...")

while True:
# Wait for messages with timeout
socks = dict(poller.poll(TQ_CONTROLLER_CONNECTION_CHECK_INTERVAL * 1000))

if self.handshake_socket in socks:
try:
identity, serialized_msg = self.handshake_socket.recv_multipart()
request_msg = ZMQMessage.deserialize(serialized_msg)

if request_msg.request_type == ZMQRequestType.HANDSHAKE:
storage_manager_id = request_msg.sender_id

# Always send ACK for HANDSHAKE (handles retransmission)
response_msg = ZMQMessage.create(
request_type=ZMQRequestType.HANDSHAKE_ACK,
sender_id=self.controller_id,
body={},
).serialize()
self.handshake_socket.send_multipart([identity, response_msg])

# Track new connections
if storage_manager_id not in self._connected_storage_managers:
self._connected_storage_managers.add(storage_manager_id)
storage_manager_type = request_msg.body.get("storage_manager_type", "Unknown")
logger.info(
f"Controller {self.controller_id} received handshake from "
f"storage manager {storage_manager_id} (type: {storage_manager_type}). "
f"Total connected: {len(self._connected_storage_managers)}"
)
else:
logger.debug(
f"Controller {self.controller_id} received duplicate handshake from "
f"storage manager {storage_manager_id}. Resending ACK."
)

except Exception as e:
logger.error(f"Controller {self.controller_id} error processing handshake: {e}")

@coderabbitai coderabbitai Bot Oct 29, 2025

Copy link
Copy Markdown

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.

⚠️ Potential issue | 🟠 Major

Harden handshake against spoofing and pickle risks.

Handshake path accepts arbitrary pickled payloads over network. Add message authentication (e.g., HMAC), consider CURVE auth, and switch away from pickle if possible.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread transfer_queue/storage/simple_backend.py
Comment on lines +86 to +96
if len(local_indexes) == 1:
# The unsqueeze op make the shape from n to (1, n)
gathered_item = self.field_data[field][local_indexes[0]]
if not isinstance(gathered_item, torch.Tensor):
result[field] = NonTensorStack(gathered_item)
else:
result[field] = gathered_item.unsqueeze(0)
else:
gathered_items = list(itemgetter(*local_indexes)(self.field_data[field]))

if gathered_items:

@coderabbitai coderabbitai Bot Oct 29, 2025

Copy link
Copy Markdown

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.

🛠️ Refactor suggestion | 🟠 Major

Validate local_indexes on GET; guard None slots.

get_data doesn’t bound-check local_indexes; accessing unset slots may return None and break stacking.

@@ def get_data(self, fields: list[str], local_indexes: list[int]) -> TensorDict[str, list]:
-        for field in fields:
+        # Validate indexes once
+        for idx in local_indexes:
+            if idx < 0 or idx >= self.storage_size:
+                raise ValueError(
+                    f"StorageUnitData get_data operation receive invalid local_index: {idx} beyond "
+                    f"storage_size: {self.storage_size}"
+                )
+        for field in fields:
@@
-                gathered_item = self.field_data[field][local_indexes[0]]
+                gathered_item = self.field_data[field][local_indexes[0]]
                 if not isinstance(gathered_item, torch.Tensor):
-                    result[field] = NonTensorStack(gathered_item)
+                    result[field] = NonTensorStack(gathered_item)  # single element
                 else:
                     result[field] = gathered_item.unsqueeze(0)
@@
-                if gathered_items:
+                if gathered_items:
+                    # Filter out None slots defensively
+                    if any(x is None for x in gathered_items):
+                        raise ValueError(f"Field '{field}' contains empty slots at {local_indexes}")
                     all_tensors = all(isinstance(x, torch.Tensor) for x in gathered_items)

Also applies to: 129-147

🤖 Prompt for AI Agents
In transfer_queue/storage/simple_backend.py around lines 86-96 (and similarly
for 129-147), validate local_indexes before using them: ensure every index is an
int within bounds of self.field_data[field] (raise IndexError if any index is
out-of-range), and when gathering entries skip or filter out slots that are None
so stacking operations don't receive None values; then build result[field] from
the filtered items (use NonTensorStack for non-tensor items and
torch.stack/unsqueeze for tensors) and handle the case where all selected slots
are None by returning an appropriate empty value or raising a clear error.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread transfer_queue/storage/simple_backend.py
Comment on lines +115 to +127
for f, values in extracted_data.items():
if f not in self.field_data:
self.field_data[f] = [None] * self.storage_size

for i, idx in enumerate(local_indexes):
if idx < 0 or idx >= self.storage_size:
raise ValueError(
f"StorageUnitData put_data operation receive invalid local_index: {idx} beyond "
f"storage_size: {self.storage_size}"
)

self.field_data[f][idx] = values[i]

@coderabbitai coderabbitai Bot Oct 29, 2025

Copy link
Copy Markdown

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.

⚠️ Potential issue | 🔴 Critical

Prevent length mismatch and index errors in put_data.

When values length ≠ local_indexes length, IndexError/incorrect writes occur. Also avoid enumerate-coupling and reuse the same index validation.

Apply:

@@
-        for f, values in extracted_data.items():
+        for f, values in extracted_data.items():
             if f not in self.field_data:
                 self.field_data[f] = [None] * self.storage_size
 
-            for i, idx in enumerate(local_indexes):
+            if len(values) != len(local_indexes):
+                raise ValueError(
+                    f"put_data length mismatch for field '{f}': "
+                    f"{len(values)} values for {len(local_indexes)} indexes"
+                )
+            for idx, value in zip(local_indexes, values):
                 if idx < 0 or idx >= self.storage_size:
                     raise ValueError(
                         f"StorageUnitData put_data operation receive invalid local_index: {idx} beyond "
                         f"storage_size: {self.storage_size}"
                     )
 
-                self.field_data[f][idx] = values[i]
+                self.field_data[f][idx] = value
🤖 Prompt for AI Agents
In transfer_queue/storage/simple_backend.py around lines 115 to 127, guard
against mismatched lengths and unsafe indexing in put_data by first verifying
that len(values) == len(local_indexes) and raising a clear ValueError if not;
then iterate over pairs using zip(local_indexes, values) (not enumerate) so each
value is matched to its index, validate each idx is within [0,
self.storage_size) before assignment, and perform the assignment into
self.field_data[f][idx] only after the bounds check (create the field list if
missing as currently done).

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread transfer_queue/storage/simple_backend.py
Comment on lines +216 to +246
try:
request_msg = ZMQMessage.deserialize(serialized_msg)
operation = request_msg.request_type
logger.debug(f"[{self.zmq_server_info.id}]: receive operation: {operation}, message: {request_msg}")

if operation == ZMQRequestType.PUT_DATA:
response_msg = self._handle_put(request_msg)
elif operation == ZMQRequestType.GET_DATA:
response_msg = self._handle_get(request_msg)
elif operation == ZMQRequestType.CLEAR_DATA:
response_msg = self._handle_clear(request_msg)
else:
response_msg = ZMQMessage.create(
request_type=ZMQRequestType.PUT_GET_OPERATION_ERROR,
sender_id=self.zmq_server_info.id,
body={
"message": f"Storage unit id #{self.zmq_server_info.id} "
f"receive invalid operation: {operation}."
},
)
except Exception as e:
response_msg = ZMQMessage.create(
request_type=ZMQRequestType.PUT_GET_ERROR,
sender_id=self.zmq_server_info.id,
body={
"message": f"Storage unit id #{self.zmq_server_info.id} occur error in processing "
f"put/get/clear request, detail error message: {str(e)}."
},
)

self.put_get_socket.send_multipart([identity, response_msg.serialize()])

@coderabbitai coderabbitai Bot Oct 29, 2025

Copy link
Copy Markdown

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.

⚠️ Potential issue | 🟠 Major

Unsafe network deserialization via pickle.

Deserializing pickled messages from sockets enables code execution if an attacker can connect. Consider msgpack/JSON + schema validation; if pickle is unavoidable, restrict bind addresses, add ZMQ CURVE/auth, and authenticate messages (HMAC).

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread transfer_queue/utils/utils.py
Comment on lines +104 to +132
@contextmanager
def limit_pytorch_auto_parallel_threads(target_num_threads: Optional[int] = None):
"""Prevent PyTorch from overdoing the automatic parallelism during torch.stack() operation"""
pytorch_current_num_threads = torch.get_num_threads()
logical_cores = psutil.cpu_count(logical=True)
physical_cores = psutil.cpu_count(logical=False)

if target_num_threads is None:
# auto determine target_num_threads
if physical_cores >= 16:
target_num_threads = 16
else:
target_num_threads = physical_cores

if target_num_threads > logical_cores:
raise RuntimeError(
f"target_num_threads {target_num_threads} should not exceed total logical CPU cores {logical_cores}"
)

if pytorch_current_num_threads <= target_num_threads:
# No need to change settings
yield
else:
torch.set_num_threads(target_num_threads)
try:
yield
finally:
# Restore the original number of threads
torch.set_num_threads(pytorch_current_num_threads)

@coderabbitai coderabbitai Bot Oct 29, 2025

Copy link
Copy Markdown

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.

⚠️ Potential issue | 🔴 Critical

Critical: Handle potential None values from psutil.cpu_count().

The function has a critical bug: psutil.cpu_count() can return None on systems where the CPU count cannot be determined. This will cause runtime errors at lines 113, 116, and 118 when performing comparisons or arithmetic with None.

Apply this diff to handle None values safely:

 @contextmanager
 def limit_pytorch_auto_parallel_threads(target_num_threads: Optional[int] = None):
     """Prevent PyTorch from overdoing the automatic parallelism during torch.stack() operation"""
     pytorch_current_num_threads = torch.get_num_threads()
     logical_cores = psutil.cpu_count(logical=True)
     physical_cores = psutil.cpu_count(logical=False)
+    
+    if logical_cores is None or physical_cores is None:
+        raise RuntimeError(
+            "Unable to determine CPU core count on this system"
+        )
 
     if target_num_threads is None:
         # auto determine target_num_threads
         if physical_cores >= 16:
             target_num_threads = 16
         else:
             target_num_threads = physical_cores
 
     if target_num_threads > logical_cores:
         raise RuntimeError(
             f"target_num_threads {target_num_threads} should not exceed total logical CPU cores {logical_cores}"
         )
🤖 Prompt for AI Agents
In transfer_queue/utils/utils.py around lines 104 to 132,
psutil.cpu_count(logical=True/False) can return None which will break the
comparisons and arithmetic; coalesce None to safe integer fallbacks (e.g., use
torch.get_num_threads() or 1 for logical_cores, and for physical_cores use the
logical_cores fallback if None), ensure target_num_threads is always an int
before comparing or assigning (use min/max as appropriate), and update the
check/assignment logic so you never compare None to an int and always restore
the original torch thread count in the finally block.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread transfer_queue/utils/zmq_utils.py
Comment on lines +69 to 74
def __init__(self, role: TransferQueueRole, id: str, ip: str, ports: dict[str, str]):
self.role = role
self.id = id
self.ip = ip
self.ports = ports

@coderabbitai coderabbitai Bot Oct 29, 2025

Copy link
Copy Markdown

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.

⚠️ Potential issue | 🟠 Major

Port type mismatch: ZMQServerInfo.ports expects str but callers pass ints

Downstream (e.g., SimpleStorageUnit) builds ports using get_free_port(), which returns an int; current signature says dict[str, str]. Align the type to int to avoid typing/tooling issues.

Apply this diff:

-    def __init__(self, role: TransferQueueRole, id: str, ip: str, ports: dict[str, str]):
+    def __init__(self, role: TransferQueueRole, id: str, ip: str, ports: dict[str, int]):
         self.role = role
         self.id = id
         self.ip = ip
         self.ports = ports
🤖 Prompt for AI Agents
In transfer_queue/utils/zmq_utils.py around lines 69 to 74, the ZMQServerInfo
__init__ signature declares ports: dict[str, str] but callers (e.g.,
SimpleStorageUnit) pass ints from get_free_port(); change the type annotation to
ports: dict[str, int] (or more generally Mapping[str, int]) to match callers,
update any related type hints/returns/usages in the module to accept ints, and
run type checks to ensure no remaining mismatches.

Sorry, something went wrong.

Uh oh!

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

All reactions
@0oshowero0
fix minor issues
ac531d0
Signed-off-by: 0oshowero0 <[email protected]>
@0oshowero0 0oshowero0 assigned Copilot Oct 30, 2025
@0oshowero0
0oshowero0 requested a review from Copilot October 30, 2025 02:33
@0oshowero0 0oshowero0 unassigned Copilot Oct 30, 2025
Copilot AI reviewed Oct 30, 2025
View reviewed changes

Copilot AI 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.

Pull Request Overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread transfer_queue/metadata.py
all_samples = []
for chunk in data:
all_samples.extend(chunk.samples)
all_samples = list(itertools.chain.from_iterable(chunk.samples for chunk in data))

Copilot AI Oct 30, 2025

Copy link

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.

This creates an intermediate generator that is immediately consumed into a list. For better performance, use all_samples = [sample for chunk in data for sample in chunk.samples] which is more efficient and readable.

Suggested change
all_samples = list(itertools.chain.from_iterable(chunk.samples for chunk in data))
all_samples = [sample for chunk in data for sample in chunk.samples]

Sorry, something went wrong.

Uh oh!

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

All reactions
Copilot uses AI. Check for mistakes.
Comment thread transfer_queue/client.py
"""
self.storage_manager = TransferQueueStorageManagerFactory.create(manager_type, config)

# TODO (TQStorage): Provide a general dynamic socket function for both Client & Storage @huazhong.

Copilot AI Oct 30, 2025

Copy link

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.

The comment on line 91 states this is a TODO for providing a general dynamic socket function for both Client & Storage, but this is a static method that doesn't accept target_role anymore. The TODO should be updated or resolved since the implementation has changed from the storage manager version.

Suggested change
# TODO (TQStorage): Provide a general dynamic socket function for both Client & Storage @huazhong.

Sorry, something went wrong.

Uh oh!

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

All reactions
Copilot uses AI. Check for mistakes.
Comment thread recipe/simple_use_case/sync_demo.py
data_fields=["input_ids", "attention_mask", "generate_sequences_ids"],
batch_size=config.global_batch_size,
global_step=0,
global_step=step,

Copilot AI Oct 30, 2025

Copy link

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.

The global_step parameter was changed from hardcoded 0 to step, but this may cause issues if the same step is used for multiple operations within the loop. Verify that this change aligns with the intended behavior of the data retrieval logic.

Sorry, something went wrong.

Uh oh!

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

All reactions
Copilot uses AI. Check for mistakes.
coderabbitai[bot]
coderabbitai Bot reviewed Oct 30, 2025
View reviewed changes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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.

Actionable comments posted: 0

♻️ Duplicate comments (6)
transfer_queue/storage/simple_backend.py (4)

173-173: UUID collision risk with truncated hex.

Using only 8 hex characters from UUID (uuid4().hex[:8]) creates collision risk in deployments with many storage units (birthday paradox: ~1% chance at ~300 units).

Use the full UUID hex or at least 16 characters:

-        self.storage_unit_id = f"TQ_STORAGE_UNIT_{uuid4().hex[:8]}"
+        self.storage_unit_id = f"TQ_STORAGE_UNIT_{uuid4().hex}"

86-103: Validate local_indexes and handle None slots defensively.

The get_data method doesn't validate that local_indexes are within bounds before accessing self.field_data[field][local_indexes[0]] or using itemgetter. This can raise IndexError for out-of-range indexes. Additionally, if storage slots contain None (e.g., after a partial clear or before data is written), itemgetter will return None values that can break stacking operations.

Apply this diff to add bounds checking and filter out None values:

 def get_data(self, fields: list[str], local_indexes: list[int]) -> TensorDict[str, list]:
     """
     Get data from storage unit according to given fields and local_indexes.
     """
     result: dict[str, list] = {}
+    
+    # Validate indexes once upfront
+    for idx in local_indexes:
+        if idx < 0 or idx >= self.storage_size:
+            raise ValueError(
+                f"StorageUnitData get_data operation receive invalid local_index: {idx} beyond "
+                f"storage_size: {self.storage_size}"
+            )
 
     for field in fields:
         # Validate field name
         if field not in self.field_data:
             raise ValueError(
                 f"StorageUnitData get_data operation receive invalid field: {field} beyond {self.field_data.keys()}"
             )
 
         if len(local_indexes) == 1:
             gathered_item = self.field_data[field][local_indexes[0]]
+            if gathered_item is None:
+                raise ValueError(f"Field '{field}' at local_index {local_indexes[0]} is empty/None")
             if not isinstance(gathered_item, torch.Tensor):
                 result[field] = NonTensorStack(gathered_item)
             else:
                 result[field] = gathered_item.unsqueeze(0)
         else:
             gathered_items = list(itemgetter(*local_indexes)(self.field_data[field]))
+            
+            # Check for None values
+            if any(x is None for x in gathered_items):
+                none_indexes = [idx for idx, x in zip(local_indexes, gathered_items) if x is None]
+                raise ValueError(f"Field '{field}' contains empty slots at indexes: {none_indexes}")
 
             if gathered_items:
                 all_tensors = all(isinstance(x, torch.Tensor) for x in gathered_items)
                 if all_tensors:
                     result[field] = torch.nested.as_nested_tensor(gathered_items)
                 else:
                     result[field] = NonTensorStack(*gathered_items)
 
     return TensorDict(result)

115-127: Prevent length mismatch and avoid enumerate-coupling in put_data.

When len(values) ≠ len(local_indexes), the current code using enumerate(local_indexes) and indexing values[i] will either raise IndexError or write incorrect data. The bounds check happens per-index but after the mismatch has already caused issues.

Apply this diff to validate lengths upfront and use zip:

 def put_data(self, field_data: TensorDict[str, Any], local_indexes: list[int]) -> None:
     """
     Put or update data into storage unit according to given field_data and local_indexes.
     """
     extracted_data = dict(field_data)
 
     for f, values in extracted_data.items():
         if f not in self.field_data:
             self.field_data[f] = [None] * self.storage_size
+        
+        if len(values) != len(local_indexes):
+            raise ValueError(
+                f"put_data length mismatch for field '{f}': "
+                f"{len(values)} values for {len(local_indexes)} indexes"
+            )
 
-        for i, idx in enumerate(local_indexes):
+        for idx, value in zip(local_indexes, values):
             if idx < 0 or idx >= self.storage_size:
                 raise ValueError(
                     f"StorageUnitData put_data operation receive invalid local_index: {idx} beyond "
                     f"storage_size: {self.storage_size}"
                 )
 
-            self.field_data[f][idx] = values[i]
+            self.field_data[f][idx] = value

216-246: Unsafe network deserialization via pickle.

Deserializing pickled messages from sockets (line 217: ZMQMessage.deserialize(serialized_msg)) enables arbitrary code execution if an attacker can connect to the storage unit's ZMQ endpoint. This is a known Python security issue with pickle over the network.

Consider these mitigations:

  1. Preferred: Replace pickle with a safe serialization format (msgpack, JSON + schema validation)
  2. If pickle unavoidable:
    • Restrict bind addresses to trusted networks only
    • Add ZMQ CURVE authentication
    • Implement message authentication (HMAC)
    • Document the security model and deployment restrictions

Also see: Python pickle security documentation

transfer_queue/storage/managers/simple_backend_manager.py (2)

120-128: Missing port validation when building address.

Line 126 builds the address using server_info.ports.get(socket_name) without verifying the port exists. If the port is missing, this produces tcp://127.0.0.1:None, leading to obscure connection errors.

Add explicit validation:

             server_info = self.storage_unit_infos.get(server_key)
 
             if not server_info:
                 raise RuntimeError(f"Server {server_key} not found in registered servers")
 
             context = zmq.asyncio.Context()
-            address = f"tcp://{server_info.ip}:{server_info.ports.get(socket_name)}"
+            port = server_info.ports.get(socket_name)
+            if port is None:
+                raise ValueError(
+                    f"StorageUnit {server_info.id} missing port '{socket_name}' in ports={server_info.ports}"
+                )
+            address = f"tcp://{server_info.ip}:{port}"
             identity = f"{self.storage_manager_id}_to_{server_info.id}_{uuid4().hex[:8]}".encode()
             sock = create_zmq_socket(context, zmq.DEALER, identity=identity)

211-221: Handle empty field_data to avoid NonTensorStack construction errors.

If transfer_data["field_data"][field] is an empty list, NonTensorStack(*[]) is called with no arguments, which may raise an error depending on the NonTensorStack implementation.

Add defensive handling for empty field data:

         tensordict_data = TensorDict(
             {
                 field: (
-                    torch.nested.as_nested_tensor(transfer_data["field_data"][field])
-                    if transfer_data["field_data"][field]
-                    and all(isinstance(x, torch.Tensor) for x in transfer_data["field_data"][field])
-                    else NonTensorStack(*transfer_data["field_data"][field])
+                    self._to_stackable(transfer_data["field_data"][field])
                 )
                 for field in transfer_data["field_data"]
             }
         )
+    
+    def _to_stackable(self, field_data_list):
+        """Convert field data to stackable format, handling empty lists."""
+        if not field_data_list:
+            return NonTensorStack()  # Handle empty case
+        if all(isinstance(x, torch.Tensor) for x in field_data_list):
+            return torch.nested.as_nested_tensor(field_data_list)
+        return NonTensorStack(*field_data_list)

Add this helper method to the AsyncSimpleStorageManager class to improve readability and handle edge cases.

🧹 Nitpick comments (2)
transfer_queue/storage/simple_backend.py (1)

260-262: Clarify field_data parameter type in docstring.

The comment at line 260 states field_data should be a TensorDict, which is correct. However, past comments noted the format description was inconsistent with usage.

The implementation is correct. For clarity, you could enhance the docstring:

         """
         Handle put request, add or update data into storage unit.
 
         Args:
-            data_parts: ZMQMessage from client.
+            data_parts: ZMQMessage from client containing:
+                        - local_indexes: list[int] - target storage positions
+                        - data: TensorDict - field data to store
 
         Returns:
             Put data success response ZMQMessage.
         """
transfer_queue/storage/managers/simple_backend_manager.py (1)

287-303: Review nested comprehension complexity.

The nested conditional in lines 289-299 for building tensor_data is complex. While functionally correct, it could benefit from extraction into a helper method for readability.

Consider extracting to a helper:

def _stack_field_data(self, field: str, v: list) -> torch.Tensor | NonTensorStack:
    """Stack field data with appropriate nesting."""
    if not v:
        return NonTensorStack()
    
    if not all(isinstance(item, torch.Tensor) for item in v):
        return NonTensorStack(*v)
    
    # All tensors - check if same shape
    if all(item.shape == v[0].shape for item in v):
        return torch.stack(torch.nested.as_nested_tensor(v).unbind())
    else:
        return torch.nested.as_nested_tensor(v)

Then simplify the dict comprehension:

tensor_data = {
    field: self._stack_field_data(field, v)
    for field, v in ordered_data.items()
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3332482 and ac531d0.

📒 Files selected for processing (5)
  • tests/test_async_simple_storage_manager.py (1 hunks)
  • tests/test_client.py (10 hunks)
  • transfer_queue/client.py (12 hunks)
  • transfer_queue/storage/managers/simple_backend_manager.py (1 hunks)
  • transfer_queue/storage/simple_backend.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
tests/test_async_simple_storage_manager.py (5)
transfer_queue/metadata.py (3)
  • BatchMeta (136-417)
  • FieldMeta (28-45)
  • SampleMeta (49-132)
transfer_queue/storage/managers/simple_backend_manager.py (7)
  • AsyncSimpleStorageManager (37-415)
  • _put_to_single_storage_unit (205-241)
  • _get_from_single_storage_unit (306-337)
  • _clear_single_storage_unit (366-388)
  • put_data (163-202)
  • get_data (243-303)
  • clear_data (339-363)
transfer_queue/utils/utils.py (1)
  • TransferQueueRole (36-39)
transfer_queue/utils/zmq_utils.py (5)
  • ZMQMessage (91-131)
  • ZMQRequestType (31-65)
  • ZMQServerInfo (68-87)
  • create (100-114)
  • serialize (116-118)
transfer_queue/storage/managers/base.py (5)
  • _connect_to_controller (55-82)
  • notify_data_update (172-256)
  • put_data (259-260)
  • get_data (263-264)
  • clear_data (267-268)
transfer_queue/storage/managers/simple_backend_manager.py (5)
transfer_queue/metadata.py (3)
  • BatchMeta (136-417)
  • global_indexes (167-169)
  • batch_index (70-72)
transfer_queue/storage/managers/base.py (3)
  • TransferQueueStorageManager (40-268)
  • notify_data_update (172-256)
  • get_data (263-264)
transfer_queue/storage/simple_backend.py (4)
  • StorageMetaGroup (355-449)
  • get_transfer_data (389-428)
  • get_data (66-103)
  • add_sample_meta (365-368)
transfer_queue/utils/utils.py (1)
  • limit_pytorch_auto_parallel_threads (105-132)
transfer_queue/utils/zmq_utils.py (7)
  • ZMQMessage (91-131)
  • ZMQRequestType (31-65)
  • ZMQServerInfo (68-87)
  • create_zmq_socket (140-170)
  • create (100-114)
  • serialize (116-118)
  • deserialize (121-131)
tests/test_client.py (6)
transfer_queue/utils/utils.py (1)
  • TransferQueueRole (36-39)
transfer_queue/utils/zmq_utils.py (1)
  • ZMQServerInfo (68-87)
transfer_queue/client.py (3)
  • initialize_storage_manager (73-89)
  • get_data (547-556)
  • TransferQueueClient (485-564)
transfer_queue/storage/managers/simple_backend_manager.py (3)
  • put_data (163-202)
  • get_data (243-303)
  • clear_data (339-363)
transfer_queue/storage/simple_backend.py (2)
  • put_data (105-126)
  • get_data (66-103)
transfer_queue/storage/managers/base.py (3)
  • put_data (259-260)
  • get_data (263-264)
  • clear_data (267-268)
transfer_queue/client.py (7)
transfer_queue/controller.py (3)
  • TransferQueueController (55-750)
  • clear (737-750)
  • get_zmq_server_info (729-735)
transfer_queue/metadata.py (2)
  • BatchMeta (136-417)
  • size (162-164)
transfer_queue/storage/simple_backend.py (6)
  • SimpleStorageUnit (150-351)
  • put_data (105-126)
  • size (431-433)
  • get_data (66-103)
  • clear (128-146)
  • get_zmq_server_info (345-351)
transfer_queue/storage/managers/base.py (4)
  • TransferQueueStorageManager (40-268)
  • put_data (259-260)
  • get_data (263-264)
  • clear_data (267-268)
transfer_queue/storage/managers/factory.py (2)
  • TransferQueueStorageManagerFactory (20-40)
  • create (35-40)
transfer_queue/utils/zmq_utils.py (5)
  • ZMQServerInfo (68-87)
  • create (100-114)
  • create_zmq_socket (140-170)
  • ZMQMessage (91-131)
  • ZMQRequestType (31-65)
transfer_queue/storage/managers/simple_backend_manager.py (6)
  • wrapper (112-157)
  • put_data (163-202)
  • get_data (243-303)
  • clear_data (339-363)
  • close (398-415)
  • get_zmq_server_info (390-396)
transfer_queue/storage/simple_backend.py (2)
transfer_queue/metadata.py (2)
  • SampleMeta (49-132)
  • batch_index (70-72)
transfer_queue/utils/zmq_utils.py (9)
  • ZMQMessage (91-131)
  • ZMQRequestType (31-65)
  • ZMQServerInfo (68-87)
  • create_zmq_socket (140-170)
  • get_free_port (134-137)
  • to_addr (75-76)
  • deserialize (121-131)
  • create (100-114)
  • serialize (116-118)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: CodeQL analysis (python)
  • GitHub Check: Agent
  • GitHub Check: pre-commit (3.10)
  • GitHub Check: build (3.11)
  • GitHub Check: build (3.11)
  • GitHub Check: pre-commit (3.10)
🔇 Additional comments (15)
transfer_queue/storage/simple_backend.py (1)

354-449: LGTM: StorageMetaGroup design is clean.

The StorageMetaGroup class provides a well-structured abstraction for grouping samples by storage unit. The helper methods (get_batch_indexes, get_global_indexes, get_field_names, get_transfer_data) cleanly separate concerns and the docstrings with examples are excellent.

transfer_queue/client.py (5)

54-71: LGTM: Single-controller initialization with validation.

The refactored constructor properly validates the controller_info parameter with type checking and null checks. The logging provides good visibility into the registration.


289-310: Storage manager initialization check is present.

The async_put method now includes the requested validation check (lines 289-293) to ensure storage_manager is initialized before use. The error message is clear and actionable.


341-352: Storage manager check properly added in async_get_data.

Lines 341-345 implement the validation check that was flagged in past reviews. The implementation matches the pattern used in async_put.


364-383: Storage manager and controller checks properly implemented.

The async_clear method now includes both storage manager validation (lines 364-368) and controller validation (lines 370-371) as requested in past reviews.


475-483: Good defensive cleanup in close().

The close method properly handles missing attributes and exceptions during cleanup, preventing resource leaks.

tests/test_async_simple_storage_manager.py (4)

35-91: Well-designed test fixture with proper ZMQ mocking.

The mock_async_storage_manager fixture properly mocks the ZMQ handshake while preserving the essential manager state (storage_unit_infos, mapping functions). This allows testing the business logic without ZMQ complexity.


93-159: Comprehensive test coverage for basic operations.

The test validates initialization, put_data, get_data, and clear_data operations with proper assertions. Mocking the internal methods (_put_to_single_storage_unit, etc.) is appropriate for unit testing the orchestration layer.


161-246: Excellent validation of round-robin mapping logic.

This test explicitly validates the distribution algorithm with 3 storage units, covering multiple cycles. The assertions clearly document the expected behavior (global_index → storage_unit and global_index → local_index mappings).


248-339: Good error propagation testing.

The test verifies that exceptions from put_data and get_data propagate correctly, while clear_data tolerates exceptions (due to return_exceptions=True). This validates the intended error handling semantics.

transfer_queue/storage/managers/simple_backend_manager.py (1)

418-550: Well-documented data flow helpers with excellent examples.

The get_transfer_data, _add_field_data, and build_storage_meta_groups functions have outstanding documentation. The docstrings with examples clearly explain the critical batch_index → data extraction flow. This will help maintainers understand the data distribution logic.

tests/test_client.py (4)

69-76: LGTM: Proper use of TransferQueueRole enum.

The ZMQServerInfo construction correctly uses TransferQueueRole.CONTROLLER instead of string-based roles, aligning with the refactored architecture.


271-278: Patch path uses full module location - good practice.

Line 272 correctly patches transfer_queue.storage.managers.simple_backend_manager.AsyncSimpleStorageManager._connect_to_controller, targeting the actual definition location rather than the re-export. This is more reliable for mocking.


265-294: Test setup properly mocks storage manager operations.

The client_setup fixture appropriately mocks the handshake and all storage manager data operations (put_data, get_data, clear_data) to isolate client logic from ZMQ I/O.


359-410: Comprehensive multi-storage test with proper cleanup.

The test_single_controller_multiple_storages function validates the single-controller architecture with 3 storage units and ensures proper cleanup in the finally block.

Sorry, something went wrong.

Uh oh!

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

All reactions
@0oshowero0
minor fix
fc3b420
Signed-off-by: 0oshowero0 <[email protected]>
coderabbitai[bot]
coderabbitai Bot reviewed Oct 30, 2025
View reviewed changes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
transfer_queue/metadata.py (1)

222-230: Fix logic bug and performance issue in add_fields.

Two issues:

  1. Logic bug: The validation at lines 222-224 only runs if fields is truthy, but the for loop at line 225 runs unconditionally. If fields is an empty list, line 226 will raise IndexError when accessing fields[idx].

  2. Performance issue: Lines 228-230 update batch-level caches inside the loop, recomputing on every iteration. These should execute once after the loop completes.

Apply this diff:

     fields = _extract_field_metas(tensor_dict, set_all_ready)
     
     if fields:
         if len(self.samples) != len(fields):
             raise ValueError(f"add_fields length mismatch: samples={len(self.samples)} vs fields={len(fields)}")
-    for idx, sample in enumerate(self.samples):
-        sample.add_fields(fields=fields[idx])
-
-        # Update batch-level fields cache
-        object.__setattr__(self, "_field_names", sorted(self.samples[0].field_names))
-        object.__setattr__(self, "_is_ready", all(sample.is_ready for sample in self.samples))
+        
+        for idx, sample in enumerate(self.samples):
+            sample.add_fields(fields=fields[idx])
+
+        # Update batch-level fields cache
+        object.__setattr__(self, "_field_names", sorted(self.samples[0].field_names))
+        object.__setattr__(self, "_is_ready", all(sample.is_ready for sample in self.samples))
     return self
♻️ Duplicate comments (3)
transfer_queue/metadata.py (3)

89-92: Docstring still contradicts implementation.

The docstring claims default values will be used for missing dtype/shape/production_status, but the implementation (line 93) simply merges the provided fields dictionary without any defaulting logic.


298-298: Consider list comprehension for better readability.

As previously noted, a list comprehension would be more direct and readable than creating an intermediate iterator.


300-300: Breaking change: ChainMap reverses precedence order.

The current implementation uses dict(ChainMap(*(chunk.extra_info for chunk in data))), which gives precedence to the first chunk's extra_info values when keys overlap.

If the original code used update() in a loop, later chunks would overwrite earlier ones (last-wins semantics). This is a breaking behavioral change.

To restore last-wins semantics, apply this diff:

-    merged_extra_info = dict(ChainMap(*(chunk.extra_info for chunk in data)))
+    merged_extra_info = {}
+    for chunk in data:
+        merged_extra_info.update(chunk.extra_info)

Or if first-wins is intentional, document this explicitly in the method docstring.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac531d0 and fc3b420.

📒 Files selected for processing (2)
  • tests/test_client.py (10 hunks)
  • transfer_queue/metadata.py (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
transfer_queue/metadata.py (1)
transfer_queue/utils/utils.py (1)
  • ProductionStatus (43-46)
tests/test_client.py (4)
transfer_queue/utils/utils.py (1)
  • TransferQueueRole (36-39)
transfer_queue/utils/zmq_utils.py (1)
  • ZMQServerInfo (68-87)
transfer_queue/client.py (3)
  • initialize_storage_manager (73-89)
  • get_data (547-556)
  • TransferQueueClient (485-564)
transfer_queue/storage/managers/simple_backend_manager.py (3)
  • put_data (163-202)
  • get_data (243-303)
  • clear_data (339-363)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build (3.11)
  • GitHub Check: pre-commit (3.10)
  • GitHub Check: pre-commit (3.10)
  • GitHub Check: build (3.11)
🔇 Additional comments (3)
transfer_queue/metadata.py (1)

430-460: LGTM! Clean field metadata extraction.

The implementation correctly extracts per-sample field metadata from the TensorDict, gracefully handling missing dtype/shape attributes with None defaults. The list comprehension is clear and efficient.

tests/test_client.py (2)

270-293: LGTM! Clean mocking approach.

The storage manager mocking strategy is well-structured:

  • Uses the full module path for patching _connect_to_controller (consistent with past feedback)
  • Mocks ZMQ operations to avoid real network I/O in tests
  • Properly initializes the manager before replacing its methods

358-410: LGTM! Test properly adapted to single-controller architecture.

The test correctly reflects the new API:

  • Single controller with multiple storage units
  • Consistent mocking approach with the fixture
  • Proper resource cleanup in the finally block
  • Appropriate verification of controller setup

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread tests/test_client.py
Comment on lines +119 to +120
print(f"MockController ERROR: {e}")
raise

@coderabbitai coderabbitai Bot Oct 30, 2025

Copy link
Copy Markdown

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.

⚠️ Potential issue | 🟡 Minor

Inconsistent error handling between MockController and MockStorage.

MockController (lines 119-120) always raises exceptions after logging, but MockStorage (lines 214-218) only raises when self.running is False. When running is True, MockStorage swallows exceptions after logging them. This inconsistency can mask test failures and make debugging harder.

For test reliability, consider making the error handling consistent by having both mocks always raise exceptions during test execution:

Apply this diff to align MockStorage error handling with MockController:

             except Exception as e:
-                if self.running:
-                    print(f"MockStorage running exception: {e}")
-                else:
-                    print(f"MockStorage ERROR: {e}")
-                    raise
+                print(f"MockStorage ERROR: {e}")
+                raise

Also applies to: 214-218

🤖 Prompt for AI Agents
In tests/test_client.py around lines 119-120 and 214-218, MockController
currently logs an exception then re-raises it while MockStorage logs and only
re-raises when self.running is False; make handling consistent by always
re-raising exceptions after logging in MockStorage (remove or replace the
conditional that swallows errors when self.running is True), so both mocks log
the error and then raise the original exception to surface failures during
tests.

Sorry, something went wrong.

Uh oh!

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

All reactions
@0oshowero0
0oshowero0 merged commit 7f7dbed into main Oct 30, 2025
1 check passed

Uh oh!

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

This was referenced Nov 5, 2025
[BREAKING][Feat] Support user-defined sampler to manager data consumption #101
Merged
[Fix] Fix data partition bugs and optimizations #100
Merged
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

Copilot code review Copilot
Copilot review effort
Applies to this pull request for everyone.Learn more about Copilot code review.
Copilot left review comments
+1 more reviewer
@coderabbitai coderabbitai[bot] coderabbitai[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.

5 participants

@0oshowero0 @baymax591 @jianjunzhong
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.