Repository: milvus-io/milvus
File: internal/core/src/segcore/storagev2translator/ManifestGroupTranslator.cpp (C++)

Code before:
    };

    // Submit cell-batch loading tasks
    auto channel = std::make_shared<milvus::segcore::CellReaderChannel>();

    auto load_futures =
        milvus::segcore::LoadCellBatchAsync(ctx,
                                            std::move(cell_specs),
                                            std::move(factory),
                                            channel,
                                            DEFAULT_FIELD_MAX_MEMORY_LIMIT,
                                            load_priority_);

    LOG_INFO(
        "[StorageV2] translator {} submits {} batch tasks for manifest column "
        "group {}",
        key_,
        load_futures.size(),
        column_group_index_);

    // Pop loop — convert each cell immediately, no ArrowTable accumulation
    std::unordered_map<milvus::cachinglayer::cid_t,
                       std::unique_ptr<milvus::GroupChunk>>
        completed_cells;
    completed_cells.reserve(cids.size());

    std::shared_ptr<milvus::segcore::CellLoadResult> cell_data;
    while (channel->pop(cell_data)) {
        CheckCancellation(
            ctx, segment_id_, "ManifestGroupTranslator::get_cells()");
        completed_cells[cell_data->cid] =
            load_group_chunk(cell_data->tables, cell_data->cid);
    }

    storage::WaitAllFutures(load_futures);

    for (auto cid : cids) {
        auto it = completed_cells.find(cid);
        AssertInfo(
            it != completed_cells.end(),
            fmt::format(
                "[StorageV2] translator {} cell {} not loaded", key_, cid));
        cells.emplace_back(cid, std::move(it->second));
    }

    return cells;
}

std::unique_ptr<milvus::GroupChunk>
ManifestGroupTranslator::load_group_chunk(
    const std::vector<std::shared_ptr<arrow::Table>>& tables,

Review comment (suggestion):
UAF risk: exception in pop loop bypasses `WaitAllFutures`, background tasks dereference dangling `chunk_reader` pointer.

Code after:

    // Pop loop — convert each cell immediately, no ArrowTable accumulation
    std::unordered_map<milvus::cachinglayer::cid_t,
                       std::unique_ptr<milvus::GroupChunk>>
        completed_cells;
    completed_cells.reserve(cids.size());

    try {
        std::shared_ptr<milvus::segcore::CellLoadResult> cell_data;
        while (channel->pop(cell_data)) {
            CheckCancellation(
                ctx, segment_id_, "ManifestGroupTranslator::get_cells()");
            completed_cells[cell_data->cid] =
                load_group_chunk(cell_data->tables, cell_data->cid);
        }
    } catch (std::exception& ex) {
        // make sure all futures are handled, but still throw pop & load ex
        try {
            storage::WaitAllFutures(load_futures);
        } catch (...) {
            LOG_WARN("WaitAllFutures exception swallowed");
        }
        throw;
    }

    storage::WaitAllFutures(load_futures);

    for (auto cid : cids) {
        auto it = completed_cells.find(cid);
        AssertInfo(
            it != completed_cells.end(),
            fmt::format(
                "[StorageV2] translator {} cell {} not loaded", key_, cid));
        cells.emplace_back(cid, std::move(it->second));
    }

    return cells;
}

std::unique_ptr<milvus::GroupChunk>
ManifestGroupTranslator::load_group_chunk(
    const std::vector<std::shared_ptr<arrow::Table>>& tables,
    const milvus::cachinglayer::cid_t cid) {
    assert(!tables.empty());
    // Use the first table's schema as reference for field iteration
    const auto& schema = tables[0]->schema();

    std::vector<FieldId> field_ids;
    field_ids.reserve(schema->num_fields());
    std::vector<FieldMeta> field_metas;
    field_metas.reserve(schema->num_fields());