Batch RealDelay probes through an isolated Observatory core - #6050
Batch RealDelay probes through an isolated Observatory core#6050eliotcougar wants to merge 13 commits into
Conversation
Merge one delay-test batch into a disposable Xray process, honor the configured profile concurrency, and update each result as its observatory candidates finish.
Build batch configs directly from v2rayNG's typed speed-test models instead of reparsing and validating arbitrary JSON states the app does not generate. Keep custom configs and non-Observatory policy strategies on the existing individual delay path. Use the worker's single completion contract, retain only cancellation and pending-result state that can occur, and isolate subscription-update probes from the live VPN daemon process.
Reserve speed-test naming for future throughput testing and use HEAD as the default Observatory probe method.
Keep cancellation in the batch-test notification while the disposable probe process owns a single active worker. Starting another batch now requests replacement directly, allowing the service to suppress the old worker's completion before handing the new intent to a fresh process instead of emitting an explicit cancellation event that can clear the new UI state.
|
你好,此pr 确实很不错,但是我们不准备接受。
|
|
This is sad. Initially, I tried to publish a PR in xray-core to make the abovementioned objects contextual instead of global, but it was rejected with a comment "You are using xray-core wrong. Can't you see it clearly says never run multiple cores". That PR was tiny in comparison. The approach in this PR is better as it comes with significant performance benefits and keeps the main running core undisturbed. The alternative I considered was to not use the Observatory at all and just run individual short-lived cores in a separate process exactly like it is done now, but that change was not significantly smaller than this, and it had the same large performance footprint for large subscriptions. |
|
你要考虑后期的可维护性,你对 lib 修改的越多后期维护量就越大,并且 xray 的修改对 lib 的影响也越大。 |
This is a valid argument. That doesn't mean the app should have a minimal working feature set or rely on fragile downstream code that may or may not cause issues. The future is bright, and the AI will be able to help with maintaining the app in the future. |
Preserve progressive Observatory batches and incremental result delivery while incorporating the latest accessibility, service reliability, and custom-profile RealDelay serialization changes from upstream.
Clear persisted results on the existing I/O coroutine before starting the probe service, while keeping the cheap visible-list reset immediate for accurate progressive results.
I would like to raise awareness to the issue with running bulk real-delay probes one xray-core per outbound inside the same process as the main core. Last time this proposal has been bundled together with some unrelated features. I tried to separate it cleanly, patched some edge cases (after I saw that guy with 500+ profiles in his app), cleaned-up the code in general, and collected some performance data. Hopefully, this time it will be considered when you have time for it.
Summary
This PR replaces the per-profile RealDelay hot path with a probe plan that runs compatible targets through one short-lived Xray core in a process separate from the VPN daemon.
It preserves progressive results, policy-group selection, cancellation, malformed-profile isolation, the existing RealDelay concurrency setting, subscription auto-test behavior, and fallback support for configurations that cannot be merged.
It depends on AndroidLibXrayLite draft PR 2dust/AndroidLibXrayLite#204.
Existing problem
The current RealDelay implementation creates one coroutine per profile on a limited dispatcher. For each ordinary profile it performs a raw TCP precheck, builds a complete configuration, creates/starts/closes a separate Xray core, and takes two GET samples.
For a large subscription this has four costs:
core.Instancevalues can overlap inside the same native process, including the process hosting the long-running VPN core.Why overlapping Xray cores in one process are unsafe
The Xray user documentation describes operational Xray instances/processes as independent, and its architecture diagram covers a single Xray process. The embedded Go API has additional process-wide state.
XTLS's libXray embedding documentation explicitly warns that Xray-core keeps its system dialer DNS client and outbound manager in process-wide state. Creating a temporary ping/test core can replace that state; closing the temporary core does not restore the previous values; overlapping instances therefore need separate processes.
The exact xray-core revision pinned by this branch (
5ca6f4b7d4dc) confirms it:core.New/core.NewWithContextcallsinternet.InitSystemDialerduring instance construction.dnsClientandobmare package globals.InitSystemDialeroverwrites them; the global outbound manager is also used fordialerProxyresolution.Instance.Closecloses the instance's features but does not restore the managers previously stored in those package globals.A temporary test core can therefore redirect DNS or
dialerProxylookup through another instance's managers, leave the long-running core with replaced process state, or leave globals pointing at features that have already closed.This PR changes
CoreTestServicefrom:RunSoLibV2RayDaemonto a disposable:Probeprocess. Interactive batches run one at a time, the process terminates after the batch, and a back-to-back replacement request is redelivered to a fresh process. Subscription update/testing also moves to:SubscriptionUpdate, separate from the VPN daemon.Process isolation protects the live tunnel even when an unmergeable profile must use the legacy per-profile fallback. The optimized path goes further and avoids overlapping temporary cores by combining compatible targets into one Xray instance.
Proposed flow
Probe-plan construction
CoreConfigContextBuildernow has a probe-specific path that resolves only the outbound dependency graph required for testing. It skips unrelated routing/DNS/runtime sections and shares a lazily decoded profile/subscription snapshot across the complete batch.For every compatible source configuration,
ProbeConfigBuilder:streamSettings.sockopt.dialerProxyreferences to the namespaced dependency;Custom Xray JSON, malformed dependency graphs, unsupported balancer strategies, and policy groups with unsupported fallback semantics are kept out of the combined config and tested through the individual compatibility path.
If Xray rejects a combined configuration despite per-source validation, the app recursively divides only the still-pending profile set. Valid subsets continue through shared cores; an irreducible single profile falls back individually. One bad profile therefore cannot discard the rest of the batch.
Concurrency and policy groups
The existing Concurrent RealDelay tests setting is passed to one native worker pool. Each worker performs one unchanged upstream
BurstObservatory.Checkfor one outbound tag.The limit is therefore enforced over actual network probes, not merely over visible policy-group rows.
Progressive results and UI cost
AndroidLib emits a serialized callback when a target completes. A policy group may publish a new selected delay while other candidates remain, and receives a final
completed=trueupdate after its last candidate.The app persists each result immediately and broadcasts a
RealPingResult(guid, delay)DTO. Including the GUID removes the previous identity-less “something changed” event that forced a full group reload.MainViewModelapplies result DTOs directly to cached rows. It drains results through one serialized 500 ms loop:Finishwaits for any scheduled drain and forces the final pending flush before completing the UI state.The service-event buffer is enlarged for large bursts, while the persisted MMKV values remain authoritative. Progress notifications are throttled separately and count physical work units.
Cancellation and failure behavior
core.NewWithContext, so cancellation reaches active Observatory checks.-1result.End-to-end 500-profile benchmark
Conditions:
/generate_204fixtures with a fixed 100 ms response delaydumpsys meminfosampled approximately once per second739e303f+ AndroidLibb2138986b6fbe5e5+ AndroidLib484a8771Values are medians of three runs; parentheses are observed min-max ranges.
All six measured runs completed all 500 targets without crashes, ANRs, probe failures, or connection timeouts. Upstream performed 500 TCP prechecks followed by 1,000 GET samples; the proposed path performed exactly 500 HEAD samples.
The 3.330-second measured HTTP phase is only about 6.6% above the 3.125-second theoretical floor for 500 targets, 16 workers, and 100 ms responses. Increasing concurrency is therefore not the useful next optimization.
The tradeoff is startup latency: building the complete dependency plan delays the first result by about 0.73 seconds. Caching or directly constructing minimal outbound fragments is the principled follow-up.
500 ms result-flush benchmark
The end-to-end comparison above used the earlier 50 ms coalescing window. A separate finish-aware emulator benchmark compared the pushed 50 ms version (
d30745cc) with the same source using the final 500 ms interval.Conditions: Pixel 5 x86_64 AVD, Android 11 / API 30, Play Store debug, 500 rows, concurrency waves of 16, three measured repetitions after warm-up. Every run included the production
Finishevent.FinishThe deliberate UI tradeoff is up to roughly half a second before the first partial row update. The final batch completion is not extended because
Finishforces the pending flush.Benchmark boundaries
5 s * ceil(targets / 16)network time: about 160 seconds for 500 targets if every worker times out.b6fbe5e5/484a8771. The final review tips (2f9b560b/93273c7) retain that architecture and add lifecycle/cancellation fixes and cleanup.Dependency and review setup
93273c799e258e02905c466ac5d6230a9d1583072f9b560b2cc08be371985103e9c682b60877979dThe generated
libv2ray.aaris intentionally not committed. Until the AndroidLib PR is merged and the submodule/AAR is updated upstream, review builds must build the AAR from PR #204 and place it inV2rayNG/app/libs/libv2ray.aar.Validation
I have been running the earlier version of this code without protections against broken configurations. I experienced no failures.
Completed against the final reviewed tips:
ProbeConfigBuilderTestgo test -race ./...go vet ./...:app:assemblePlaystoreDebuggit diff --checkThe focused Kotlin and Go test files are retained locally for continued development but intentionally are not part of either PR diff.
Fixes #6077
Fixes #6089 (75d88e8)