diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp index e2cab409c5..4ea1654b7a 100644 --- a/indra/llmessage/message.cpp +++ b/indra/llmessage/message.cpp @@ -1969,6 +1969,19 @@ void LLMessageSystem::enableCircuit(const LLHost &host, bool trusted) cdp->setTrusted(trusted); } +void LLMessageSystem::closeCircuit(const LLHost &host) +{ + // Sent once, unreliably: the circuit is disabled at once, so nothing + // waits on an ack, and where the packet is lost the other end times the + // circuit out as it would have. + if (mCircuitInfo.findCircuit(host)) + { + newMessageFast(_PREHASH_CloseCircuit); + sendMessage(host); + } + disableCircuit(host); +} + void LLMessageSystem::disableCircuit(const LLHost &host) { LL_INFOS("Messaging") << "LLMessageSystem::disableCircuit for " << host << LL_ENDL; diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h index 5a927d5c6a..2cb5f60e94 100644 --- a/indra/llmessage/message.h +++ b/indra/llmessage/message.h @@ -739,6 +739,10 @@ class LLMessageSystem : public LLMessageSenderInterface void enableCircuit(const LLHost &host, bool trusted); void disableCircuit(const LLHost &host); + // Tells the other end of the circuit to host that it is closing, with + // CloseCircuit, then disables it here. The other end stops sending on + // it, rather than resending what goes unacked until it times it out. + void closeCircuit(const LLHost &host); // Use this to establish trust on startup and in response to // DenyTrustedCircuit. diff --git a/indra/llmessage/tests/message_test.cpp b/indra/llmessage/tests/message_test.cpp index ac0f2dca20..8ab7421f61 100644 --- a/indra/llmessage/tests/message_test.cpp +++ b/indra/llmessage/tests/message_test.cpp @@ -148,6 +148,149 @@ namespace CHECK(response->mStatus == HTTP_NOT_FOUND); } + // CloseCircuit alone, as the template the viewer reads has it. + const char* const CLOSE_CIRCUIT_TEMPLATE = + "version 2.0\n" + "{\n" + " CloseCircuit Fixed 0xFFFFFFFD NotTrusted Unencoded\n" + "}\n"; + + // A message template file of the test's own, there while this lives. + class CloseCircuitTemplateFile + { + public: + CloseCircuitTemplateFile(const std::string& path) + : mPath(path) + { + llofstream file(mPath.c_str()); + file << CLOSE_CIRCUIT_TEMPLATE; + } + + ~CloseCircuitTemplateFile() + { + if (LLFile::remove(mPath) != 0) + { + LL_WARNS() << "Could not remove " << mPath << LL_ENDL; + } + } + + CloseCircuitTemplateFile(const CloseCircuitTemplateFile&) = delete; + CloseCircuitTemplateFile& operator=(const CloseCircuitTemplateFile&) = delete; + + private: + std::string mPath; + }; + + // gMessageSystem is the system under test while this lives, and what it + // was before once it ends: disableCircuit asks gMessageSystem for the + // circuit's code. + class CloseCircuitSystemScope + { + public: + explicit CloseCircuitSystemScope(LLMessageSystem& system) + : mPrior(gMessageSystem) + { + gMessageSystem = &system; + } + + ~CloseCircuitSystemScope() + { + gMessageSystem = mPrior; + } + + CloseCircuitSystemScope(const CloseCircuitSystemScope&) = delete; + CloseCircuitSystemScope& operator=(const CloseCircuitSystemScope&) = delete; + + private: + LLMessageSystem* mPrior; + }; + + // A socket of the test's own, standing for the other end of a circuit. + class PeerSocket + { + public: + PeerSocket() + { + start_net(mSocket, mPort); + } + + ~PeerSocket() + { + end_net(mSocket); + } + + PeerSocket(const PeerSocket&) = delete; + PeerSocket& operator=(const PeerSocket&) = delete; + + bool isOpen() const { return mSocket >= 0; } + LLHost host() const { return LLHost("127.0.0.1", mPort); } + + // The next packet to arrive within seconds, or nothing. + std::vector receive(F32 seconds) + { + char buffer[NET_BUFFER_SIZE]; + LLTimer waited; + while (waited.getElapsedTimeF32() < seconds) + { + const S32 size = receive_packet(mSocket, buffer); + if (size > 0) + { + return std::vector(buffer, buffer + size); + } + ms_sleep(5); + } + return {}; + } + + private: + S32 mSocket = -1; + int mPort = NET_USE_OS_ASSIGNED_PORT; + }; + + // The other end of a circuit closed here stops sending on it only where + // it is told so; otherwise it resends what goes unacked until it times + // the circuit out. + TEST_CASE_METHOD(LLMessageSystemTestData, "LLMessageSystem: closeCircuit tells the other end with CloseCircuit", "[LLMessageSystem]") + { + const std::string template_path = mTestConfigDir + mSep + "message_template.msg"; + const CloseCircuitTemplateFile message_template(template_path); + LLMessageSystem system(template_path, NET_USE_OS_ASSIGNED_PORT, 1, 0, 0, false, 5.f, 100.f); + REQUIRE(system.isOK()); + const CloseCircuitSystemScope scope(system); + + PeerSocket peer; + REQUIRE(peer.isOpen()); + system.enableCircuit(peer.host(), false); + REQUIRE(system.mCircuitInfo.findCircuit(peer.host()) != nullptr); + + system.closeCircuit(peer.host()); + + // The six-byte header, then CloseCircuit's number as a Fixed + // message's: all four bytes, high first. + const std::vector packet = peer.receive(1.f); + REQUIRE(packet.size() >= 10); + CHECK(packet[5] == 0); + CHECK(packet[6] == 0xFF); + CHECK(packet[7] == 0xFF); + CHECK(packet[8] == 0xFF); + CHECK(packet[9] == 0xFD); + } + + TEST_CASE_METHOD(LLMessageSystemTestData, "LLMessageSystem: closeCircuit sends nothing to a host it has no circuit with", "[LLMessageSystem]") + { + const std::string template_path = mTestConfigDir + mSep + "message_template.msg"; + const CloseCircuitTemplateFile message_template(template_path); + LLMessageSystem system(template_path, NET_USE_OS_ASSIGNED_PORT, 1, 0, 0, false, 5.f, 100.f); + REQUIRE(system.isOK()); + const CloseCircuitSystemScope scope(system); + + PeerSocket peer; + REQUIRE(peer.isOpen()); + system.closeCircuit(peer.host()); + CHECK(peer.receive(0.25f).empty()); + CHECK(system.mCircuitInfo.findCircuit(peer.host()) == nullptr); + } + TEST_CASE_METHOD(LLMessageSystemTestData, "LLMessageSystem: a system whose template failed to load still closes its socket", "[LLMessageSystem]") // a system whose template failed to load still closes its socket { diff --git a/indra/newview/alregionretirement.cpp b/indra/newview/alregionretirement.cpp index 0db6197eb7..511dc95b6f 100644 --- a/indra/newview/alregionretirement.cpp +++ b/indra/newview/alregionretirement.cpp @@ -270,9 +270,14 @@ void ALRegionRetirement::finish() regionp->saveObjectCache(); const F64 saved = LLTimer::getTotalSeconds(); LLWorld::getInstance()->removeRegion(host); - // As DisableSimulator does: what the simulator still sends is dropped by the message system, not handled for a - // region that is gone. - gMessageSystem->disableCircuit(host); + // The circuit is left for the simulator to close with DisableSimulator, as it would without retirement: until then + // what it sends is acked, its pings answered, and its messages for the region that is gone dropped. Where asked, + // it is closed here instead, the simulator told so with CloseCircuit. + static LLCachedControl close_circuit(gSavedSettings, "AlchemyRetireDepartedRegionsCloseCircuit", false); + if (close_circuit) + { + gMessageSystem->closeCircuit(host); + } const F64 removed = LLTimer::getTotalSeconds(); LL_INFOS("RegionRetirement") << "Retired " << name << ": " << mObjectCount << " objects retired over " << mFrames diff --git a/indra/newview/alregionretirement.h b/indra/newview/alregionretirement.h index a847876890..584ff180ef 100644 --- a/indra/newview/alregionretirement.h +++ b/indra/newview/alregionretirement.h @@ -38,8 +38,9 @@ class LLViewerRegion; // After a teleport the region left behind stays until its simulator sends DisableSimulator, about a minute later, // with its objects, avatars, terrain and probes. Once the arrival has settled, every region the agent is neither in // nor near is retired: marked so that its simulator creates no new objects, its objects killed a frame's budget at a -// time, then removed, and its circuit closed, as DisableSimulator removes one. One that comes near the view again -// meanwhile is kept, and what was killed of it is asked for again. +// time, then removed. Its circuit is left for its simulator to close with DisableSimulator, unless +// AlchemyRetireDepartedRegionsCloseCircuit asks for it to be closed at once, the simulator told so with CloseCircuit. +// One that comes near the view again meanwhile is kept, and what was killed of it is asked for again. class ALRegionRetirement { public: diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml index 08375c4e0e..5075ee26bd 100644 --- a/indra/newview/app_settings/settings_alchemy.xml +++ b/indra/newview/app_settings/settings_alchemy.xml @@ -2894,6 +2894,17 @@ Value 1 + AlchemyRetireDepartedRegionsCloseCircuit + + Comment + Close a retired region's circuit at once, telling its simulator with CloseCircuit, instead of leaving it for the simulator to close when it disconnects the region. + Persist + 1 + Type + Boolean + Value + 0 + AlchemyRevokeObjectPerms Comment diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 5d513c2b5e..50a2024926 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -514,7 +514,8 @@ void process_layer_data(LLMessageSystem *mesgsys, void **user_data) if(!regionp) { - LL_WARNS() << "Invalid region for layer data." << LL_ENDL; + // Once for each simulator: a retired region's simulator sends on until it disables the circuit. + LL_WARNS_ONCE() << "Invalid region for layer data from " << mesgsys->getSender() << LL_ENDL; return; } S32 size; diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp index 08ed753199..4c14d4d08c 100644 --- a/indra/newview/llviewerobjectlist.cpp +++ b/indra/newview/llviewerobjectlist.cpp @@ -459,7 +459,8 @@ void LLViewerObjectList::processObjectUpdate(LLMessageSystem *mesgsys, if (!regionp) { - LL_WARNS() << "Object update from unknown region! " << region_handle << LL_ENDL; + // Once for each region: a retired region's simulator sends on until it disables the circuit. + LL_WARNS_ONCE() << "Object update from unknown region! " << region_handle << LL_ENDL; return; } @@ -743,7 +744,8 @@ void LLViewerObjectList::processCachedObjectUpdate(LLMessageSystem *mesgsys, LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(region_handle); if (!regionp) { - LL_WARNS() << "Object update from unknown region! " << region_handle << LL_ENDL; + // Once for each region: a retired region's simulator sends on until it disables the circuit. + LL_WARNS_ONCE() << "Object update from unknown region! " << region_handle << LL_ENDL; return; } diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp index c11fde4ced..1c87c7c0ff 100644 --- a/indra/newview/llworld.cpp +++ b/indra/newview/llworld.cpp @@ -1094,7 +1094,11 @@ void process_disable_simulator(LLMessageSystem *mesgsys, void **user_data) LLHost host = mesgsys->getSender(); //LL_INFOS() << "Disabling simulator with message from " << host << LL_ENDL; - LLWorld::getInstance()->removeRegion(host); + // A region retired before its simulator disabled it is gone already, and only its circuit is left. + if (LLWorld::getInstance()->getRegion(host)) + { + LLWorld::getInstance()->removeRegion(host); + } mesgsys->disableCircuit(host); }