<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>http://osimdev.org/wiki/index.php?action=history&amp;feed=atom&amp;title=OpenSimulator_Internals%2FCode_Map%2FLLUDPServer</id>
	<title>OpenSimulator Internals/Code Map/LLUDPServer - Revision history</title>
	<link rel="self" type="application/atom+xml" href="http://osimdev.org/wiki/index.php?action=history&amp;feed=atom&amp;title=OpenSimulator_Internals%2FCode_Map%2FLLUDPServer"/>
	<link rel="alternate" type="text/html" href="http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/LLUDPServer&amp;action=history"/>
	<updated>2026-08-04T15:28:30Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.3</generator>
	<entry>
		<id>http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/LLUDPServer&amp;diff=120&amp;oldid=prev</id>
		<title>Jwbshaw: first</title>
		<link rel="alternate" type="text/html" href="http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/LLUDPServer&amp;diff=120&amp;oldid=prev"/>
		<updated>2026-07-09T06:27:37Z</updated>

		<summary type="html">&lt;p&gt;first&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= Code Map: LLUDPServer =&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Source:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Namespace:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;OpenSim.Region.ClientStack.LindenUDP&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Key classes:&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;LLUDPServer&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;LLUDPServerShim&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;LLUDPServer&amp;lt;/code&amp;gt; is the UDP transport layer for a single region. It handles&lt;br /&gt;
all inbound and outbound UDP traffic between the region and its connected viewers&lt;br /&gt;
using the Linden Lab UDP (LLUDP) protocol.&lt;br /&gt;
&lt;br /&gt;
One instance exists per region. It is not shared across regions even when multiple&lt;br /&gt;
regions run in the same simulator process.&lt;br /&gt;
&lt;br /&gt;
The region module entry point is &amp;lt;code&amp;gt;LLUDPServerShim&amp;lt;/code&amp;gt;, a thin&lt;br /&gt;
&amp;lt;code&amp;gt;INonSharedRegionModule&amp;lt;/code&amp;gt; wrapper that instantiates and owns&lt;br /&gt;
&amp;lt;code&amp;gt;LLUDPServer&amp;lt;/code&amp;gt;. The shim handles the module lifecycle; the server&lt;br /&gt;
handles the actual network I/O.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Threading Model ==&lt;br /&gt;
&lt;br /&gt;
LLUDPServer runs three persistent threads per region:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Thread !! Started by !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| Inbound receive thread || &amp;lt;code&amp;gt;StartInbound()&amp;lt;/code&amp;gt; via base class &amp;lt;code&amp;gt;OpenSimUDPBase&amp;lt;/code&amp;gt; || Receives raw UDP datagrams from the OS socket and places them on &amp;lt;code&amp;gt;packetInbox&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;IncomingPacketHandler&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;StartInbound()&amp;lt;/code&amp;gt; || Drains &amp;lt;code&amp;gt;packetInbox&amp;lt;/code&amp;gt;, dispatches packets to the correct &amp;lt;code&amp;gt;LLClientView&amp;lt;/code&amp;gt; via &amp;lt;code&amp;gt;ProcessInPacket()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;OutgoingPacketHandler&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;StartOutbound()&amp;lt;/code&amp;gt; || Drives per-client outbound queues, resends, ACKs, and pings on a timer loop.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A fourth engine, &amp;lt;code&amp;gt;OqrEngine&amp;lt;/code&amp;gt; (Outgoing Queue Refill Engine), runs as a&lt;br /&gt;
&amp;lt;code&amp;gt;JobEngine&amp;lt;/code&amp;gt; thread pool to refill per-client outbound queues without&lt;br /&gt;
blocking the outgoing packet handler.&lt;br /&gt;
&lt;br /&gt;
=== Inbound path ===&lt;br /&gt;
&lt;br /&gt;
 OS socket&lt;br /&gt;
   → base class receive thread (OpenSimUDPBase)&lt;br /&gt;
     → PacketReceived()&lt;br /&gt;
       → decode + zerodecode&lt;br /&gt;
       → pending cache check (UseCircuitCode handling)&lt;br /&gt;
       → client lookup by endpoint&lt;br /&gt;
       → ACK processing (appended ACKs, PacketAck packets)&lt;br /&gt;
       → duplicate detection (PacketArchive)&lt;br /&gt;
       → packetInbox.Add()&lt;br /&gt;
         → IncomingPacketHandler thread&lt;br /&gt;
           → LLClientView.ProcessInPacket()&lt;br /&gt;
&lt;br /&gt;
UseCircuitCode packets are handled specially: they are dispatched via&lt;br /&gt;
&amp;lt;code&amp;gt;FireAndForget&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;HandleUseCircuitCode()&amp;lt;/code&amp;gt;, which&lt;br /&gt;
authenticates the circuit, creates a new &amp;lt;code&amp;gt;LLClientView&amp;lt;/code&amp;gt; and&lt;br /&gt;
&amp;lt;code&amp;gt;LLUDPClient&amp;lt;/code&amp;gt;, and then reinjects any packets that arrived while&lt;br /&gt;
the client was being set up (held in a per-endpoint pending queue).&lt;br /&gt;
&lt;br /&gt;
=== Outbound path ===&lt;br /&gt;
&lt;br /&gt;
 Scene code / region modules&lt;br /&gt;
   → LLClientView.SendXxx()&lt;br /&gt;
     → LLUDPServer.SendPacket() / SendPacketData() / SendUDPPacket()&lt;br /&gt;
       → optional zerocoding&lt;br /&gt;
       → OutgoingPacket constructed&lt;br /&gt;
       → LLUDPClient.EnqueueOutgoing()&lt;br /&gt;
         if queue full or not throttled:&lt;br /&gt;
           → SendPacketFinal() → SyncSend() → OS socket&lt;br /&gt;
         else:&lt;br /&gt;
           → sits in per-client priority queue&lt;br /&gt;
             → OutgoingPacketHandler → DequeueOutgoing() → SendPacketFinal()&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Key Structures ==&lt;br /&gt;
&lt;br /&gt;
=== packetInbox ===&lt;br /&gt;
&lt;br /&gt;
A &amp;lt;code&amp;gt;BlockingCollection&amp;amp;lt;IncomingPacket&amp;amp;gt;&amp;lt;/code&amp;gt;. The receive thread&lt;br /&gt;
produces; the &amp;lt;code&amp;gt;IncomingPacketHandler&amp;lt;/code&amp;gt; thread consumes. Bounded only&lt;br /&gt;
by memory.&lt;br /&gt;
&lt;br /&gt;
=== TokenBucket / ThrottleRates ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Throttle&amp;lt;/code&amp;gt; is a scene-wide token bucket capping total outbound&lt;br /&gt;
bandwidth. &amp;lt;code&amp;gt;ThrottleRates&amp;lt;/code&amp;gt; holds the per-client default rates for&lt;br /&gt;
each traffic category (resend, texture, asset, land, wind, cloud, task,&lt;br /&gt;
state, AvatarInfo). Both are configured from &amp;lt;code&amp;gt;[ClientStack.LindenUDP]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Pending cache ===&lt;br /&gt;
&lt;br /&gt;
An &amp;lt;code&amp;gt;ExpiringCacheOS&amp;amp;lt;IPEndPoint, Queue&amp;amp;lt;UDPPacketBuffer&amp;amp;gt;&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
keyed by endpoint. While a new client is being set up after a UseCircuitCode,&lt;br /&gt;
all arriving packets for that endpoint are held here and reinjected once the&lt;br /&gt;
client is ready. Entries expire after 60 seconds.&lt;br /&gt;
&lt;br /&gt;
=== OqrEngine ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;JobEngine&amp;lt;/code&amp;gt; instance named &amp;quot;Outgoing Queue Refill Engine&amp;quot;. When a&lt;br /&gt;
client&amp;#039;s outbound queue runs dry, a refill job is posted here rather than&lt;br /&gt;
blocking the outgoing packet handler thread. This prevents one slow client&lt;br /&gt;
from stalling outbound processing for all others.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Outgoing Packet Handler Timer Loop ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;OutgoingPacketHandler&amp;lt;/code&amp;gt; thread runs a continuous loop with&lt;br /&gt;
three chained timers:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Interval !! Action&lt;br /&gt;
|-&lt;br /&gt;
| 100ms || Check for unacked packets past their RTO; resend via &amp;lt;code&amp;gt;HandleUnacked()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| 500ms || Send accumulated ACKs for all clients via &amp;lt;code&amp;gt;SendAcks()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| 5000ms || Send ping checks to all clients via &amp;lt;code&amp;gt;SendPing()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If no packet was sent in a loop iteration and there are connected clients,&lt;br /&gt;
the thread sleeps 15ms. If there are no clients, it sleeps 100ms.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== ACK Handling ==&lt;br /&gt;
&lt;br /&gt;
Inbound ACKs arrive in two forms:&lt;br /&gt;
* Appended to the tail of any reliable packet (&amp;lt;code&amp;gt;Header.AppendedAcks&amp;lt;/code&amp;gt;).&lt;br /&gt;
* As explicit &amp;lt;code&amp;gt;PacketAck&amp;lt;/code&amp;gt; packets.&lt;br /&gt;
&lt;br /&gt;
Both are processed in &amp;lt;code&amp;gt;PacketReceived()&amp;lt;/code&amp;gt; and passed to&lt;br /&gt;
&amp;lt;code&amp;gt;LLUDPClient.NeedAcks.Acknowledge()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Outbound ACKs are accumulated in &amp;lt;code&amp;gt;LLUDPClient.PendingAcks&amp;lt;/code&amp;gt; and&lt;br /&gt;
sent either appended to the next reliable outbound packet, or as a standalone&lt;br /&gt;
&amp;lt;code&amp;gt;PacketAck&amp;lt;/code&amp;gt; by the 500ms timer, whichever comes first. The&lt;br /&gt;
threshold for immediate ACK sending is 2 * MTU bytes received since the last ACK.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Reliability and Resends ==&lt;br /&gt;
&lt;br /&gt;
Reliable packets are added to &amp;lt;code&amp;gt;LLUDPClient.NeedAcks&amp;lt;/code&amp;gt; after send.&lt;br /&gt;
The 100ms timer calls &amp;lt;code&amp;gt;HandleUnacked()&amp;lt;/code&amp;gt;, which retrieves all packets&lt;br /&gt;
whose age exceeds the client&amp;#039;s current RTO and invokes their&lt;br /&gt;
&amp;lt;code&amp;gt;UnackedMethod&amp;lt;/code&amp;gt; callback (default: &amp;lt;code&amp;gt;ResendUnacked()&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;ResendUnacked()&amp;lt;/code&amp;gt; sets the resent flag on the packet and re-enqueues&lt;br /&gt;
it in the &amp;lt;code&amp;gt;Resend&amp;lt;/code&amp;gt; throttle category.&lt;br /&gt;
&lt;br /&gt;
A client is disconnected if no packet of any kind is received within the ack&lt;br /&gt;
timeout (default 60 seconds; 300 seconds if the client is paused).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== MTU and Zerocoding ==&lt;br /&gt;
&lt;br /&gt;
MTU is fixed at 1400 bytes. Max payload is 1200 bytes.&lt;br /&gt;
&lt;br /&gt;
Packets marked &amp;lt;code&amp;gt;MSG_ZEROCODED&amp;lt;/code&amp;gt; are compressed by run-length encoding&lt;br /&gt;
of zero bytes before send, and decoded on receive. If zerocoding makes a packet&lt;br /&gt;
larger than the unencoded form, the flag is stripped and the unencoded data&lt;br /&gt;
is sent instead.&lt;br /&gt;
&lt;br /&gt;
Packets larger than the MTU are split into multiple datagrams via&lt;br /&gt;
&amp;lt;code&amp;gt;Packet.ToBytesMultiple()&amp;lt;/code&amp;gt; where the packet type supports variable&lt;br /&gt;
blocks. &amp;lt;code&amp;gt;CoarseLocationUpdate&amp;lt;/code&amp;gt; is explicitly excluded from splitting.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Config ==&lt;br /&gt;
&lt;br /&gt;
All keys are in &amp;lt;code&amp;gt;[ClientStack.LindenUDP]&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;OpenSim.ini&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Key !! Default !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;client_socket_rcvbuf_size&amp;lt;/code&amp;gt; || 0 (OS default) || UDP receive buffer size passed to OS.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;scene_throttle_max_bps&amp;lt;/code&amp;gt; || 6250000 || Scene-wide outbound bandwidth cap in bits/sec.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;TextureSendLimit&amp;lt;/code&amp;gt; || 20 || Texture packets queued per OQR event.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;DefaultRTO&amp;lt;/code&amp;gt; || 0 (auto) || Default retransmission timeout in ms.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;MaxRTO&amp;lt;/code&amp;gt; || 0 (auto) || Maximum retransmission timeout in ms.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;AckTimeout&amp;lt;/code&amp;gt; || 60 || Seconds before disconnecting a silent client.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;PausedAckTimeout&amp;lt;/code&amp;gt; || 300 || Seconds before disconnecting a paused client.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;DisableFacelights&amp;lt;/code&amp;gt; || false || Strip facelights from client updates.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;SupportViewerObjectsCache&amp;lt;/code&amp;gt; || true || Enable viewer-side object cache support.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Statistics ==&lt;br /&gt;
&lt;br /&gt;
LLUDPServer registers the following stats under the &amp;lt;code&amp;gt;clientstack&amp;lt;/code&amp;gt;&lt;br /&gt;
category (visible via the &amp;lt;code&amp;gt;show stats&amp;lt;/code&amp;gt; console command):&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;InboxPacketsCount&amp;lt;/code&amp;gt; -- packets waiting in &amp;lt;code&amp;gt;packetInbox&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;IncomingUDPReceivesCount&amp;lt;/code&amp;gt; -- total UDP datagrams received.&lt;br /&gt;
* &amp;lt;code&amp;gt;IncomingPacketsProcessedCount&amp;lt;/code&amp;gt; -- packets dispatched to clients.&lt;br /&gt;
* &amp;lt;code&amp;gt;IncomingPacketsMalformedCount&amp;lt;/code&amp;gt; -- packets that could not be parsed.&lt;br /&gt;
* &amp;lt;code&amp;gt;IncomingPacketsOrphanedCount&amp;lt;/code&amp;gt; -- packets with no matching client.&lt;br /&gt;
* &amp;lt;code&amp;gt;IncomingPacketsResentCount&amp;lt;/code&amp;gt; -- inbound packets flagged as resends by clients.&lt;br /&gt;
* &amp;lt;code&amp;gt;OutgoingUDPSendsCount&amp;lt;/code&amp;gt; -- total UDP datagrams sent.&lt;br /&gt;
* &amp;lt;code&amp;gt;OutgoingPacketsResentCount&amp;lt;/code&amp;gt; -- packets resent due to missing ACK.&lt;br /&gt;
* &amp;lt;code&amp;gt;OutgoingPacketsQueuedCount&amp;lt;/code&amp;gt; -- packets queued across all clients.&lt;br /&gt;
* &amp;lt;code&amp;gt;OQRERequestsWaiting&amp;lt;/code&amp;gt; -- jobs pending in the OqrEngine.&lt;br /&gt;
* &amp;lt;code&amp;gt;AverageUDPProcessTime&amp;lt;/code&amp;gt; -- average ms per inbound UDP receive.&lt;br /&gt;
* &amp;lt;code&amp;gt;ClientLogoutsDueToNoReceives&amp;lt;/code&amp;gt; -- forced disconnects due to ack timeout.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[OpenSimulator_Internals/Package_Overview#OpenSim.Region.ClientStack|Package Overview: ClientStack]]&lt;br /&gt;
* [[OpenSimulator_Internals/Walkthroughs/Avatar_Rez_In_Region|Walkthrough: Avatar Rez In Region]]&lt;br /&gt;
* [[OpenSimulator_Internals/Walkthroughs/Avatar_Logs_Out|Walkthrough: Avatar Logs Out]]&lt;br /&gt;
&lt;br /&gt;
[[Category:OpenSimulator Internals]]&lt;/div&gt;</summary>
		<author><name>Jwbshaw</name></author>
	</entry>
</feed>