pub struct ProxyWatcher { /* private fields */ }Expand description
OS proxy watcher: initial snapshot, then debounced changes (equality ignores
captured_at).
Own OS threads: one, and on Linux one more per live source, plus one for
WatchOptions::poll_interval (elsewhere that interval shortens the single thread’s
own wait instead of adding one). Drop joins them.
ProxyWatcher::health for route liveness. Windows, macOS, Linux, Android and iOS;
elsewhere Error::Unsupported. Applies no *_proxy convention of its own: only KDE’s
ProxyType = 4 builds a ProxyEnv, from the variables
kioslaverc names.
The stream is level-triggered: while a subscriber keeps polling, a finite number of
polls always reaches a WatchEvent::Snapshot matching the current
state, a WatchEvent::Error, or None. An Error item does not
end the stream; None comes only once the backend thread has stopped, and is preceded
by a snapshot reporting WatchHealth::stopped. Undelivered snapshots fold into the
newest one, so a subscriber that stops polling loses the history between snapshots,
never current; failures with no snapshot between them fold into
the newest, and undelivered failures are capped at 1024, oldest discarded first. The
trim runs when a snapshot is published, so a failure arriving after a full one holds
the queue at 1025 until the next snapshot trims it back.
One subscriber, not a broadcast. Only the most recently registered waker is kept, and
poll_next takes &mut, so the only way to poll a watcher from more than one task is
to put it behind a lock; after which the task that polled first is parked with its
waker overwritten, and stays parked until something else on this watcher wakes it. To
fan a stream out, poll it in one place and forward.
The waker is woken on the backend thread, which Drop joins, so the last owner of the
watcher must not live inside the waker: dropping it there makes that thread wait for
itself.
Send but not Sync, on every platform. So current,
health, state and poll_now take
&self and do no OS I/O, yet still belong to the thread that owns the watcher, and
Arc<ProxyWatcher> is not Send. Move the watcher into the task that polls it and
forward from there what other threads need. Under the tokio feature watch_channel
is that forwarding already written; without it, one owner republishes
current into a Mutex or a channel after every item. Neither
recovers poll_now, which stays with the owner: send it a request
rather than sharing the watcher.
On Linux the watcher copies the process environment once, on the constructing thread,
and every later re-read takes the desktop, sandbox and kioslaverc variables from that
copy. A variable changed after construction is therefore not seen (set what you need
before constructing one), and the watcher’s own threads never race a concurrent
std::env::set_var. GLib, which the GSettings and portal routes load at run time,
reads variables of its own on those threads; this crate does not control those reads.
use std::future::poll_fn;
use std::pin::Pin;
use futures_core::Stream;
use proxy_watch::{ProxyWatcher, WatchEvent};
let mut watcher = ProxyWatcher::new()?;
println!("current: {:?}", watcher.current().effective);
let next = futures_executor::block_on(poll_fn(|cx| Pin::new(&mut watcher).poll_next(cx)));
match next {
Some(WatchEvent::Snapshot { state, .. }) => {
println!("now: {:?} (live: {})", state.config.effective, state.health.is_fully_live());
}
Some(WatchEvent::Error { error, .. }) => eprintln!("re-read failed: {error}"),
_ => {}
}Implementations§
Source§impl ProxyWatcher
impl ProxyWatcher
Sourcepub fn new() -> Result<Self, Error>
pub fn new() -> Result<Self, Error>
Start watching with WatchOptions::default.
§Errors
Error::Unsupported, Error::Io, Error::Sandboxed (Linux, in a sandbox
with no dconf), Error::CgiHttpProxy (KDE with ProxyType = 4, in a CGI
environment), or a parse error from malformed OS settings.
Sourcepub fn with_options(options: WatchOptions) -> Result<Self, Error>
pub fn with_options(options: WatchOptions) -> Result<Self, Error>
Start watching with explicit options.
Blocks the way read does, and can block for twice as long: the notification
route is armed before the configuration is read, so a change landing between the
two is already queued rather than lost. The GSettings subscription on Linux and the
whole of macOS come up after that read instead, and close the same window with one
forced re-read once live: Linux queues it from spawn, macOS publishes on entering
its run loop. On macOS that subscription and the read each run the same five-second
retry budget, so a configd that is still coming up can hold this call for ten
seconds.
§Errors
Same as ProxyWatcher::new.
Sourcepub fn current(&self) -> ProxyConfig
pub fn current(&self) -> ProxyConfig
The most recent snapshot (no OS I/O; may briefly wait for the internal mutex). Equals the constructor read right after start.
current without copying the configuration: the snapshot itself,
shared. The mutex is held only to clone the Arc, so this suits a caller that asks
on every connection. A later change replaces the snapshot the watcher holds and
leaves the one returned here as it was.
let watcher = proxy_watch::ProxyWatcher::new()?;
let snapshot = watcher.current_shared();
assert_eq!(*snapshot, watcher.current());Sourcepub fn health(&self) -> WatchHealth
pub fn health(&self) -> WatchHealth
Construction-time routes plus any runtime degradation (see WatchHealth).
Does no OS I/O; it may briefly wait for the same internal mutex
ProxyWatcher::current uses.
Sourcepub fn state(&self) -> WatchState
pub fn state(&self) -> WatchState
Sourcepub fn poll_now(&self)
pub fn poll_now(&self)
Request an immediate re-read (same debounce/equality path as a native notify).
Call it when the caller knows something this crate cannot see: a connection attempt failed in a way that might be proxy-related, or the caller’s own network-change watch fired. This crate does not watch the network itself; this call is the hook for that signal.
Returns immediately, and may deliver nothing. A re-read shares a notification’s
equality skip: one that finds the configuration and the health unchanged publishes
no WatchEvent, and asking again does not force one. The Stream reports
change, not completion, and current and state
answer the same before and after such a re-read, so neither shows that this request
landed. WatchEvent::Error says the same for the recovery case.