Skip to content

perf: zero-copy GET for large strings with CoW#7426

Open
romange wants to merge 1 commit into
mainfrom
perf/zero-copy-cow
Open

perf: zero-copy GET for large strings with CoW#7426
romange wants to merge 1 commit into
mainfrom
perf/zero-copy-cow

Conversation

@romange
Copy link
Copy Markdown
Collaborator

@romange romange commented May 26, 2026

Note

Stacked on #7412 (perf/zero-copy-core-primitives). Adds the reply-builder
integration, EngineShard wiring, and GET command changes on top of the
foundational primitives from that PR.

Summary

Wires the zero-copy GET path end-to-end for LARGE_STR_TAG values with
NONE_ENC, ASCII1_ENC, and ASCII2_ENC encodings. GET on a large
string no longer allocates a std::string; the reply builder streams bytes
directly from the shard's CompactObj storage. Concurrent mutations are
safe via the Copy-on-Write mechanism from the base PR.

Changes

GET fast path (string_family.cc)

ReadStringBorrow calls pv.TryGetRaw() (which registers the read pin and
stamps read_pending) and forwards RawBorrow.pin to the reply builder for
post-send release. Two sub-paths:

  • NONE_ENC: SendBulkStringBorrowed — iovec reference, zero bytes copied
  • ASCII1/ASCII2_ENC: SendBulkStringStreamed — ASCII-packed source decoded
    chunk-by-chunk into the reply builder's scratch; no full decoded payload held anywhere

get_zero_copy flag (default on) for A/B benchmarking without rebuild.

Reply builder (facade/reply_builder.{h,cc})

  • WriteDecodedChunks / SendBulkStringStreamed: stream-decode a packed
    source into the scratch buffer with intermediate Flush() for large values
  • AddPostSendPin / SetPostSendUnpinFn: opaque pin released in Send()
    after the socket write completes

Capture/replay (facade/reply_capture, reply_payload.h)

CapturingReplyBuilder overrides both send methods to record borrowed
payloads without materialising a std::string. Replay forwards to the real
sink's streaming path — zero-copy survives MULTI/EXEC squashing.

Wiring (engine_shard.cc)

InitShardThreadLocalState installs the unpin function at startup.
Heartbeat calls CompactObj::DrainPendingReads().

Stats (server_state.h, engine_shard.h, server_family.cc)

borrowed_string_views_total (shard) and borrowed_strings_sent_total
(coordinator) in INFO stats.

Tests

  • GetLargeRawBorrowed / GetLargeRawBorrowedSquashed — NONE_ENC round-trip, stat counters
  • GetLargeAsciiBorrowedChunked / GetLargeAsciiBorrowedChunkedSquashed — ASCII paths including scratch-flush sizes
  • PendingReadPinOrphanDrain / PendingReadDrainNonOrphaned — pin lifecycle via TryGetRaw + CoW

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Zero-copy GET for large strings with Copy-on-Write and streaming decode

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Implements zero-copy GET for large strings using Copy-on-Write mechanism
  - Borrowed views into CompactObj storage avoid std::string allocation
  - Supports NONE_ENC, ASCII1_ENC, ASCII2_ENC encodings
  - Chunked streaming decode for ASCII-packed values
• Adds PendingRead pin registry with MPSC free list for safe concurrent access
  - Orphaning mechanism protects borrowed buffers during mutations
  - Drain cycle reclaims orphaned buffers on shard heartbeat
• Extends reply builder with post-send pin release and streaming decode
  - WriteDecodedChunks streams decoded payload chunk-by-chunk into scratch
  - AddPostSendPin/SetPostSendUnpinFn manage pin lifetime across socket writes
• Preserves zero-copy through MULTI/EXEC via CapturingReplyBuilder overrides
  - BulkStringView and BulkStringStreamed payload variants avoid materialization
  - Replay path streams directly to real sink without intermediate copy

Grey Divider

File Changes

1. src/core/compact_object.cc ✨ Enhancement +19/-19

Add read_pending bit checks and orphan callback integration

src/core/compact_object.cc


2. src/core/compact_object_test.cc 🧪 Tests +2/-2

Reduce test string sizes for faster execution

src/core/compact_object_test.cc


3. src/facade/reply_builder.cc ✨ Enhancement +87/-0

Implement WriteDecodedChunks and post-send pin release

src/facade/reply_builder.cc


View more (16)
4. src/facade/reply_builder.h ✨ Enhancement +63/-0

Add streaming decode and post-send pin APIs

src/facade/reply_builder.h


5. src/facade/reply_capture.cc ✨ Enhancement +44/-0

Override SendBulkStringBorrowed and SendBulkStringStreamed

src/facade/reply_capture.cc


6. src/facade/reply_capture.h ✨ Enhancement +3/-0

Add borrowed and streamed bulk string overrides

src/facade/reply_capture.h


7. src/facade/reply_payload.h ✨ Enhancement +28/-0

Add BulkStringView and BulkStringStreamed payload variants

src/facade/reply_payload.h


8. src/server/engine_shard.cc ✨ Enhancement +11/-1

Install post-send unpin function and drain pending reads

src/server/engine_shard.cc


9. src/server/engine_shard.h ✨ Enhancement +2/-0

Add borrowed_string_views_total stat counter

src/server/engine_shard.h


10. src/server/server_state.cc ✨ Enhancement +2/-1

Add borrowed_strings_sent_total stat counter

src/server/server_state.cc


11. src/server/server_state.h ✨ Enhancement +9/-0

Add borrowed_strings_sent_total coordinator stat

src/server/server_state.h


12. src/server/server_family.cc ✨ Enhancement +2/-0

Export borrowed string stats to INFO metrics

src/server/server_family.cc


13. src/server/string_family.cc ✨ Enhancement +123/-2

Implement zero-copy GET fast path with BorrowedString

src/server/string_family.cc


14. src/server/string_family_test.cc 🧪 Tests +220/-0

Add comprehensive zero-copy GET and pin lifecycle tests

src/server/string_family_test.cc


15. src/core/compact_object.h ✨ Enhancement +31/-14

Add IsReadPending/SetReadPending helpers and RawBorrow struct

src/core/compact_object.h


16. src/core/detail/bitpacking.h ✨ Enhancement +15/-0

Add chunked ASCII decode primitive and alignment constant

src/core/detail/bitpacking.h


17. docs/zero_copy_get.md 📝 Documentation +224/-162

Document zero-copy GET architecture and implementation details

docs/zero_copy_get.md


18. src/http_api.cc ✨ Enhancement +0/-0

Add visitor overloads for borrowed and streamed payloads

src/http_api.cc


19. src/server/http_api.cc Additional files +18/-0

...

src/server/http_api.cc


Grey Divider

Qodo Logo

@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented May 26, 2026

🤖 Augment PR Summary

Summary: This PR wires an end-to-end zero-copy GET fast path for large in-memory strings, borrowing bytes directly from shard-owned CompactObj storage and relying on Copy-on-Write to stay safe under concurrent mutations.

Changes:

  • Adds a get_zero_copy flag to toggle the borrowed fast path for benchmarking.
  • Extends GET to return a cmn::BorrowedString variant when PrimeValue::TryBorrow() succeeds (raw and ASCII-packed large strings).
  • Updates the reply path to send borrowed payloads via SendBulkStringBorrowed (including capture/replay flows such as MULTI/EXEC).
  • Hooks pin reclamation into EngineShard::Heartbeat() via CompactObj::DrainPendingReads().
  • Adds new INFO stats counters: shard borrowed_string_views_total and coordinator borrowed_strings_sent_total.
  • Adjusts facade linking to include dfly_core where needed.
  • Adds unit tests covering raw/ASCII borrowed GET, MULTI/EXEC squashing, and pin drain/orphan lifecycles; tweaks a Python connection test to disable zero-copy for reproducibility.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

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

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread src/server/string_family.cc Outdated
Comment thread docs/zero_copy_get.md Outdated
Comment thread src/facade/reply_capture.cc Outdated
Comment thread src/server/string_family.cc
@romange romange force-pushed the perf/zero-copy-core-primitives branch from 754d67b to 9e7b14e Compare May 27, 2026 06:32
@romange
Copy link
Copy Markdown
Collaborator Author

romange commented May 27, 2026

/config

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

🛠️ Wiki configuration file settings:
[github_app]
pr_commands = [
    "/agentic_describe",
    "/agentic_review"
]

[pr_compliance]
enabled = false

[pr_description]
enable_pr_diagram = false

[review_labels]
enabled = false

[review_agent]
enable_compliance_agent = false

🛠️ Local configuration file settings:
 

🛠️ Global configuration file settings:

🛠️ PR-Agent final configurations:
==================== CONFIG ====================
config.organization_id = 'codium.ai'  
config.qodo_llm_gateway_metadata_tags = True  
config.second_model_for_exhaustive_mode = 'openai/openai/o4-mini'  
config.model = 'openai/openai/gpt-5.2'  
config.model_turbo = 'openai/anthropic/claude-haiku-4-5'  
config.model_reasoning = 'openai/vertex_ai/gemini-3.1-pro-preview'  
config.fallback_models = ['openai/anthropic/claude-sonnet-4-6', 'openai/openai/gpt-5.2']  
config.use_async_clone = True  
config.git_provider = 'github'  
config.publish_output = True  
config.publish_output_no_suggestions = True  
config.publish_output_progress = True  
config.enable_v1_deprecation_banner = True  
config.verbosity_level = 0  
config.publish_logs = False  
config.debug_mode = False  
config.use_wiki_settings_file = True  
config.use_repo_settings_file = True  
config.use_global_settings_file = True  
config.use_global_wiki_settings_file = False  
config.disable_auto_feedback = False  
config.ai_timeout = 300  
config.response_language = 'en-US'  
config.clone_repo_instead_of_fetch = True  
config.always_clone = False  
config.add_repo_metadata = True  
config.add_repo_metadata_resolve_references = False  
config.clone_repo_time_limit = 300  
config.publish_inline_comments_fallback_batch_size = 5  
config.publish_inline_comments_fallback_sleep_time = 2  
config.max_model_tokens = 32000  
config.custom_model_max_tokens = -1  
config.patch_extension_skip_types = ['.md', '.txt']  
config.extra_allowed_extensions = []  
config.allow_dynamic_context = True  
config.allow_forward_dynamic_context = True  
config.max_extra_lines_before_dynamic_context = 12  
config.patch_extra_lines_before = 5  
config.patch_extra_lines_after = 1  
config.ai_handler = 'litellm'  
config.cli_mode = False  
config.fetch_github_apps_from_platform = False  
config.trial_git_org_max_invokes_per_month = 30  
config.trial_ratio_close_to_limit = 0.8  
config.quota_just_exceeded_message = '### Qodo reviews are paused for this user.\n\nTroubleshooting steps vary by plan [Learn more →](https://docs.qodo.ai/subscription-plans#what-you%E2%80%99ll-see-when-reviews-are-paused)\n\n\n**On a Teams plan?**\nReviews resume once this user has a paid seat *and* their Git account is linked in Qodo.\n[Link Git account →](https://docs.qodo.ai/subscription-plans#linking-a-git-account)\n\n**Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?**\nThese require an Enterprise plan - Contact us\n[Contact us →](https://docs.qodo.ai/qodo-support#support)\n'  
config.invite_only_mode = False  
config.enable_request_access_msg_on_new_pr = False  
config.check_also_invites_field = False  
config.allowed_users = []  
config.calculate_context = True  
config.disable_checkboxes = False  
config.output_relevant_configurations = False  
config.large_patch_policy = 'clip'  
config.seed = -1  
config.temperature = 0.2  
config.allow_dynamic_context_ab_testing = False  
config.choose_dynamic_context_ab_testing_ratio = 0.5  
config.ignore_pr_title = ['^\\[Auto\\]', '^Auto']  
config.ignore_pr_target_branches = []  
config.ignore_pr_source_branches = []  
config.ignore_pr_labels = []  
config.ignore_ticket_labels = []  
config.allow_only_specific_folders = []  
config.ignore_pr_authors = 'REDACTED'  
config.ignore_repositories = []  
config.ignore_language_framework = []  
config.is_auto_command = False  
config.is_new_pr = False  
config.enable_ai_metadata = True  
config.present_reasoning = True  
config.max_tickets = 10  
config.max_tickets_chars = 8000  
config.extract_tickets_from_pr_title = False  
config.max_tickets_from_title = 1  
config.title_extraction_skip_patterns = ['^revert\\b', '^merge\\b', '^release\\b', '^backport\\b', '^bump\\b']  
config.prevent_any_approval = False  
config.enable_comment_approval = False  
config.enable_auto_approval = False  
config.auto_approve_for_low_review_effort = -1  
config.auto_approve_for_no_suggestions = False  
config.ensure_ticket_compliance = False  
config.new_diff_format = True  
config.new_diff_format_add_external_references = True  
config.tasks_queue_ttl_from_dequeue_in_seconds = 1200  
config.enable_custom_labels = False  
config.custom_labels_discovery_prefixes = ['pr_agent:', 'qodo:']  

==================== PR_REVIEWER ====================
pr_reviewer.require_score_review = False  
pr_reviewer.require_tests_review = True  
pr_reviewer.require_estimate_effort_to_review = True  
pr_reviewer.require_can_be_split_review = False  
pr_reviewer.require_security_review = True  
pr_reviewer.require_todo_scan = False  
pr_reviewer.require_ticket_analysis_review = True  
pr_reviewer.require_ticket_labels = False  
pr_reviewer.require_no_ticket_labels = False  
pr_reviewer.check_pr_additional_content = False  
pr_reviewer.persistent_comment = True  
pr_reviewer.extra_instructions = ''  
pr_reviewer.final_update_message = True  
pr_reviewer.enable_review_labels_security = True  
pr_reviewer.enable_review_labels_effort = True  
pr_reviewer.enable_help_text = False  

==================== PR_COMPLIANCE ====================
pr_compliance.enabled = False  
pr_compliance.enable_rules_platform = False  
pr_compliance.rule_providers = []  
pr_compliance.rule_providers_max_rules = 300  
pr_compliance.enable_security_section = True  
pr_compliance.enable_ticket_section = True  
pr_compliance.enable_codebase_duplication_section = True  
pr_compliance.enable_custom_compliance_section = True  
pr_compliance.require_ticket_analysis_review = True  
pr_compliance.allow_repo_pr_compliance = True  
pr_compliance.enable_global_pr_compliance = True  
pr_compliance.max_lines_allowed = 2000  
pr_compliance.local_wiki_compliance_str = ''  
pr_compliance.global_wiki_pr_compliance = ''  
pr_compliance.local_repo_compliance_str = ''  
pr_compliance.global_repo_pr_compliance_str = ''  
pr_compliance.global_compliance_str = ''  
pr_compliance.enable_generic_custom_compliance_checklist = True  
pr_compliance.persist_generic_custom_compliance_checklist = False  
pr_compliance.display_no_compliance_only = False  
pr_compliance.enable_security_compliance = True  
pr_compliance.enable_update_pr_compliance_checkbox = True  
pr_compliance.enable_todo_scan = False  
pr_compliance.enable_ticket_labels = False  
pr_compliance.enable_no_ticket_labels = False  
pr_compliance.check_pr_additional_content = False  
pr_compliance.enable_compliance_labels_security = True  
pr_compliance.enable_user_defined_compliance_labels = True  
pr_compliance.enable_estimate_effort_to_review = True  
pr_compliance.max_rag_components_to_analyze = 5  
pr_compliance.min_component_size = 5  
pr_compliance.persistent_comment = True  
pr_compliance.enable_help_text = False  
pr_compliance.extra_instructions = ''  

==================== PR_DESCRIPTION ====================
pr_description.publish_labels = False  
pr_description.add_original_user_description = True  
pr_description.generate_ai_title = False  
pr_description.extra_instructions = ''  
pr_description.enable_pr_type = True  
pr_description.final_update_message = True  
pr_description.enable_help_text = False  
pr_description.enable_help_comment = False  
pr_description.bring_latest_tag = False  
pr_description.enable_pr_diagram = False  
pr_description.pr_diagram_direction = 'LR'  
pr_description.publish_description_as_comment = False  
pr_description.publish_description_as_comment_persistent = True  
pr_description.enable_semantic_files_types = True  
pr_description.collapsible_file_list = 'adaptive'  
pr_description.collapsible_file_list_threshold = 8  
pr_description.inline_file_summary = False  
pr_description.use_description_markers = False  
pr_description.include_generated_by_header = True  
pr_description.enable_large_pr_handling = True  
pr_description.max_ai_calls = 4  
pr_description.auto_create_ticket = False  

==================== PR_AGENTIC_DESCRIPTION ====================
pr_agentic_description.enable_file_changes_section = True  
pr_agentic_description.file_listing_style = 'cards'  
pr_agentic_description.publish_as_comment = True  

==================== PR_QUESTIONS ====================
pr_questions.aware_ai_handler = False  
pr_questions.enable_help_text = False  

==================== PR_CODE_SUGGESTIONS ====================
pr_code_suggestions.suggestions_depth = 'exhaustive'  
pr_code_suggestions.commitable_code_suggestions = False  
pr_code_suggestions.decouple_hunks = False  
pr_code_suggestions.dual_publishing_score_threshold = -1  
pr_code_suggestions.focus_only_on_problems = True  
pr_code_suggestions.allow_thumbs_up_down = False  
pr_code_suggestions.enable_suggestion_type_reuse = False  
pr_code_suggestions.enable_more_suggestions_checkbox = True  
pr_code_suggestions.high_level_suggestions_enabled = True  
pr_code_suggestions.extra_instructions = ''  
pr_code_suggestions.enable_help_text = False  
pr_code_suggestions.show_extra_context = False  
pr_code_suggestions.persistent_comment = True  
pr_code_suggestions.max_history_len = 5  
pr_code_suggestions.apply_suggestions_checkbox = True  
pr_code_suggestions.enable_chat_in_code_suggestions = True  
pr_code_suggestions.apply_limit_scope = True  
pr_code_suggestions.suggestions_score_threshold = 0  
pr_code_suggestions.new_score_mechanism = True  
pr_code_suggestions.new_score_mechanism_th_high = 9  
pr_code_suggestions.new_score_mechanism_th_medium = 7  
pr_code_suggestions.discard_unappliable_suggestions = False  
pr_code_suggestions.num_code_suggestions_per_chunk = 3  
pr_code_suggestions.num_best_practice_suggestions = 2  
pr_code_suggestions.max_number_of_calls = 3  
pr_code_suggestions.final_clip_factor = 0.8  
pr_code_suggestions.demand_code_suggestions_self_review = False  
pr_code_suggestions.code_suggestions_self_review_text = '**Author self-review**: I have reviewed the PR code suggestions, and addressed the relevant ones.'  
pr_code_suggestions.approve_pr_on_self_review = False  
pr_code_suggestions.fold_suggestions_on_self_review = True  
pr_code_suggestions.publish_post_process_suggestion_impact = True  
pr_code_suggestions.wiki_page_accepted_suggestions = True  
pr_code_suggestions.enable_local_self_reflect_in_large_prs = False  
pr_code_suggestions.simplify_response = True  

==================== PR_CUSTOM_PROMPT ====================
pr_custom_prompt.prompt = 'The code suggestions should focus only on the following:\n- ...\n- ...\n...\n'  
pr_custom_prompt.suggestions_score_threshold = 0  
pr_custom_prompt.num_code_suggestions_per_chunk = 4  
pr_custom_prompt.self_reflect_on_custom_suggestions = True  
pr_custom_prompt.enable_help_text = False  

==================== PR_ADD_DOCS ====================
pr_add_docs.extra_instructions = ''  
pr_add_docs.docs_style = 'Sphinx'  
pr_add_docs.file = ''  
pr_add_docs.class_name = ''  

==================== PR_UPDATE_CHANGELOG ====================
pr_update_changelog.push_changelog_changes = False  
pr_update_changelog.extra_instructions = ''  
pr_update_changelog.add_pr_link = True  
pr_update_changelog.skip_ci_on_push = True  

==================== PR_ANALYZE ====================
pr_analyze.enable_help_text = False  

==================== PR_TEST ====================
pr_test.enable = True  
pr_test.extra_instructions = ''  
pr_test.testing_framework = ''  
pr_test.num_tests = 3  
pr_test.avoid_mocks = True  
pr_test.file = ''  
pr_test.class_name = ''  
pr_test.enable_help_text = False  

==================== PR_IMPROVE_COMPONENT ====================
pr_improve_component.num_code_suggestions = 4  
pr_improve_component.extra_instructions = ''  
pr_improve_component.file = ''  
pr_improve_component.class_name = ''  

==================== REVIEW_AGENT ====================
review_agent.enable_database_persistence = False  
review_agent.llm_model = 'openai/openai/gpt-5.2_thinking'  
review_agent.conversion_llm_model = 'openai/openai/gpt-5.2'  
review_agent.enabled = True  
review_agent.ensemble_models = ['openai/openai/gpt-5.2_thinking', 'openai/anthropic/claude-opus-4-6_thinking']  
review_agent.publish_output = True  
review_agent.enable_extended_mode = False  
review_agent.enable_context_collector = False  
review_agent.enable_issues_agent = True  
review_agent.enable_ticket_context = True  
review_agent.enable_compliance_agent = False  
review_agent.enable_spec_agent = True  
review_agent.enable_ui_agent = False  
review_agent.enable_persona_agent = False  
review_agent.persona_identifier = ''  
review_agent.persona_repo_name = 'qodo-personas'  
review_agent.persona_repo_default_branch = 'main'  
review_agent.persona_auto_select = True  
review_agent.persona_max_count = 2  
review_agent.persona_source = 'repo'  
review_agent.persona_url_target = 'repo'  
review_agent.persona_portal_base_url = ''  
review_agent.enable_deduplication = True  
review_agent.enable_conversion_agent = True  
review_agent.apply_conversion = True  
review_agent.enable_precision_agent = False  
review_agent.enable_cross_repo_agent = False  
review_agent.enable_qodo_review_skill = False  
review_agent.enable_past_bugs_collector = True  
review_agent.enable_sandbox_usage = False  
review_agent.sandbox_configs_repo_name = 'qodo-sandbox-configs'  
review_agent.persistent_comment = True  
review_agent.persistent_comment_notification = True  
review_agent.enable_incremental_review = True  
review_agent.enable_auto_fix = False  
review_agent.enable_review_on_fix_pr = False  
review_agent.rules_enabled = True  
review_agent.requirements_gap_enabled = True  
review_agent.llm_call_timeout = 180  
review_agent.context_collector_llm_model = 'turbo'  
review_agent.feedback_tool_llm_model = 'turbo'  
review_agent.spec_llm_model = ''  
review_agent.persona_llm_model = ''  
review_agent.persona_selector_llm_model = ''  
review_agent.conversion_batching_mode = 'batch'  
review_agent.conversion_batch_size = 10  
review_agent.cross_repo_llm_model = ''  
review_agent.precision_llm_model = ''  
review_agent.precision_max_llm_calls = 45  
review_agent.precision_batching_mode = 'batch'  
review_agent.precision_batch_size = 50  
review_agent.precision_agent_vote_strategy = 'unanimous_discard'  
review_agent.langsmith_project_name = 'review-agent'  
review_agent.max_tokens_for_file = 'REDACTED'  
review_agent.single_unified_diff_tokens_limit = 'REDACTED'  
review_agent.max_llm_calls = 100  
review_agent.max_llm_calls_limit = 100  
review_agent.warn_when_remaining = 3  
review_agent.context_collector_max_llm_calls = 6  
review_agent.compliance_batch_size = 0  
review_agent.past_bugs_max_results = 10  
review_agent.past_bugs_llm_model = 'openai/gpt-5.4-mini'  
review_agent.pass_previous_findings_to_agents = True  
review_agent.deduplication_llm_max_tokens = 'REDACTED'  
review_agent.publishing_action_level_rank_threshold = 0  
review_agent.comments_location_policy = 'both'  
review_agent.inline_comments_severity_threshold = 3  
review_agent.prefer_single_line_comments = False  
review_agent.issues_user_guidelines = ''  
review_agent.compliance_user_guidelines = ''  
review_agent.additional_repos = []  
review_agent.demand_self_review = False  
review_agent.self_review_text = '**Author self-review**: I have reviewed the code review findings, and addressed the relevant ones.'  
review_agent.approve_pr_on_self_review = False  

==================== PR_HELP ====================
pr_help.force_local_db = False  
pr_help.num_retrieved_snippets = 5  

==================== PR_NEW_ISSUE ====================
pr_new_issue.label_to_prompt_part = {'general': 'general question', 'feature': 'feature request (may already be addressed in the documentation)', 'bug': 'possible bug report (may be a by design behavior)'}  
pr_new_issue.supported_repos = ['qodo-ai/pr-agent']  

==================== PR_HELP_DOCS ====================
pr_help_docs.repo_url = ''  
pr_help_docs.repo_default_branch = 'main'  
pr_help_docs.docs_path = 'docs'  
pr_help_docs.exclude_root_readme = False  
pr_help_docs.supported_doc_exts = ['.md', '.mdx', '.rst']  
pr_help_docs.enable_help_text = False  

==================== PR_SIMILAR_ISSUE ====================
pr_similar_issue.skip_comments = False  
pr_similar_issue.force_update_dataset = False  
pr_similar_issue.max_issues_to_scan = 500  
pr_similar_issue.vectordb = 'pinecone'  

==================== PR_FIND_SIMILAR_COMPONENT ====================
pr_find_similar_component.class_name = ''  
pr_find_similar_component.file = ''  
pr_find_similar_component.search_from_org = False  
pr_find_similar_component.allow_fallback_less_words = True  
pr_find_similar_component.number_of_keywords = 5  
pr_find_similar_component.number_of_results = 5  

==================== BEST_PRACTICES ====================
best_practices.auto_best_practices_str = ''  
best_practices.wiki_best_practices_str = ''  
best_practices.global_wiki_best_practices = ''  
best_practices.local_repo_best_practices_str = ''  
best_practices.global_repo_best_practices_str = ''  
best_practices.global_best_practices_str = ''  
best_practices.organization_name = ''  
best_practices.max_lines_allowed = 2000  
best_practices.enable_global_best_practices = True  
best_practices.allow_repo_best_practices = True  
best_practices.enabled = True  

==================== AUTO_BEST_PRACTICES ====================
auto_best_practices.enable_auto_best_practices = True  
auto_best_practices.utilize_auto_best_practices = True  
auto_best_practices.extra_instructions = ''  
auto_best_practices.min_suggestions_to_auto_best_practices = 10  
auto_best_practices.number_of_days_to_update = 30  
auto_best_practices.max_patterns = 5  
auto_best_practices.minimal_date_to_update = '2025-01-26'  
auto_best_practices.generate_new_best_practices = False  

==================== JIRA ====================
jira.jira_client_id = 'REDACTED'  
jira.jira_app_secret = 'REDACTED'  

==================== LINEAR ====================
linear.linear_client_id = 'REDACTED'  

==================== PR_TO_TICKET ====================
pr_to_ticket.default_base_url = ''  
pr_to_ticket.default_project_key = 'REDACTED'  
pr_to_ticket.fallback_to_git_provider_issues = True  
pr_to_ticket.direct_update_compliance = False  

==================== PR_FIX ====================
pr_fix.enabled = False  

==================== github_app ====================
github_app.bot_user = 'github-actions[bot]'  
github_app.override_deployment_type = True  
github_app.handle_pr_actions = ['opened', 'reopened', 'ready_for_review']  
github_app.pr_commands = ['/agentic_describe', '/agentic_review']  
github_app.feedback_on_draft_pr = False  
github_app.handle_push_trigger = False  
github_app.push_commands = ['/agentic_review']  
github_app.ignore_pr_title = []  
github_app.ignore_bot_pr = True  

@romange romange force-pushed the perf/zero-copy-core-primitives branch from 9e7b14e to 1557173 Compare May 27, 2026 12:22
Base automatically changed from perf/zero-copy-core-primitives to main May 27, 2026 13:41
@romange romange force-pushed the perf/zero-copy-cow branch 5 times, most recently from 897be3d to 3eb5798 Compare May 30, 2026 05:16
@romange
Copy link
Copy Markdown
Collaborator Author

romange commented May 30, 2026

/agentic_review

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 30, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0)

Grey Divider


Action required

1. Shutdown pin use-after-free 🐞 Bug ☼ Reliability
Description
PinnedMap::~PinnedMap deletes all PendingRead entries even though non-owning threads can still hold
cmn::BorrowedString objects whose destructor calls ReleaseInternal() and dereferences the
PendingRead* pin. This can crash during shard thread teardown / process shutdown if any borrowed GET
reply is still in-flight when the shard thread’s TLS destructors run.
Code

src/core/compact_object.cc[R449-455]

Evidence
The CompactObj pin model explicitly allows non-owning threads to unpin by dereferencing the
PendingRead* stored inside BorrowedString; deleting those PendingRead objects in the owning thread’s
TLS destructor can therefore invalidate pins still held by other threads.

src/core/compact_object.cc[437-455]
src/common/borrowed_string.h[51-53,139-142]: TBD
src/core/compact_object.cc[503-506]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`PinnedMap::~PinnedMap()` currently `delete`s every `PendingRead*` in `pin_map_` during thread shutdown. However, `cmn::BorrowedString` can be destroyed on non-owning threads, and its destructor calls `BorrowedStringOps::ReleaseInternal()`, which dereferences the `PendingRead*` pin and decrements its atomic refcount. If the owning thread exits first and runs `~PinnedMap()`, later `BorrowedString` destruction will dereference freed memory (UAF) and can crash.

### Issue Context
The code explicitly documents that non-owning threads unpin by calling `BorrowedString::Unpin()` while the owning thread drains and deletes entries. Destructor-time deletion bypasses the refcount-based safety model.

### Fix Focus Areas
- src/core/compact_object.cc[449-455]

### Suggested fix
Change shutdown cleanup to **never delete pins that might still be referenced**:
- Option A (safest): remove the destructor cleanup entirely (intentionally leak `PendingRead` entries on thread shutdown; the thread heap/process is being torn down anyway).
- Option B: only delete entries whose `refcnt.load(acquire) == 0` (and free orphaned buffers for those), and **leave refcnt>0 entries allocated** to avoid UAF; optionally log/DFATAL in debug builds to catch unexpected outstanding borrows at shutdown.

Also consider (optional hardening): explicitly call `CompactObj::DrainPendingReads()` as part of shard shutdown ordering *before* TLS teardown, but this should be additive—not a substitute for making `~PinnedMap()` safe.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +449 to +455
~PinnedMap() {
// Thread shutdown: free any PendingRead entries that EngineShard::Heartbeat
// never had a chance to drain. Orphan buffers are best-effort — the
// thread's mimalloc heap is going away anyway.
for (auto& [_, pin] : pin_map_)
delete pin;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Shutdown pin use-after-free 🐞 Bug ☼ Reliability

PinnedMap::~PinnedMap deletes all PendingRead entries even though non-owning threads can still hold
cmn::BorrowedString objects whose destructor calls ReleaseInternal() and dereferences the
PendingRead* pin. This can crash during shard thread teardown / process shutdown if any borrowed GET
reply is still in-flight when the shard thread’s TLS destructors run.
Agent Prompt
### Issue description
`PinnedMap::~PinnedMap()` currently `delete`s every `PendingRead*` in `pin_map_` during thread shutdown. However, `cmn::BorrowedString` can be destroyed on non-owning threads, and its destructor calls `BorrowedStringOps::ReleaseInternal()`, which dereferences the `PendingRead*` pin and decrements its atomic refcount. If the owning thread exits first and runs `~PinnedMap()`, later `BorrowedString` destruction will dereference freed memory (UAF) and can crash.

### Issue Context
The code explicitly documents that non-owning threads unpin by calling `BorrowedString::Unpin()` while the owning thread drains and deletes entries. Destructor-time deletion bypasses the refcount-based safety model.

### Fix Focus Areas
- src/core/compact_object.cc[449-455]

### Suggested fix
Change shutdown cleanup to **never delete pins that might still be referenced**:
- Option A (safest): remove the destructor cleanup entirely (intentionally leak `PendingRead` entries on thread shutdown; the thread heap/process is being torn down anyway).
- Option B: only delete entries whose `refcnt.load(acquire) == 0` (and free orphaned buffers for those), and **leave refcnt>0 entries allocated** to avoid UAF; optionally log/DFATAL in debug builds to catch unexpected outstanding borrows at shutdown.

Also consider (optional hardening): explicitly call `CompactObj::DrainPendingReads()` as part of shard shutdown ordering *before* TLS teardown, but this should be additive—not a substitute for making `~PinnedMap()` safe.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

PinnedMap being thread-local destructs during thread destruction.

@romange
Copy link
Copy Markdown
Collaborator Author

romange commented May 30, 2026

augment review

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

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

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread src/core/detail/bitpacking.h Outdated
// facade::SinkReplyBuilder::ReplyScope contract.
StringResult ReadStringBorrow(DbIndex dbid, string_view key, const PrimeValue& pv,
EngineShard* es) {
static thread_local bool zero_copy_enabled = absl::GetFlag(FLAGS_get_zero_copy);
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 30, 2026

Choose a reason for hiding this comment

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

ReadStringBorrow caches FLAGS_get_zero_copy in a static thread_local, so runtime changes (e.g. via CONFIG SET if this ever becomes mutable) won’t propagate consistently across shard threads. Consider clarifying that get_zero_copy is startup-only, or otherwise ensuring updates are observed.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Send(iores.error().message());
}

// Specialized overload for StringResult (3-variant: string / string_view /
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 30, 2026

Choose a reason for hiding this comment

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

This comment says StringResult is a 3-variant string / string_view / tiering future, but the variant is now std::string / cmn::BorrowedString / tiering future. Keeping this accurate matters because BorrowedString has pin/lifetime semantics that differ from a plain string_view.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@romange romange force-pushed the perf/zero-copy-cow branch 2 times, most recently from 1c61903 to f34ed26 Compare June 1, 2026 09:14
Wire the zero-copy GET path end-to-end, building on the core primitives
(read_pending bit, TryGetRaw, pin registry) from the preceding PR.

`ReadStringBorrow` calls `pv.TryGetRaw()` instead of `pv.ToString()` for
large string values. `TryGetRaw` registers a read pin and stamps
`read_pending` as a side effect; the returned `RawBorrow.pin` is forwarded
to `SinkReplyBuilder::AddPostSendPin` so it is released once the socket
write completes.

`BorrowedString` carries `{encoded, pin, decoded_size, encoding}`:
- `NONE_ENC`: reply builder streams bytes directly via `SendBulkStringBorrowed`
  (iovec reference, no copy)
- `ASCII1/ASCII2_ENC`: reply builder streams via `SendBulkStringStreamed`,
  decoding the ASCII-packed source one chunk at a time into its scratch
  buffer — no full decoded payload is ever materialised anywhere

`get_zero_copy` flag (default on) lets operators A/B benchmark the path
without a rebuild; cached thread-locally to avoid per-GET flag reads.

- `borrowed_string_views_total` (shard-side): incremented each time
  `TryGetRaw` succeeds and a borrow is issued
- `borrowed_strings_sent_total` (coordinator-side): incremented each time
  the borrowed path reaches the reply builder
- Both exposed in `INFO stats`

Collapses the zero-copy GET facade surface around a single move-only owning
type. cmn::BorrowedString carries encoded bytes + decoding metadata + an
opaque pin and its own release fn; the destructor releases the pin.

Capture/replay collapses two payload alternatives (BulkStringView +
BulkStringStreamed) into one unique_ptr<cmn::BorrowedString>; the variant
owns the pin until replay moves the borrow into the real sink.

dfly_facade now links dfly_core for detail::ascii_unpack (used by the
chunked decode helper inside reply_builder.cc).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Roman Gershman <roman@dragonflydb.io>
@romange romange force-pushed the perf/zero-copy-cow branch from f34ed26 to 41e5378 Compare June 1, 2026 10:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant