@@ -234,11 +234,18 @@ class CECServerSocket : public CECMuleSocket
234234 // queue. See WriteDoneAndQueueEmpty for the full reasoning.
235235 int m_notification_dispatch_depth;
236236
237- // EC INC_UPDATE skip-unchanged state. `m_lastEcGenSeen` is the highest
238- // `CKnownFile::s_globalEcGen` value already reflected in this
239- // connection's `m_obj_tagmap` — files with a smaller `m_ecGen` did
240- // not change since the last response and can be skipped this cycle.
237+ // EC INC_UPDATE skip-unchanged state. `m_lastEcGenSeen*` values are
238+ // the highest `CKnownFile::s_globalEcGen` already reflected in the
239+ // client's view *for that particular request path* — files with a
240+ // smaller `m_ecGen` did not change since the last response of that
241+ // path and can be skipped this cycle. Three independent counters
242+ // because the request paths interleave on different schedules:
243+ // * `m_lastEcGenSeen` — `EC_OP_GET_UPDATE` (amulegui)
244+ // * `m_lastEcGenSeenShared` — `EC_OP_GET_SHARED_FILES` (amuleweb)
245+ // * `m_lastEcGenSeenPart` — `EC_OP_GET_DLOAD_QUEUE` (amuleweb)
241246 uint64 m_lastEcGenSeen;
247+ uint64 m_lastEcGenSeenShared;
248+ uint64 m_lastEcGenSeenPart;
242249
243250 // Client opted in to partial-update protocol at auth time (advertised
244251 // `EC_TAG_CAN_PARTIAL_UPDATE`). When set, `Get_EC_Response_GetUpdate`
@@ -249,10 +256,16 @@ class CECServerSocket : public CECMuleSocket
249256 // unchanged files so old clients (which infer deletion from absence)
250257 // keep working unchanged — see `Get_EC_Response_GetUpdate`.
251258 bool m_partialUpdateActive;
252- // Set of file ECIDs sent in the previous INC_UPDATE response. Diffed
253- // against the current encoder snapshot each cycle to compute the
254- // removal list emitted to partial-update-capable clients.
259+ // Set of file ECIDs sent in the previous response for each EC
260+ // request path. Diffed against the current snapshot to compute the
261+ // removal list emitted to partial-update-capable clients. Tracked
262+ // per-path because amulegui uses `EC_OP_GET_UPDATE` (mixed shared +
263+ // partfile, served by `Get_EC_Response_GetUpdate`) while amuleweb
264+ // drives two separate INC_UPDATE streams via `EC_OP_GET_SHARED_FILES`
265+ // and `EC_OP_GET_DLOAD_QUEUE` (each served by its own handler).
255266 std::set<uint32> m_lastSentFileIds;
267+ std::set<uint32> m_lastSentSharedFileIds;
268+ std::set<uint32> m_lastSentPartFileIds;
256269};
257270
258271
@@ -263,6 +276,8 @@ m_conn_state(CONN_INIT),
263276m_passwd_salt(GetRandomUint64()),
264277m_notification_dispatch_depth(0 ),
265278m_lastEcGenSeen(0 ),
279+ m_lastEcGenSeenShared(0 ),
280+ m_lastEcGenSeenPart(0 ),
266281m_partialUpdateActive(false )
267282{
268283 wxASSERT (theApp->ECServerHandler );
@@ -731,7 +746,9 @@ static CECPacket *Get_EC_Response_StatRequest(const CECPacket *request, CLoggerA
731746 return response;
732747}
733748
734- static CECPacket *Get_EC_Response_GetSharedFiles (const CECPacket *request, CFileEncoderMap &encoders)
749+ static CECPacket *Get_EC_Response_GetSharedFiles (const CECPacket *request, CFileEncoderMap &encoders,
750+ uint64 &io_lastEcGenSeen,
751+ bool partial_update_active, std::set<uint32> &io_lastSentFileIds)
735752{
736753 wxASSERT (request->GetOpCode () == EC_OP_GET_SHARED_FILES );
737754
@@ -744,29 +761,69 @@ static CECPacket *Get_EC_Response_GetSharedFiles(const CECPacket *request, CFile
744761
745762 encoders.UpdateEncoders ();
746763
764+ // Skip-unchanged + EC_TAG_FILE_REMOVED is wired only for the
765+ // `EC_DETAIL_UPDATE` polling path that amuleweb uses (`EC_OP_GET_
766+ // SHARED_FILES` re-issued each cycle with an encoder-retained diff
767+ // state) and only when the client opted into the partial-update
768+ // protocol at auth. `EC_DETAIL_FULL` callers (amulecmd `show shared`,
769+ // any one-shot query) still get every alive file as a full tag.
770+ const bool skip_unchanged_path =
771+ partial_update_active && detail_level == EC_DETAIL_UPDATE ;
772+ const uint64 ec_snapshot = skip_unchanged_path
773+ ? CKnownFile::GetGlobalECGen () : 0 ;
774+ const uint64 ec_threshold = io_lastEcGenSeen;
775+
747776 // Snapshot the shared-file list once. GetFileByIndex() does an O(N)
748777 // std::advance over the underlying std::map and re-acquires list_mut
749778 // on every call -- looping it N times is O(N^2) and pegs the main
750779 // thread for tens of minutes on users with tens of thousands of
751780 // shared files (issue #666).
752781 std::vector<CKnownFile*> snapshot;
753782 theApp->sharedfiles ->CopyFileList (snapshot);
783+
784+ // Snapshot the alive set for the partial-update removal diff below.
785+ std::set<uint32> current_file_ids;
786+
754787 for (std::vector<CKnownFile*>::const_iterator it = snapshot.begin ();
755788 it != snapshot.end (); ++it) {
756789 const CKnownFile *cur_file = *it;
757790
758791 if ( !cur_file || (!queryitems.empty () && !queryitems.count (cur_file->ECID ())) ) {
759792 continue ;
760793 }
794+ const uint32 ecid = cur_file->ECID ();
795+ if (skip_unchanged_path) {
796+ current_file_ids.insert (ecid);
797+ if (cur_file->GetECGen () <= ec_threshold) {
798+ // Client already has the latest exported view of
799+ // this file; absence here is "no change", not
800+ // "deleted" — see `EC_TAG_FILE_REMOVED` emission
801+ // below.
802+ continue ;
803+ }
804+ }
761805
762806 CEC_SharedFile_Tag filetag (cur_file, detail_level);
763- CKnownFile_Encoder *enc = encoders[cur_file-> ECID () ];
807+ CKnownFile_Encoder *enc = encoders[ecid ];
764808 if ( detail_level != EC_DETAIL_UPDATE ) {
765809 enc->ResetEncoder ();
766810 }
767811 enc->Encode (&filetag);
768812 response->AddTag (filetag);
769813 }
814+
815+ if (skip_unchanged_path) {
816+ // One EC_TAG_FILE_REMOVED per file that was in the previous
817+ // response but is no longer alive on the server.
818+ for (std::set<uint32>::const_iterator it = io_lastSentFileIds.begin ();
819+ it != io_lastSentFileIds.end (); ++it) {
820+ if (!current_file_ids.count (*it)) {
821+ response->AddTag (CECTag (EC_TAG_FILE_REMOVED , *it));
822+ }
823+ }
824+ io_lastSentFileIds.swap (current_file_ids);
825+ io_lastEcGenSeen = ec_snapshot;
826+ }
770827 return response;
771828}
772829
@@ -933,7 +990,9 @@ static CECPacket *Get_EC_Response_GetClientQueue(const CECPacket *request, CObjT
933990}
934991
935992
936- static CECPacket *Get_EC_Response_GetDownloadQueue (const CECPacket *request, CFileEncoderMap &encoders)
993+ static CECPacket *Get_EC_Response_GetDownloadQueue (const CECPacket *request, CFileEncoderMap &encoders,
994+ uint64 &io_lastEcGenSeen,
995+ bool partial_update_active, std::set<uint32> &io_lastSentFileIds)
937996{
938997 CECPacket *response = new CECPacket (EC_OP_DLOAD_QUEUE );
939998
@@ -944,29 +1003,64 @@ static CECPacket *Get_EC_Response_GetDownloadQueue(const CECPacket *request, CFi
9441003
9451004 encoders.UpdateEncoders ();
9461005
1006+ // Skip-unchanged + EC_TAG_FILE_REMOVED is wired only for the
1007+ // `EC_DETAIL_UPDATE` polling path that amuleweb uses, and only when
1008+ // the client opted into the partial-update protocol at auth. Other
1009+ // callers still get every alive file as a full tag.
1010+ const bool skip_unchanged_path =
1011+ partial_update_active && detail_level == EC_DETAIL_UPDATE ;
1012+ const uint64 ec_snapshot = skip_unchanged_path
1013+ ? CKnownFile::GetGlobalECGen () : 0 ;
1014+ const uint64 ec_threshold = io_lastEcGenSeen;
1015+
9471016 // Snapshot once to avoid re-locking downloadqueue's mutex on every
9481017 // iteration (see Get_EC_Response_GetSharedFiles for the matching
9491018 // shared-files fix in issue #666).
9501019 std::vector<CPartFile*> snapshot;
9511020 theApp->downloadqueue ->CopyFileList (snapshot);
1021+
1022+ std::set<uint32> current_file_ids;
1023+
9521024 for (std::vector<CPartFile*>::const_iterator it = snapshot.begin ();
9531025 it != snapshot.end (); ++it) {
9541026 CPartFile *cur_file = *it;
9551027
9561028 if ( !queryitems.empty () && !queryitems.count (cur_file->ECID ()) ) {
9571029 continue ;
9581030 }
1031+ const uint32 ecid = cur_file->ECID ();
1032+ if (skip_unchanged_path) {
1033+ current_file_ids.insert (ecid);
1034+ if (cur_file->GetECGen () <= ec_threshold) {
1035+ // Client already has the latest exported view of
1036+ // this partfile; absence here is "no change",
1037+ // not "deleted" — see `EC_TAG_FILE_REMOVED`
1038+ // emission below.
1039+ continue ;
1040+ }
1041+ }
9591042
9601043 CEC_PartFile_Tag filetag (cur_file, detail_level);
9611044
962- CPartFile_Encoder * enc = static_cast <CPartFile_Encoder *>(encoders[cur_file-> ECID () ]);
1045+ CPartFile_Encoder * enc = static_cast <CPartFile_Encoder *>(encoders[ecid ]);
9631046 if ( detail_level != EC_DETAIL_UPDATE ) {
9641047 enc->ResetEncoder ();
9651048 }
9661049 enc->Encode (&filetag);
9671050
9681051 response->AddTag (filetag);
9691052 }
1053+
1054+ if (skip_unchanged_path) {
1055+ for (std::set<uint32>::const_iterator it = io_lastSentFileIds.begin ();
1056+ it != io_lastSentFileIds.end (); ++it) {
1057+ if (!current_file_ids.count (*it)) {
1058+ response->AddTag (CECTag (EC_TAG_FILE_REMOVED , *it));
1059+ }
1060+ }
1061+ io_lastSentFileIds.swap (current_file_ids);
1062+ io_lastEcGenSeen = ec_snapshot;
1063+ }
9701064 return response;
9711065}
9721066
@@ -1623,12 +1717,16 @@ CECPacket *CECServerSocket::ProcessRequest2(const CECPacket *request)
16231717 //
16241718 case EC_OP_GET_SHARED_FILES :
16251719 if ( request->GetDetailLevel () != EC_DETAIL_INC_UPDATE ) {
1626- response = Get_EC_Response_GetSharedFiles (request, m_FileEncoder);
1720+ response = Get_EC_Response_GetSharedFiles (request, m_FileEncoder,
1721+ m_lastEcGenSeenShared,
1722+ m_partialUpdateActive, m_lastSentSharedFileIds);
16271723 }
16281724 break ;
16291725 case EC_OP_GET_DLOAD_QUEUE :
16301726 if ( request->GetDetailLevel () != EC_DETAIL_INC_UPDATE ) {
1631- response = Get_EC_Response_GetDownloadQueue (request, m_FileEncoder);
1727+ response = Get_EC_Response_GetDownloadQueue (request, m_FileEncoder,
1728+ m_lastEcGenSeenPart,
1729+ m_partialUpdateActive, m_lastSentPartFileIds);
16321730 }
16331731 break ;
16341732 //
0 commit comments