Skip to content

multi: remove redundant completion message list - #22805

Closed
cmeister2 wants to merge 2 commits into
masterfrom
cm2/multi-remove-msgsent
Closed

cmeister2 wants to merge 2 commits into
masterfrom
cm2/multi-remove-msgsent

Conversation

@cmeister2

@cmeister2 cmeister2 commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Use the msgsent bitset to track unread application messages and scan it
from curl_multi_info_read(). Remove entries when read or when their easy
handle is removed.

This removes the linked-list node from each easy handle. Completion order
is not API-defined.

Add regression coverage for CURLMINFO_XFERS_DONE.

Closes #22805

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The implementation is consistent with the API contract and includes focused regression coverage.

Pull request overview

Removes redundant completed-transfer tracking while aligning CURLMINFO_XFERS_DONE with unread completion messages.

Changes:

  • Uses MSTATE_MSGSENT and the message queue for completion accounting.
  • Removes the msgsent bitset and related allocation logic.
  • Adds regression coverage and updates internal documentation.
File summaries
File Description
lib/multi.c Simplifies completion tracking and counts queued messages.
lib/multihandle.h Removes the redundant bitset.
docs/internals/MID.md Updates transfer-set documentation.
tests/libtest/lib2453.c Tests completion-message accounting.
tests/data/test2453 Defines the regression scenario.
tests/libtest/Makefile.inc Registers the test source.
tests/data/Makefile.am Registers the test case.
Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

cmeister2 added a commit that referenced this pull request Sep 2, 2026
Use MSTATE_MSGSENT for completed-transfer accounting and the message-list
length for CURLMINFO_XFERS_DONE.

This removes the duplicate bitset and its allocation while ensuring
XFERS_DONE reflects only unread application messages.

Add a regression test covering the count before and after
curl_multi_info_read().

Closes #22805
@cmeister2
cmeister2 force-pushed the cm2/multi-remove-msgsent branch from 2f8da3c to 9ae584f Compare September 2, 2026 16:52
@bagder
bagder requested a review from icing September 2, 2026 21:34

@icing icing left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What about keeping the bitset but doing away with multi->msglist? That would also shrink the easy handle some more.

@cmeister2

Copy link
Copy Markdown
Contributor Author

What about keeping the bitset but doing away with multi->msglist? That would also shrink the easy handle some more.

When codex had a look it said "@icing's “bitset only” variant is not behavior-neutral: it would naturally return completions by handle ID instead of completion order, and it would turn the deliberately O(1) info-read path into a bitset scan".

If that's a thing we are ok with then it's a possibility.

@icing

icing commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

The order in which finished transfers are returned is not defined anywhere. Just because the time of finishing is determined by the network/server.

One could possibly construct a scenerio where an application after having seen something in a callback, expects the multi info read to return exactly that transfer. Possibly. But that is brittle code, likely to fail on some other future change as well.

The bitset scan can be more costly, yes. Not by much. But the list manipulations also cost, which the bitset makes cheaper. The cleanup on removing a handle is also cheaper.

Personally, I prefer having less pointer lists in easy management.

Update: rechecked the uint-bset.c code and it has optimizations for checking/counts on empty bitsets, which is the common case for the pending set.

cmeister2 added a commit that referenced this pull request Sep 3, 2026
Use the msgsent bitset to track unread application messages and scan it
from curl_multi_info_read(). Remove entries when read or when their easy
handle is removed.

This removes the linked-list node from each easy handle. Completion order
is not API-defined.

Add regression coverage for CURLMINFO_XFERS_DONE.

Closes #22805
@cmeister2
cmeister2 force-pushed the cm2/multi-remove-msgsent branch from 9ae584f to acec0de Compare September 3, 2026 09:20
@cmeister2 cmeister2 changed the title multi: remove redundant msgsent bitset multi: remove redundant completion message list Sep 3, 2026
@cmeister2
cmeister2 requested a balanced review from Copilot September 3, 2026 09:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Counting the bitset for every message makes draining large completion queues effectively quadratic.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread lib/multi.c
Comment on lines +3107 to +3108
*msgs_in_queue =
curlx_uztosi(Curl_uint32_bset_count(&multi->msgsent));

@icing icing Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not quite true. More like O(log(N)) as the firstused slot index will increase.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@icing : Curl_uint32_bset_count doesn't use the first slot index:

uint32_t Curl_uint32_bset_count(struct uint32_bset *bset)
{
  uint32_t i;
  uint32_t n = 0;
  for(i = 0; i < bset->nslots; ++i) {
    if(bset->slots[i])
      n += CURL_POPCOUNT64(bset->slots[i]);
  }
  return n;
}

I'm having a look to see if it can use first_slot_used as part of that calc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oops, my mistake. Why doesn't it though...hmm....should work, I think.

@cmeister2 cmeister2 Sep 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤖

Historically, count() predates first_slot_used; that field was later added specifically to optimize empty() and first(). There is no correctness reason for counting from zero—it just was not updated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok, have made that fix. It doesn't resolve the quadratic worst case but realistically we're not going to be hitting that.

We could add a cached count to uint32_bset but that would likely grow the structure from 16 to 24 bytes.

@cmeister2 cmeister2 Sep 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Alternatively adding a count alongside the bitset itself would only increase the count by 8 bytes (but would make the _count call pointless really)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I doubt that this is worth it (but I am just guessing):

  • on an empty bset, count does not loop at all
  • on many platform, counting is one instruction per 64bit slot and the data subsequently is from the same cache line. The set would need to grow quite large for this to make a difference.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agree.

cmeister2 added a commit that referenced this pull request Sep 3, 2026
Use the msgsent bitset to track unread application messages and scan it
from curl_multi_info_read(). Remove entries when read or when their easy
handle is removed.

This removes the linked-list node from each easy handle. Completion order
is not API-defined.

Add regression coverage for CURLMINFO_XFERS_DONE.

Closes #22805
@cmeister2
cmeister2 force-pushed the cm2/multi-remove-msgsent branch from acec0de to a746959 Compare September 3, 2026 11:42
Use the msgsent bitset to track unread application messages and scan it
from curl_multi_info_read(). Remove entries when read or when their easy
handle is removed.

This removes the linked-list node from each easy handle. Completion order
is not API-defined.

Add regression coverage for CURLMINFO_XFERS_DONE.

Closes #22805
Start counting at first_slot_used to avoid scanning the known-empty
prefix of the bitset.
@cmeister2
cmeister2 force-pushed the cm2/multi-remove-msgsent branch from a746959 to 020f6fe Compare September 3, 2026 11:44
@bagder bagder closed this in 73c0180 Sep 3, 2026
@bagder
bagder deleted the cm2/multi-remove-msgsent branch September 3, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

3 participants