149 lines
8.6 KiB
Markdown
149 lines
8.6 KiB
Markdown
# Native network manager and simulator lifecycle
|
|
|
|
`NetworkManager`, `Simulator`, `PacketEventDictionary`, and
|
|
`CapsEventDictionary` implement the native Rust boundary corresponding to the
|
|
LibreMetaverse connection and dispatch layer. They use the generated packet
|
|
codecs and the bounded `UDPBase` transport; they do not invoke .NET code or
|
|
start a helper process.
|
|
|
|
## Ownership and execution
|
|
|
|
A manager owns bounded incoming and outgoing channels plus one keepalive
|
|
worker. Each simulator owns a `UDPBase` transport running on a dedicated
|
|
current-thread Tokio executor so the mapped synchronous connection API does not
|
|
depend on the caller already having entered a runtime. Async connection methods
|
|
move blocking compatibility work to short-lived named standard threads and
|
|
return their result through a runtime-neutral one-shot future. Every worker
|
|
holds only a weak manager reference between operations. Dropping the last
|
|
manager and subscription guard therefore releases all manager-owned
|
|
`GridClient` handles, cancels the workers, joins their threads, and closes
|
|
simulator transports.
|
|
|
|
The public `SimulatorCollection` is a shared snapshot collection. Lookup,
|
|
addition, removal, current-simulator selection, and disconnect transitions are
|
|
synchronized. User callbacks receive cloned handles only after internal locks
|
|
have been released. `Subscription` is an RAII guard: dropping or closing it
|
|
removes exactly its registration through a weak registry reference.
|
|
|
|
## Dispatch semantics
|
|
|
|
Packet callbacks preserve registration order. `PacketType::Default` handlers
|
|
are selected before handlers for the concrete packet type. Once any callback
|
|
registered for a packet type requests asynchronous dispatch, that packet type
|
|
remains asynchronous after later removals, matching the conservative C#
|
|
delegate state. A bounded worker executes each asynchronous default/specific
|
|
batch in order. Callback panics are isolated and cannot terminate a processor
|
|
or poison a callback registry.
|
|
|
|
CAPS dispatch snapshots the default (`""`) and named callback chains, then
|
|
invokes them in that order without holding the registry lock. The built-in
|
|
`EnableSimulator` handler honors `Agent.MultipleSims`, ignores duplicate
|
|
endpoints, validates ports, and connects each newly advertised region with its
|
|
reported handle and dimensions. Incoming `DisableSimulator`, `KickUser`,
|
|
`RegionHandshake`, `StartPingCheck`, and generic-streaming UDP packets perform
|
|
their reference lifecycle actions before user dispatch. Ping replies preserve
|
|
the incoming ping identifier and immediately flush pending acknowledgements
|
|
when the simulator reports a nonzero `OldestUnacked` sequence.
|
|
|
|
## Connection and shutdown behavior
|
|
|
|
Connecting reuses an existing endpoint or inserts one simulator atomically,
|
|
starts the bounded manager workers, raises cancellable `SimConnecting`, sends a
|
|
reliable `UseCircuitCode`, and waits up to `Timing.LoginTimeout` for its actual
|
|
protocol ACK. Async compatibility methods use a runtime-neutral blocking
|
|
bridge, so they can be polled without an ambient Tokio runtime. Circuit-code
|
|
changes propagate to every tracked simulator. A default connection stores its
|
|
seed capability, updates `CurrentSim`, raises `SimChanged`, and then raises
|
|
`SimConnected`. A received region handshake is decoded and answered with the
|
|
reference `RegionHandshakeReply` flags before the simulator is marked complete.
|
|
|
|
The keepalive uses the same two-interval disconnect-candidate transition as the
|
|
C# timer: traffic clears the candidate, the first silent interval marks it and
|
|
sends a ping, and the second shuts the manager down with `NetworkTimeout`.
|
|
Concurrent disconnect attempts are serialized so one simulator and one global
|
|
transition are emitted. Per-simulator removal reports `NetworkTimeout`; losing
|
|
the last simulator reports `SimShutdown`. Explicit shutdown preserves the
|
|
caller-provided reason/message and sends `CloseCircuit` only for client or
|
|
network-timeout shutdowns.
|
|
|
|
Promoting an already-connected simulator is a controlled handoff. The manager
|
|
first sends a reliable `UseCircuitCode` followed by the agent/session/circuit
|
|
identified `CompleteAgentMovement`, installs the new seed capability, swaps
|
|
`CurrentSim`, and finally raises `SimChanged` with the previous simulator. The
|
|
old simulator remains tracked until an explicit disconnect so multi-simulator
|
|
traffic can continue. A completed shutdown leaves the manager reusable: a later
|
|
connection creates fresh workers and transports rather than retaining canceled
|
|
ones. Connection attempts made while logout or teardown is still active fail
|
|
with `InvalidOperation`, preventing a reentrant reconnect from racing resource
|
|
cleanup.
|
|
|
|
`RequestLogout`, blocking `Logout`, runtime-neutral `LogoutAsync`, and
|
|
nonblocking `BeginLogout` all send the native `LogoutRequest` packet at most
|
|
once per active handshake. A matching `LogoutReply` is accepted only for the
|
|
current agent and session, raises `LoggedOut` with the returned inventory IDs,
|
|
and then performs a client-initiated shutdown. Blocking calls wait for that
|
|
ordered teardown; asynchronous calls additionally observe caller cancellation.
|
|
`BeginLogout` raises an empty `LoggedOut` only after a bounded network-timeout
|
|
teardown when no reply arrives, matching the reference ordering.
|
|
|
|
Teardown is idempotent and ordered. It cancels active login and logout waits,
|
|
disconnects non-current simulators before the current simulator, stops manager
|
|
workers, clears connection/circuit/session/seed state and parsed login secrets,
|
|
then raises one `Disconnected` event and marks shutdown complete. Reentrant and
|
|
repeated shutdown calls do not repeat events. Nonblocking logout waiters observe
|
|
the lifecycle cancellation and terminate rather than retaining the manager or
|
|
client; their last detached error remains available through
|
|
`NetworkManager::last_logout_error` for diagnostics.
|
|
|
|
## Login behavior
|
|
|
|
The native login path constructs the reference LLSD request, including the C#
|
|
MD5 password form, token/MFA fields, viewer identity, platform metadata,
|
|
requested options, and normalized home/last/region start locations. It uses the
|
|
client's injectable capability HTTP transport, limits redirects and server
|
|
delays, observes linked caller/client cancellation, and reports the mapped
|
|
`LoginStatus` transitions. Only a successful response invokes registered login
|
|
callbacks and proceeds to an ACK-gated initial simulator connection.
|
|
|
|
`LoginResponseData` parses the session and circuit identifiers, home and look-at
|
|
vectors, simulator endpoint and dimensions, seed capability, inventory roots and
|
|
skeletons, buddy rights, account benefits, service URLs, category/config arrays,
|
|
premium packages, and initial outfit. A rejected, malformed, timed-out, or
|
|
canceled login leaves no simulator installed. Passwords, login tokens, MFA
|
|
hashes, session identifiers, seed capabilities, and estate access tokens are
|
|
excluded from `Debug` output and diagnostic messages.
|
|
|
|
## Statistics and milestone gate
|
|
|
|
`SimulatorSimStats` is the native thread-safe counterpart of the reference
|
|
`Simulator.SimStats`. Atomic counters track received/sent packets and bytes,
|
|
resends, drops, pings, pongs, lag, and bandwidth samples. Server `SimStats`
|
|
blocks update the 25 defined simulator measurements with the reference ID and
|
|
rounding rules; unknown IDs are ignored. Debug snapshots contain aggregate
|
|
counters only—never packet bytes, endpoints, capability URLs, or credentials.
|
|
|
|
The offline fake-grid gate composes the completed layers rather than replacing
|
|
their focused tests. It authenticates through injected LLSD HTTP, establishes a
|
|
real loopback UDP circuit, discovers seed capabilities, consumes an
|
|
`EventQueueGet` message that enables a second region, downloads a capability
|
|
asset, promotes that region, and completes the logout handshake. It then proves
|
|
CAPS polling was canceled, download and network workers were joined, sockets
|
|
were closed, state was cleared, and diagnostic formatting contains none of the
|
|
fixture tokens. `tools/check_milestone_08.py` also audits owned source files for
|
|
failure-only bodies and verifies the explicit queue, payload, event, retry, and
|
|
concurrency policy markers remain present.
|
|
|
|
The deterministic tests use loopback fake simulators and require no live grid:
|
|
|
|
```sh
|
|
cargo test -p libremetaverse --test network_manager
|
|
```
|
|
|
|
They cover login hashing, redirects, cancellation and initial handoff alongside
|
|
ACK-gated circuit setup, typed handshake reply/state, ping response, CAPS enable,
|
|
UDP disable, current/seed selection, controlled region handoff, reconnect after
|
|
teardown, all three logout wait modes, reply validation and event order, callback
|
|
ordering and filtering, RAII unregistration, runtime-neutral async calls,
|
|
concurrent registration/removal, reentrant/concurrent disconnect, and keepalive
|
|
timeout reasons.
|