perf(ec): skip unchanged search results on the multi-search union poll - #723
Merged
got3nks merged 2 commits intoJul 30, 2026
Merged
Conversation
The EC_DETAIL_INC_UPDATE union re-sent one tag per live result on every poll, for every open search, whether or not anything had changed. Measured against a daemon with two finished searches and ~900 results, that is 12.3 KB every 3 s of which not one field is new: 7.0 KB of EC_TAG_SEARCHFILE envelopes and 5.3 KB of EC_TAG_SEARCH_ID. The envelopes were load-bearing. CRemoteContainer::ProcessUpdate deletes any item absent from the reply, so omitting an unchanged result made the client drop it from the tab. EC_TAG_SEARCH_ID was then sent unconditionally, outside the valuemap, so a result re-created after such a deletion could still be attributed to a tab. Both fall away once removal is explicit. The daemon now skips a result whose valuemap diff is empty, emits EC_TAG_FILE_REMOVED for results the core no longer holds, and sends EC_TAG_SEARCH_ID only on a result's first appearance. CSearchListRem::ProcessUpdate deletes only on those tombstones instead of sweeping by absence. An idle search costs nothing. Negotiated as its own capability, EC_TAG_CAN_PARTIAL_SEARCH, rather than reusing EC_TAG_CAN_PARTIAL_UPDATE. That existing flag is advertised by every RemoteConnect client and means the *file* INC_UPDATE stream; an already released amuleGUI advertises it, speaks multi-search, and still deletes any result missing from the reply, so reusing it would make such a client silently lose its search results against a newer daemon. amuleweb and amuleapi never reach the union at all (no EC_TAG_CAN_MULTI_SEARCH, and EC_DETAIL_FULL per id respectively). A tombstone also clears that ECID's valuemap, so a re-created result cannot inherit a diff that would strip the fields it needs. Not yet measured end to end.
clang-format v18 rewrapped Get_EC_Response_Search_Results_Union's parameter list; Tier-2 clang-tidy flagged the new EC_TAG_FILE_REMOVED walk in CSearchListRem::ProcessUpdate as modernize-loop-convert.
LSalami
added a commit
to LSalami/amule
that referenced
this pull request
Jul 31, 2026
muleToolbar() duplicated several msgid source-location references (Networks, Searches, Downloads Window, etc.) already present via amuleDlg.cpp's own toolbar setup. Deleting it drops those now-stale #: comments; no msgid added, removed, or retranslated -- verified via unchanged msgid count (1903 before/after) and a diff limited to source-location comments and POT-Creation-Date. Regenerated after rebasing onto upstream/master to pick up po/ changes from amule-org#723/amule-org#724/amule-org#726/amule-org#728/amule-org#730/amule-org#731, which had drifted our prior regeneration out of sync.
got3nks
pushed a commit
that referenced
this pull request
Jul 31, 2026
…r.cpp (#675) (#725) * chore(gui): delete unreachable bitmap functions/entries from muuli_wdr.cpp First slice of the icon-system cleanup scoped in #675: remove code with zero call sites anywhere in the tree, before any wxArtProvider migration work starts. - muleToolbar(): whole function unused -- superseded by the main wxToolBar setup in amuleDlg.cpp; nothing calls it. - moreImages(): whole function unused, both of its two icon entries. - amuleDlgImages(): 21 of 35 index blocks have no caller anywhere (0-13, 16, 17, 19, 27, 28, 31, 34). The 14 live ones are untouched -- 10 of those (20-26, 29, 32, 33) are already the fallback path inside amuleDlg.cpp's Add_Skin_Icon, which prefers a wxArtProvider/SVG lookup first; the other 4 (14, 15, 18, 30) are still called directly. - amuleSpecial(): 6 of 26 index blocks have no caller (6, 7, 8, 9, 18, 20) -- checked both direct call sites and the PrefsUnifiedDlg.cpp fallback table (pages[].m_imageidx), which uses neither. convert_xpm in PartFileConvertDlg.cpp was on the same "no literal grep hits" list initially but is not actually dead -- SetIcon(wxICON (convert)) reaches it via the wxICON macro's token-pasting (X##_xpm), invisible to a plain identifier search. Caught by a full build failing on the undeclared identifier, not by inspection; left untouched. Deletes 1373 lines (~16% of the file). No behavior change: every touched entry was unreachable code. clang-format v18 clean; full amule build verified (macOS, CLIENT_GUI unaffected since neither touched symbol is CLIENT_GUI-only). * chore(po): regenerate catalogs after muuli_wdr.cpp dead-code removal muleToolbar() duplicated several msgid source-location references (Networks, Searches, Downloads Window, etc.) already present via amuleDlg.cpp's own toolbar setup. Deleting it drops those now-stale #: comments; no msgid added, removed, or retranslated -- verified via unchanged msgid count (1903 before/after) and a diff limited to source-location comments and POT-Creation-Date. Regenerated after rebasing onto upstream/master to pick up po/ changes from #723/#724/#726/#728/#730/#731, which had drifted our prior regeneration out of sync.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
An idle amuleGUI sitting on the Search panel spends roughly 14 KB per poll for every 1000 search results it holds, purely to tell the daemon that nothing has changed. At the 3 s cadence that is about 17 MB/hour per 1000 results, per connected client — and it scales with how much the user has open, not with how much changed.
Every figure below was measured with amule-ec-profiler, a pass-through EC proxy that sizes each packet and breaks it down per tag, so the costs here are observed on the wire rather than reasoned from the source.
What it costs today
Measured against a daemon holding two finished searches and ~900 results, with the search tab open and nothing running:
EC_TAG_SEARCHFILEEC_TAG_SEARCH_IDNothing else — every mutable field diffs away correctly through the per-connection valuemap. What remains is a fixed ~14 bytes per result, per poll, forever, for results nobody is looking at.
Why both halves were there
The envelope was load-bearing.
CRemoteContainer::ProcessUpdatedeletes any item absent from the reply, so omitting an unchanged result made the client drop it from the tab. Absence was the deletion protocol.EC_TAG_SEARCH_IDwas then emitted unconditionally, outside the valuemap, so a result re-created after such a deletion could still be attributed to a tab. It was guarding against a case the first half caused.Both fall away once removal is explicit.
The change
The union now skips a result whose valuemap diff is empty and that the client already has, emits
EC_TAG_FILE_REMOVEDfor results the core no longer holds, and sendsEC_TAG_SEARCH_IDonly on a result's first appearance.CSearchListRem::ProcessUpdatedeletes only on those tombstones instead of sweeping by absence. A tombstone also clears that ECID's valuemap, so a re-created result cannot inherit a diff that would strip the fields it needs — the same hazardfreshEcidsguards on the file path.This reuses the existing knownfile tombstone rather than inventing a second one: the meaning ("this ECID is gone") and the payload are identical.
Compatibility
Negotiated as its own capability,
EC_TAG_CAN_PARTIAL_SEARCH, not by reusingEC_TAG_CAN_PARTIAL_UPDATE. That existing flag is advertised unconditionally by everyRemoteConnectclient and means the file INC_UPDATE stream. An already-released amuleGUI advertises it, speaks multi-search, and still deletes any result missing from the reply — so reusing it would make such a client silently lose its search results against a newer daemon.AUTH_OKecho → client keeps the absence sweepamuleweb never sets
EC_TAG_CAN_MULTI_SEARCH, so it stays on the per-searchEC_DETAIL_UPDATEbranch; amuleapi addresses searches by id atEC_DETAIL_FULL.Measured
Same daemon, same searches, before and after:
EC_TAG_SEARCH_IDThe two runs held different amounts, which is the point: master's cost is per result, so it has to be compared per result. This branch's 10 bytes is a bare envelope with a zero tag count — a constant, unchanged whether the client holds 800 results or 1500. The cost is now proportional to what changed, not to what is open.
Mechanism confirmed on the wire rather than inferred: closing two tabs produced 1223
EC_TAG_FILE_REMOVEDtombstones once each; a subsequent search delivered 313EC_TAG_SEARCHFILEagainst 269EC_TAG_SEARCH_ID, the 44-tag gap being updates to already-known results correctly omitting the id.Testing
70c1ec09a(this branch's base, so the only difference is this change): 11,616 B per poll of envelope + id, zero tombstones, results intact. The fallback is per connection, not global.19-search.sh108/108.Two things stated rather than measured, so they can be challenged cheaply:
m_serverPartialSearchhas exactly two writes —falsein the constructor andtrueonly when the daemon echoes the capability inAUTH_OK— so against an old daemon the client falls through to the unmodified baseProcessUpdate. The premise that an old daemon ignores an unknownAUTH_REQtag is the same mechanism ten existing capability tags already rely on.Results carrying Kad comments are still re-sent in full every poll, because that block has no valuemap. That is unchanged here, but it will now be the only remaining per-poll cost and is worth a separate look.