Observatory: add reliable one-shot outbound probe batches and result notifications#6492
Closed
eliotcougar wants to merge 15 commits into
Closed
Observatory: add reliable one-shot outbound probe batches and result notifications#6492eliotcougar wants to merge 15 commits into
eliotcougar wants to merge 15 commits into
Conversation
Expose instance-scoped observatory update subscriptions so embedders can react when regular or burst probe results change without polling. Report the effective initial probe-cycle deadline, allowing temporary routing overrides to remain active long enough for a fresh result. Clone regular observation snapshots under lock and emit notifications only after result locks are released. Add coverage for subscription lifecycle, regular and burst updates, and configured probe deadlines.
Expose an embedder-facing batch probe interface so third-party applications can compare many outbound handlers through one running Xray instance instead of creating a core per outbound. Validate every tag before dialing, bound concurrency across outbounds, keep samples for each outbound sequential, and publish the completed result set atomically. Make batches cancellable, mutually exclusive with scheduled observation, and safe to close from an update listener. Cover concurrency, sampling, replacement, failed probes, cancellation, overlap, missing handlers, lifecycle shutdown, and synchronous listener cleanup.
Embedder cancellation contexts do not carry Xray's private instance value, so deriving tagged probe dials from the caller context makes the tagged dialer reject every real request. Keep the observer-owned context as the probe parent and bridge caller cancellation into it instead. Preserve the caller's cancellation error and cover the value propagation that real tagged dialing depends on.
A failed outbound probe followed by a failed direct connectivity check identifies an unavailable underlying network, not a dead outbound. Abort the batch with a public sentinel error, cancel the remaining workers, and leave the last complete observation untouched instead of atomically publishing a misleading all-failed snapshot. Cover the error, notification, and snapshot-preservation behavior.
An empty outbound list is a valid completed batch, but the early success path previously retained and republished the prior result map. Replace it with an explicit empty snapshot after cancellation checks, so GetObservation always describes the batch that just completed and never exposes stale routes as fresh results.
Go's HTTP transport may detach DialContext from request cancellation so an in-progress dial can be reused, allowing timed-out probe dials to outlive their worker slot. Create a dedicated transport and timeout-bound Xray context for every measurement, keep that context through the complete response, and cancel it only when the measurement ends. This preserves instance values while preventing lingering dials from escaping the configured batch concurrency bound.
BurstObservatory.Check can be invoked independently of scheduled selectors, including from Xray's reverse path, so it could mutate results while an embedder batch was building or publishing. Track active manual checks and the synchronous publication window under the existing lifecycle lock. Reject overlapping batches and drop manual checks during batch execution/publication without holding locks across probes or callbacks, preserving atomic snapshots and avoiding Close/listener deadlocks.
A burst observer's initial-cycle deadline describes one concurrent sample and cannot safely bound a finite batch with many tags or repeated samples. Extend the batch probe contract with a dedicated deadline calculation based on unique tags, worker waves, sequential samples, configured request timeout, and the optional connectivity check. Share argument normalization with execution, reject duration overflow, and keep the scheduled-observer deadline semantics unchanged.
Scheduled burst samples are intentionally aged out, but applying that policy to a finite batch lets early tags expire while later tags are still being probed. A successful batch can then publish a snapshot that no longer contains all requested measurements. Treat non-positive result validity as non-expiring and use it for one-shot batches. The completed snapshot now remains coherent until a later batch replaces it or the observer is closed.
Health-ping averaging converted the nanosecond sum through int. On ARMv7, valid measurements whose total exceeds roughly 2.1 seconds wrap before division and can produce a negative or otherwise corrupt delay. Divide the int64-backed time.Duration directly and cover multi-second samples with a regression test that also runs successfully under GOARCH=386.
Calling arbitrary subscriber functions synchronously lets a slow or panicking embedder extend probe deadlines, overlap core teardown, and serialize otherwise concurrent observatory work. No callback contract can safely stop a function that refuses to return. Publish coalesced signals through buffered subscription channels instead. Notification is now nonblocking, Xray never executes subscriber code, concurrent updates are serialized by the channel, and observer shutdown closes every subscription explicitly.
The batch API previously accepted any positive concurrency and sample counts. Those values directly size worker pools and retained RTT storage, while ProbeOutbounds could execute plans that ProbeOutboundsDeadline rejected for duration overflow. Apply explicit worker, per-outbound sample, and total-measurement ceilings, and share deadline validation with the execution path. Invalid or overflowing plans now fail before starting any network work or allocating their result snapshot.
Holding every result until the slowest outbound finishes makes large batches appear stalled, even when useful latency measurements are already available. Expose an isolated in-progress observation view and signal after each completed sample. Successful batches promote that view atomically to the stable snapshot, while cancellation or network loss discards it and signals subscribers to restore the previous complete results.
A one-shot probe runs in a disposable process, so it has no meaningful prior batch to restore when cancellation or network loss interrupts the current run. Keep every measurement completed by the current batch, omit unfinished outbounds, and use the returned error to mark the result set incomplete. This preserves useful UI data without manufacturing failures for probes that never finished.
One-shot probes run through Observer, which already validates inputs and serializes batch and manual activity. HealthPing repeated those checks and maintained a separate active snapshot even though completed samples are now retained on every exit path. Keep batch policy at the Observer boundary, replace the current result map when execution starts, and publish each sample directly. Remove the redundant batch lock, active-result wrapper, and success-only final notification while leaving standard Observatory and scheduled BurstObservatory operation unchanged. Update tests to use production update wiring and model one disposable probe batch instead of relying on previous or repeated batches.
Member
|
不打算扩展这个接口了 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds an embedder-oriented Observatory API for running a finite, bounded batch of outbound probes through one already-created Xray instance.
It is intended for applications that need to measure many outbounds on demand but cannot afford to create one Xray process or core instance per outbound. A caller can provide the outbound tags, concurrency limit, and sample count, receive progressive result notifications, and retain the measurements completed by the current batch even if it is cancelled or interrupted.
The PR also adds Observatory result-update notifications and probe-deadline reporting. Besides supporting responsive one-shot probing, these APIs provide the coordination required by downstream cached-route warm-start implementations.
Motivation
Applications commonly need to display the delay of many configured outbounds or evaluate policy-group candidates in one user-initiated operation.
Creating an independent Xray process for every outbound can provide isolation, but it is often impractical:
This feature provides a middle ground: one disposable Xray instance can contain all required outbounds and probe them concurrently with bounded resource usage. Embedders can therefore perform reliable bulk probing without requiring one process per outbound and without running multiple Xray cores in the same process.
Public API
The following optional extension interfaces are added:
ObservatoryBatchProbeProbeOutboundsruns a finite batch through the outbounds already registered in the current Xray instance.The caller controls:
context.Context.ProbeOutboundsDeadlinereports the configured worst-case probe budget for the same parameters. This allows an embedding application to add only its own process-management grace period instead of relying on an arbitrary fixed timeout.ObservatoryUpdateNotifierThe notifier publishes a coalesced, non-blocking signal whenever observable results change.
The signal is deliberately not the result itself and is not a routing decision. After receiving it, a consumer queries
GetObservationor reevaluates its balancer. This keeps delivery lightweight, prevents slow subscribers from blocking probe workers, and avoids exposing mutable internal state.ObservatoryProbeDeadlineThis reports the longest expected time before a scheduled Observatory can publish its initial result. Batch callers use
ProbeOutboundsDeadlineinstead.The interface is implemented by both the regular Observatory and BurstObservatory.
One-shot probing behavior
The BurstObservatory implementation now supports one-shot batches with the following behavior:
maxConcurrency.Check.A one-shot observer must be configured without scheduled subject selectors. Scheduled observation and one-shot probing are intentionally kept mutually exclusive so their result sets and lifecycles cannot race.
The current implementation also applies defensive limits to active workers, samples per outbound, and total retained measurements. These are safety limits for untrusted or erroneous embedder parameters, not recommended operating values.
Progressive and partial results
One-shot probing does not wait for every outbound to finish before exposing useful information.
After each completed sample:
GetObservation.The batch begins with an empty result set. There is no previous batch snapshot: only measurements produced by the current invocation are exposed. Outbounds that have not produced a sample yet are absent.
Samples used by the one-shot batch are non-expiring because the intended lifecycle is:
After
ProbeOutboundsreturns,GetObservationretains every sample completed by that invocation:This makes partial progress useful while avoiding misleading synthetic failures.
Network-loss handling
A failed outbound and an unavailable underlying network are different conditions.
When an outbound probe fails and a connectivity check is configured, the implementation checks direct connectivity using the same cancellable instance context. If the underlying network is unavailable:
ErrObservatoryProbeNetworkUnavailableis returned.This allows an embedding application to report that the test was interrupted by network loss rather than incorrectly presenting every remaining proxy as unavailable.
Probe and connectivity dials honor cancellation at their individual deadlines, including tagged outbound dials. This prevents timed-out requests from remaining active after their result is no longer useful.
Deadline calculation
Batch deadlines are derived from the actual probing parameters:
Overflow and invalid values are rejected.
Scheduled Observatory deadlines are also exposed for regular and burst implementations. This gives downstream applications a configuration-aware safety boundary rather than forcing them to guess how long an initial observation should take.
Warm-start support
The notification and scheduled-deadline additions are also required by downstream route warm-start implementations.
A mobile application may remember the last viable policy-group target for a particular network. After the underlying network changes, it can temporarily restore that target while the newly created core's Observatory performs its first fresh probe cycle.
A correct warm-start flow requires two pieces of information from Xray:
ObservatoryUpdateNotifierallows the embedder to react when a real result changes instead of polling Observatory state every few hundred milliseconds. After a notification, the embedder reevaluates the balancer, stores the fresh viable target, and releases its temporary warm-route override.ObservatoryProbeDeadlinegives the embedder a sensible fallback deadline if no viable update is produced. For regular Observatory it accounts for concurrent or sequential probing. For BurstObservatory it reflects the configured health-ping timeout and optional connectivity check.The update dispatcher is instance-scoped, supports multiple subscribers, coalesces unread notifications, and cannot be blocked by a slow consumer.
These APIs do not implement or persist a route cache inside Xray. Network identification and cache policy remain the embedding application's responsibility.
Compatibility
ObservatoryandBurstObservatoryinterfaces retain their existing methods.GetObservationreturns stable snapshots rather than exposing mutable internal result objects.This PR is independent of XTLS/Xray-core#6483, whose reduced scope concerns instance-owned system dialer state.
Downstream use
This work is intended as the Xray-core prerequisite for:
AndroidLibXrayLite wraps the batch API for mobile embedders. v2rayNG uses it in one focused disposable Android process containing one Xray instance, allowing multiple profiles and policy-group candidates to be probed concurrently without creating one process per outbound.
The same API can be used by other desktop, mobile, or embedded clients that need efficient bulk outbound measurements.
Testing
The implementation includes coverage for:
Focused Observatory and extension tests pass, including race-detector runs for the BurstObservatory and extension packages. A release-style Xray executable also builds successfully.