pub struct SessionManager<S> {
session_initiations: Cache<StartChallenge, MTx<One<Result<StartEstablished<SessionId>, StartErrorType<SessionId>>>>>,
session_notifiers: Arc<OnceLock<(Arc<Mutex<Pin<Box<dyn Sink<IncomingSession, Error = SessionManagerError> + Send>>>>, MTx<Array<(SessionId, ClosureReason)>>)>>,
start_protocol_tx: Arc<OnceLock<MTx<Array<(HoprPseudonym, HoprStartProtocol)>>>>,
active_sessions: Arc<AtomicUsize>,
sessions: Cache<SessionId, SessionSlot>,
msg_sender: Arc<OnceLock<S>>,
pix_toolbox: Arc<OnceLock<PixToolbox>>,
cfg: SessionManagerConfig,
slot_allocated: Arc<Mutex<HashMap<SessionId, Vec<Sender<()>>>>>,
ssa_request_locks: Cache<HoprPseudonym, Arc<Mutex<()>>>,
live_cycle_bytes: Arc<AtomicU64>,
admission_tx: Arc<OnceLock<Mutex<SessionAdmissionSink>>>,
}Expand description
Manages lifecycles of Sessions.
Once the manager is started, the SessionManager::dispatch_message
should be called for each [ApplicationData] received by the node.
This way, the SessionManager takes care of proper Start sub-protocol message processing
and correct dispatch of Session-related packets to individual existing Sessions.
Secondly, the manager can initiate new outgoing sessions via SessionManager::new_session,
probe sessions using SessionManager::ping_session
and list them via SessionManager::active_sessions.
Since the SessionManager operates over the HOPR protocol,
the message transport S is required.
Such transport must also be Clone, since it will be cloned into all the created HoprSession objects.
§SURB balancing
The manager also can take care of automatic SURB balancing per Session.
With each packet sent from the session initiator over to the receiving party, zero to 2 SURBs might be delivered. When the receiving party wants to send reply packets back, it must consume 1 SURB per packet. This means that if the difference between the SURBs delivered and SURBs consumed is negative, the receiving party might soon run out of SURBs. If SURBs run out, the reply packets will be dropped, causing likely quality of service degradation.
In an attempt to counter this effect, there are two co-existing automated modes of SURB balancing: local SURB balancing and remote SURB balancing.
§Local SURB balancing
Local SURB balancing is performed on the sessions that were initiated by another party (and are therefore incoming to us). The local SURB balancing mechanism continuously evaluates the rate of SURB consumption and retrieval, and if SURBs are running out, the packet egress shaping takes effect. This by itself does not avoid the depletion of SURBs but slows it down in the hope that the initiating party can deliver more SURBs over time. This might happen either organically by sending effective payloads that allow non-zero number of SURBs in the packet, or non-organically by delivering KeepAlive messages via remote SURB balancing. Both are governed by the initiator’s estimate of our buffer, so signalling SURB distress is what tells it to keep the organic ones coming.
The egress shaping is done automatically, unless the Session initiator sets the Capability::NoRateControl
flag during Session initiation.
§Remote SURB balancing
Remote SURB balancing is performed by the Session initiator. The SURB balancer estimates the number of SURBs delivered to the other party, and also the number of SURBs consumed by seeing the amount of traffic received in replies. When enabled, a desired target level of SURBs at the Session counterparty is set. According to measured inflow and outflow of SURBs to/from the counterparty, the production of non-organic SURBs is started via keep-alive messages (sent to counterparty) and is controlled to maintain that target level.
In other words, the Session initiator tries to compensate for the usage of SURBs by the counterparty by sending new ones via the keep-alive messages.
The same estimate also governs organic production: once the counterparty is estimated to be at
its target, outgoing Session data packets carry no SURBs at all, and they return to one per packet
as soon as the estimate falls back below target. Without that, a balancer could correctly silence
its keep-alives while organic SURBs kept pouring into a full buffer — where each one evicts an
older SURB and destroys the PIX share it was carrying. A SurbDistress or OutOfSurbs signal
from the counterparty overrides the estimate and restores production immediately, which is what
makes it safe to act on an estimate that forward-path loss can inflate.
This mechanism is configurable via the surb_management field in SessionClientConfig.
§Possible scenarios
There are 4 different scenarios of local vs. remote SURB balancing configuration, but an equilibrium (= matching the SURB production and consumption) is most likely to be reached only when both are configured (the ideal case below):
§1. Ideal local and remote SURB balancing
- The Session recipient (Exit) set the
initial_return_session_egress_rate,max_surb_buffer_durationandmaximum_surb_buffer_sizevalues in theSessionManagerConfig. - The Session initiator (Entry) sets the
target_surb_buffer_sizewhich matches themaximum_surb_buffer_sizeof the counterparty. - The Session initiator (Entry) does NOT set the
Capability::NoRateControlcapability flag when opening Session. - The Session initiator (Entry) sets
max_surbs_per_secslightly higher than themaximum_surb_buffer_size / max_surb_buffer_durationvalue configured at the counterparty.
In this situation, the maximum Session egress from Exit to the Entry is given by the
maximum_surb_buffer_size / max_surb_buffer_duration ratio. If there is enough bandwidth,
the (remote) SURB balancer sending SURBs to the Exit will stabilize roughly at this rate of SURBs/sec,
and the whole system will be in equilibrium during the Session’s lifetime (under ideal network conditions).
§2. Remote SURB balancing only
- The Session initiator (Entry) DOES set the
Capability::NoRateControlcapability flag when opening Session. - The Session initiator (Entry) sets
max_surbs_per_secandtarget_surb_buffer_sizevalues inSurbBalancerConfig
In this one-sided situation, the Entry node floods the Exit node with SURBs, only based on its estimated consumption of SURBs at the Exit. The Exit’s egress is not rate-limited at all. If the Exit runs out of SURBs at any point in time, it will simply drop egress packets.
This configuration could potentially only lead to an equilibrium
when the SurbBalancer at the Entry can react fast enough to Exit’s demand.
§3. Local SURB balancing only
- The Session recipient (Exit) set the
initial_return_session_egress_rate,max_surb_buffer_durationandmaximum_surb_buffer_sizevalues in theSessionManagerConfig. - The Session initiator (Entry) does NOT set the
Capability::NoRateControlcapability flag when opening Session. - The Session initiator (Entry) does NOT set the
SurbBalancerConfigat all when opening Session.
In this one-sided situation, the Entry node does not provide any additional SURBs at all (except the ones that are naturally carried by the egress packets which have space to hold SURBs). It relies only on the Session egress limiting of the Exit node. The Exit will limit the egress roughly to the rate of natural SURB occurrence in the ingress.
This configuration could potentially only lead to an equilibrium when uploading non-full packets (ones that can carry at least a single SURB), and the Exit’s egress is limiting itself to such a rate. If Exit’s egress reaches low values due to SURB scarcity, the upper layer protocols over Session might break.
§4. No SURB balancing on each side
- The Session initiator (Entry) DOES set the
Capability::NoRateControlcapability flag when opening Session. - The Session initiator (Entry) does NOT set the
SurbBalancerConfigat all when opening Session.
In this situation, no additional SURBs are being produced by the Entry and no Session egress rate-limiting takes place at the Exit.
This configuration can only lead to an equilibrium when Entry sends non-full packets (ones that carry at least a single SURB) and the Exit is consuming the SURBs (Session egress) at a slower or equal rate. Such configuration is very fragile, as any disturbances in the SURB flow might lead to a packet drop at the Exit’s egress.
§SURB decay
In a hypothetical scenario of a non-zero packet loss, the Session initiator (Entry) might send a certain number of SURBs to the Session recipient (Exit), but only a portion of it is actually delivered. The Entry has no way of knowing that and assumes that everything has been delivered. A similar problem happens when the Exit uses SURBs to construct return packets, but only a portion of those packets is actually delivered to the Entry. At this point, the Entry also subtracts fewer SURBs from its SURB estimate at the Exit.
In both situations, the Entry thinks there are more SURBs available at the Exit than there really are.
To compensate for a potential packet loss, the Entry’s estimation of Exit’s SURB buffer is regularly
diminished by a percentage of the target_surb_buffer_size, even if no incoming traffic from the
Exit is detected.
This behavior can be controlled via the surb_decay field of SurbBalancerConfig.
§SURB balance and target notification
The Session recipient (Exit) can notify the Session initiator (Entry) periodically about its estimated
number of SURBs for the Session. This can help the Entry to adjust its approximation of that level so
that its Local SURB balancer can better intervene.
This can be set using the surb_balance_notify_period field of SessionManagerConfig for the Exit.
Likewise, the Entry can inform the Exit about its desired SURB buffer target so that the Exit
can better accommodate its Remote SURB balancing.
This can be set using the surb_target_notify field of the SessionManagerConfig of each new Session.
Both mechanisms leverage the Keep Alive message to report the respective values.
§PIX (Protocol for Incentivization of eXits) Protocol Flow
When a Session is opened with the Capability::UsePIX flag, the following protocol
runs between the Entry (initiator) and Exit (recipient) to provide on-chain payment
guarantees for the data relayed through the Session.
§1. PIX Parameter Negotiation (Session Initiation)
During SessionManager::new_session, the Entry encodes its PIX SSA (Session Stealth
Address) parameters — a PixParams quadruple of polys_per_ssa, shares_per_poly,
surplus_shares and the curve suite — into the upper 32 bits of the 64-bit options word
carried in the most significant 64 bits of the StartSession.additional_data field, via
PixParams::into_additional_data. The first two describe how many polynomials and shares each
SSA will use; the third is how many extra shares per polynomial the Entry emits to absorb losses. Those three define
the data quota per SSA, which is polys × (threshold + surplus) × SESSION_MTU — the surplus is priced in rather
than free, since a cycle emits it whether or not any share is lost (see pix_params_to_quota).
The fourth is not a dimension and does not enter the quota: it names the elliptic curve the Entry’s build instantiates PIX over, which fixes the width of every curve-sized field later in the handshake. It is fixed at build time on both sides and is therefore not negotiated — the Exit refuses anything but its own, below.
What is announced is built from the installed SsaShareGenerator’s
SsaGeneratorConfig, never from the caller: the
generator is what produces the shares that go on the wire, so advertising anything else would
let the Session proceed while emitting shares the Exit cannot reconstruct. The dimensions are
therefore not configurable per Session — Capability::UsePIX is the whole per-Session switch,
and a node with no PIX toolbox installed refuses the Session. That check runs before the
initiation challenge slot is reserved, so a node that cannot serve PIX at all cannot have its
challenge slots exhausted by repeated requests for it.
The caller never needs to know the resulting quota in advance: it is computed node-side and
handed back per SSA in AgreedSsaQuota::quota_per_ssa, which is what sizes the deposit.
On the Exit side, check_pix_params validates these parameters against:
- The protocol ranges, which
PixParams::try_from_additional_dataenforces as it unpacks. - The configured
IncomingSessionPixConfig::quota_range(by default ≈227 MiB–908 MiB). Dynamic admission checks the smallestbatch_size × quota_per_ssaup to the configured maximum; fixed admission checks the per-SSA quota itself and retains the configured exact batch. - Optionally,
IncomingSessionPixConfig::enforce_pixrejects Sessions that do not offer PIX. - The Exit only checks the product
polys × (threshold + surplus), not the individual values, so the Entry can split it to suit its computing power. The computation is easily parallelizable in the number of polynomials, but not in threshold. The surplus is inside that product, so redundancy is bought rather than taken: a cycle emitsthreshold + surplusshares per polynomial come what may, and the deposit covers all of them.
If parameters are rejected, a StartErrorReason::UnacceptablePixParams error is returned.
§2. Exit SSA Request (SsaRequest → Entry)
Once the PIX parameters are accepted, the Exit spawns a SessionPixSupervisor, whose opening
RequestSsa action has send_ssa_request create a new SSA commitment via the server-side
SsaReconstructor. This produces an Exit commitment (a group element) that is sent back to
the Entry as a SsaServerCommitmentMessage.
One action, and therefore one message, carries the batch selected during admission: no more than
SupervisorConfig::ssas_per_request SSAs at
contiguous indices, sharing the single params field, since every SSA in a Session uses the same
dimensions. The Entry caps what it will accept at
SessionManagerConfig::max_ssas_per_ssa_request, and rejects an over-cap request in full while
replying with an UnacceptablePixParams StartErrorType, so the Exit does not have to infer the
refusal from a deadline. The default is a batch of one, which is byte-for-byte the unbatched
exchange.
From here the supervisor owns the cycle’s deadlines: max_ssa_delivery_time for the Entry’s
commitment, then max_deposit_wait for the funds, then the recovery deadlines. Missing any of
them closes the Session with ClosureReason::PixFailure. The first two are multiplied by the batch
size, because a batch asks the Entry for that many commitment sets and that many deposits before
any of them can fairly be called late.
§3. Entry SSA Commitment (SsaCommit → Exit)
Upon receiving the SsaServerCommitmentMessage, the Entry’s handle_ssa_request
generates a client commitment using the shared SsaShareGenerator (which is also
used by the packet pipeline to embed PIX shares into return-path SURBs). The client
commitment is combined with the Exit commitment to derive the on-chain deposit address
via HoprPixSpec::group_to_deposit_address.
The Entry then sends one or more SsaClientCommitmentMessages back to the Exit and
emits a HoprSessionOutPixEvent::ReadyToDeposit to the upper layer, signaling that
funds can be deposited at the computed address.
“One or more” is hundreds at the deployed dimensions — one commitment per polynomial, chunked to
packet size — and the Exit can use none of it until every message has landed, since the SSA
commitment is the sum of them all. A single lost packet therefore used to cost the whole Session
on max_ssa_delivery_time, typically after the Entry had already been told to fund the address.
So a partially delivered commitment is repaired rather than waited out. Each arriving message
reports SessionPixEvent::CommitmentProgress, which arms (and re-arms) the supervisor’s
commitment_recommit_interval; if
delivery then goes quiet with the set incomplete, RequestCommitmentRetransmission has
send_commitment_retransmission_request ask the Entry — via an SsaRequest whose
missing scope names the polynomial
runs the reconstructor never received — to re-send exactly those. The Entry answers in
handle_ssa_recommit from the bytes it sent the first time, advancing nothing and announcing no
second deposit. A cycle for which nothing arrived never asks: it has no scope to name, and the
delivery deadline remains what ends it.
§4. Deposit Awaiting (Exit Side)
The Exit receives the client commitment messages in handle_ssa_commit, inserts the
coefficient commitments into the SsaReconstructor, and extracts the deposit address.
It emits HoprSessionOutPixEvent::DepositNeeded to the upper layer with the
AgreedSsaQuota and a channel to confirm the deposit.
A verifiable commitment is what starts the deposit clock, so the supervisor is told
(CommitmentVerified) before anything can be reported against it. A PixDepositObserver task
then forwards every confirmation arriving on that channel as DepositConfirmed: top-ups
accumulate, and the supervisor is what decides when enough has landed. The observer carries no
timeout of its own — max_deposit_wait is the deadline, and a second authority racing the
first is exactly what the supervisor exists to prevent. If the upper layer drops the sender
without ever confirming, the observer reports that (DepositObserverClosed) rather than
letting the deadline run out on a deposit that is never coming.
Once the deposit suffices, the SSA moves to recovering. Its recovery deadlines start when it reaches the front, and its deposit releases the egress gate only while it is there. When a paid front leaves—whether recovered or retired after a tolerated failure—an unfunded successor restores its own bounded predeposit allowance; an already funded successor stays open but starts a fresh served-without-progress ceiling.
§5. SSA Collection, Recovery and Pipelining
As the Entry sends return-path SURBs during the Session, each SURB can carry a PIX
share generated from the client’s polynomial set. The Exit’s SsaReconstructor
collects these shares and reports how far the cycle has got via
HoprSessionInPixEvent::RecoveryProgress; dispatch_pix_event forwards those snapshots to
the supervisor, which uses them both to reset max_recovery_idle and to keep the gate serving.
When the last cycle of a batch reaches the early recovery threshold (≈85%), an
HoprSessionInPixEvent::SsaAlmostRecovered event fires and the supervisor answers with a
RequestSsa action for the next batch — pipelining the costly commitment exchange with the tail
of share collection. If that cycle is still awaiting its own commitment or deposit, the request
waits for funding; if two generations still own cycle state, it waits for one to be fully
released. Those checks are independent of the configurable share-order policy.
Once fully recovered, HoprSessionInPixEvent::SsaRecovered fires, allowing the Exit to
unlock and redeem the deposited funds. The reconstructor releases the live cycle immediately;
the supervisor retains a short tombstone, whose expiry emits RetireSsa to drop that cycle’s
SsaCommitmentGuard and abort its deposit observer. The next batch is requested here if
SsaAlmostRecovered has not already done so. Observers are keyed by SSA index, so retiring one
cycle never cancels a pipelined successor’s.
§6. Unverifiable Shares
Shares are not checked individually. Once a polynomial has collected threshold of them,
the reconstructor interpolates its constant term and compares it against the commitment; if
they disagree, at least one of those shares did not come from the committed polynomial and an
HoprSessionInPixEvent::UnverifiableShares event fires, carrying the reconstructor’s running
total for the SSA rather than a delta.
The first such report closes the Session, unconditionally and with nothing to configure. The
failed polynomial is never retried, and the SSA is the sum of every polynomial’s constant term,
so the cycle is already unrecoverable and can never pay; closing immediately caps what a
malicious Entry is served at threshold packets. The running total is logged rather than
accumulated. See SessionPixSupervisor::on_unverifiable_shares for why no tolerance is offered.
§Configuring PIX at the Exit
The Exit configures PIX via IncomingSessionPixConfig within SessionManagerConfig.
The PixToolbox (holding the SsaShareGenerator and SsaReconstructor) must
be provided via SessionManager::start for PIX to function.
Fields§
§session_initiations: Cache<StartChallenge, MTx<One<Result<StartEstablished<SessionId>, StartErrorType<SessionId>>>>>§session_notifiers: Arc<OnceLock<(Arc<Mutex<Pin<Box<dyn Sink<IncomingSession, Error = SessionManagerError> + Send>>>>, MTx<Array<(SessionId, ClosureReason)>>)>>§start_protocol_tx: Arc<OnceLock<MTx<Array<(HoprPseudonym, HoprStartProtocol)>>>>§active_sessions: Arc<AtomicUsize>Authoritative session count for admission control.
Incremented atomically inside allocate_session_slot before the cache insertion,
and decremented at every removal path (explicit close, eviction, guard rollback).
sessions: Cache<SessionId, SessionSlot>§msg_sender: Arc<OnceLock<S>>§pix_toolbox: Arc<OnceLock<PixToolbox>>§cfg: SessionManagerConfig§slot_allocated: Arc<Mutex<HashMap<SessionId, Vec<Sender<()>>>>>Per-SessionId waiters notified when a new session slot is allocated. Lets message handlers that arrive before the slot insertion completes (e.g. SsaRequest vs SessionEstablished) await the slot once instead of busy-looping with sleeps. Keyed by SessionId so that only waiters for the relevant session are woken.
ssa_request_locks: Cache<HoprPseudonym, Arc<Mutex<()>>>Serialises handle_ssa_request per pseudonym.
Start messages are processed under for_each_concurrent, so without this several
SsaRequests for one pseudonym run at once — and each reads the successor gate’s admission
state before any of them has advanced it. Every racer passes, and the Entry commits to (and
funds) as many batches as the Exit cared to send, which is exactly what the gate exists to
stop. The generator’s own monotonic index is the backstop that keeps this from being a
correctness hole, but it only rejects equal or lower indices: an Exit numbering its batches
upwards races past it.
An async lock rather than a blocking one because the guarded region awaits — commitment generation goes to a blocking pool, and publication awaits the transport.
A moka cache rather than a map, for its idle eviction: a HashMap keyed by pseudonym would
otherwise retain one entry per Session for the life of the process.
live_cycle_bytes: Arc<AtomicU64>Live reconstructor-cycle bytes this node has committed to, summed over every PIX Session.
The counterpart of active_sessions, and the reason that one is not sufficient: a Session is
admitted on a slot count, but what it costs the reconstructor is set by the dimensions its
peer offered. Charged in handle_incoming_session_initiation and returned by
CycleBudgetReservation’s Drop, so no removal path has to remember to decrement it.
admission_tx: Arc<OnceLock<Mutex<SessionAdmissionSink>>>Where incoming Sessions are put to the session server for an admission decision.
Unset on a node that runs no session server — an Entry, or a relay — where there is nobody to
ask and every Session is admitted on this node’s own terms.
Behind a Mutex so every request goes through one sender.
futures::channel::mpsc gives each cloned Sender its own guaranteed slot on top of the
buffer, so sending through a fresh clone each time would let the queue grow with the number
of concurrent initiations — the bound would not bound. try_send never blocks, so the lock
is held only for the enqueue itself.
Implementations§
Source§impl<S> SessionManager<S>where
S: Sink<(DestinationRouting, ApplicationDataOut)> + Clone + Send + Sync + Unpin + 'static,
S::Error: Error + Send + Sync + Clone + 'static,
impl<S> SessionManager<S>where
S: Sink<(DestinationRouting, ApplicationDataOut)> + Clone + Send + Sync + Unpin + 'static,
S::Error: Error + Send + Sync + Clone + 'static,
Sourcepub fn new(cfg: SessionManagerConfig) -> Self
pub fn new(cfg: SessionManagerConfig) -> Self
Creates a new instance given the config.
Sourcepub fn start<T>(
&self,
msg_sender: S,
new_session_notifier: T,
pix: Option<PixToolbox>,
admission: Option<SessionAdmissionSink>,
) -> Result<Vec<AbortHandle>>
pub fn start<T>( &self, msg_sender: S, new_session_notifier: T, pix: Option<PixToolbox>, admission: Option<SessionAdmissionSink>, ) -> Result<Vec<AbortHandle>>
Starts the instance with the given msg_sender Sink
and a channel new_session_notifier used to notify when a new incoming session is opened to us.
Optionally, the PIX processor and event sink can be provided for handling PIX protocol.
If not specified, the SessionManager will not handle PIX protocol.
admission is where incoming Sessions are put to the session server for a per-Session
decision before they are established. Without it — an Entry, or a relay, or any caller that
installed no session server — every Session is admitted on this node’s own configured terms,
which is the behaviour of a node that does not distinguish between targets.
This method must be called prior to any calls to SessionManager::new_session or
SessionManager::dispatch_message.
Sourcepub fn is_started(&self) -> bool
pub fn is_started(&self) -> bool
Check if start has been called and the instance is running.
Sourcefn allocate_session_slot(
&self,
session_id: SessionId,
slot: SessionSlot,
) -> Option<SessionSlotGuard<'_>>
fn allocate_session_slot( &self, session_id: SessionId, slot: SessionSlot, ) -> Option<SessionSlotGuard<'_>>
Atomically allocates a new SessionSlot for session_id and returns an RAII
SessionSlotGuard for it.
Establishing a session involves several fallible steps after the slot has been
inserted. The returned guard rolls the slot back - tearing the partially
established session down via close_session - unless it is
committed.
The active-sessions gauge is incremented here, atomically with the insertion and
the guard creation, precisely so that it is always paired with the guard’s
rollback decrement (performed through close_session). This keeps the gauge
accurate: it is never decremented for a slot that was not counted in the first
place, and every counted slot is decremented exactly once when it leaves the cache.
Returns None if a slot for session_id already exists; in that case nothing is
inserted, the gauge is left untouched, and no guard is produced. The atomic entry
API guarantees that only one concurrent caller can claim the slot for a given
pseudonym (avoiding a TOCTOU race), which also rules out loopback sessions onto
ourselves.
Capacity is enforced by an atomic counter incremented before the cache insertion,
making it impossible for two concurrent callers (with different session IDs) to both
succeed when the cache is already at maximum_sessions.
Sourcepub async fn new_session(
&self,
destination: Address,
target: SessionTarget,
cfg: SessionClientConfig,
) -> Result<HoprSession>
pub async fn new_session( &self, destination: Address, target: SessionTarget, cfg: SessionClientConfig, ) -> Result<HoprSession>
Initiates a new outgoing Session to destination with the given configuration.
If the Session’s counterparty does not respond within
the configured period,
this method returns TransportSessionError::Timeout.
It will also fail if the instance has not been started.
Sourcepub async fn ping_session(&self, id: &SessionId) -> Result<()>
pub async fn ping_session(&self, id: &SessionId) -> Result<()>
Sends a keep-alive packet with the given SessionId.
This currently “fires & forgets” and does not expect nor await any “pong” response.
Sourceasync fn request_deposit_data(
pix_toolbox: &PixToolbox,
session_id: SessionId,
ssa_indices: &[SsaIndex],
) -> Result<Vec<PixDepositData>>
async fn request_deposit_data( pix_toolbox: &PixToolbox, session_id: SessionId, ssa_indices: &[SsaIndex], ) -> Result<Vec<PixDepositData>>
Asks the deposit pool for the data to attach to a batch of SSAs, and collects its answers.
Sourcefn spawn_pix_action_driver(
&self,
session_id: SessionId,
slot: &SessionSlot,
action_rx: AsyncRx<Array<SessionPixAction>>,
reply_routing: DestinationRouting,
) -> AbortHandle
fn spawn_pix_action_driver( &self, session_id: SessionId, slot: &SessionSlot, action_rx: AsyncRx<Array<SessionPixAction>>, reply_routing: DestinationRouting, ) -> AbortHandle
Spawns the task that carries out one Session’s supervisor actions.
The supervisor decides; this executes. Keeping the two apart is what lets the state machine
be pure and exhaustively tested, and it means every side effect a PIX Session can have is
visible in one match.
The task owns an SsaCommitmentGuard per SSA it has requested and one Session retirement
scope. Terminal actions retire through the frontier before releasing their guard; aborting
the task drops every remaining guard and then the Session’s compact frontier. No state grows
with the number of cycles the Session completed.
Sourcefn spawn_exit_keep_alive(
&self,
session_id: SessionId,
slot: &SessionSlot,
msg_sender: S,
is_pix: bool,
announced_surb_target: u64,
)
fn spawn_exit_keep_alive( &self, session_id: SessionId, slot: &SessionSlot, msg_sender: S, is_pix: bool, announced_surb_target: u64, )
Starts the Exit’s single Exit → Entry keep-alive stream and installs the handle that steers it.
Called for every PIX Session, and for any Session with a configured
surb_balance_notify_period — the SURB-level notification and PIX fill are the same packet
sent for different reasons, so they share one stream and one rate controller. See
PixFillControl.
Returns without spawning anything when neither producer applies, which is an Exit Session that is not PIX on a node that has switched the notification off. Such a Session originates nothing, which is what it did before this existed.
Deliberately not passed through the PIX egress gate. These packets carry no payload; gating
them would let an exhausted predeposit budget silence the very signal the Entry needs in order
to fund the Session, turning a stall into a teardown. The bound that does apply is
PixFillControl::admit’s SURB reserve, which is about this node’s own ability to send at
all rather than about what the peer has paid for.
announced_surb_target is the SURB buffer target the Entry asked for, and it is what that
reserve is derived against — see effective_surb_reserve. It has to be, because the
configured reserve is an absolute count while the buffer it is measured against is the Entry’s
choice; a Session whose Entry asks for a shallower buffer than the configured reserve could
otherwise never be filled at all.
Sourcefn stop_pix_fill(&self, session_id: &SessionId)
fn stop_pix_fill(&self, session_id: &SessionId)
Silences a Session’s PIX fill, leaving its SURB-level notification alone.
Called on every path that is about to tear a Session down. close_session aborts the stream
outright, so this is not what stops it — it is what stops the extra traffic in the window
between a supervisor deciding the Session is over and the teardown reaching the stream. Every
packet originated in that window is return-routed to a pseudonym whose SURBs are gone, and one
packet the routing resolver cannot resolve holds up every packet this node originates.
Sourceasync fn report_ssa_request(
&self,
session_id: &SessionId,
slot: Option<&SessionSlot>,
action: SessionPixAction,
ok: bool,
)
async fn report_ssa_request( &self, session_id: &SessionId, slot: Option<&SessionSlot>, action: SessionPixAction, ok: bool, )
Reports an action outcome to a Session’s supervisor, if it still has one.
Sourceasync fn notify_session_error(
&self,
session_id: SessionId,
routing: DestinationRouting,
reason: StartErrorReason,
context: &'static str,
)
async fn notify_session_error( &self, session_id: SessionId, routing: DestinationRouting, reason: StartErrorReason, context: &'static str, )
Best-effort SessionError for an already-established Session, identified by its SessionId.
Best-effort in the strict sense: the outcome is logged and discarded. Every caller is on a path that has already decided the Session is over, so there is nothing a failed send could change — and the peer has a deadline of its own as the backstop either way. What the notice buys is promptness and attribution: the peer tears down in about a round trip, with the reason in its log, instead of waiting out a timer that names the timer rather than the cause.
The peer’s handle_session_error closes the Session on a SessionId-identified error and sends
nothing back, so there is no error exchange to loop.
Sourceasync fn notify_pix_failure(
&self,
session_id: SessionId,
reply_routing: DestinationRouting,
)
async fn notify_pix_failure( &self, session_id: SessionId, reply_routing: DestinationRouting, )
Best-effort notice to the Entry that this Session died for PIX reasons.
Sourceasync fn send_ssa_request(
&self,
session_id: SessionId,
slot: &SessionSlot,
ssa_ids: &[SsaId<HoprPseudonym>],
params: PixParams,
) -> Result<Vec<SsaCommitmentGuard<HoprPixSpec>>>
async fn send_ssa_request( &self, session_id: SessionId, slot: &SessionSlot, ssa_ids: &[SsaId<HoprPseudonym>], params: PixParams, ) -> Result<Vec<SsaCommitmentGuard<HoprPixSpec>>>
Registers an Exit commitment for every index in ssa_indices and asks the Entry to commit to
the matching SSAs — all in a single SsaServerCommitmentMessage.
Driven by the supervisor’s RequestSsa action, which is what
allocates the indices and fixes the dimensions — this only carries it out. Taking them as
arguments rather than re-reading them from the slot keeps one source of truth for what was
negotiated: the commitments registered here and the parameters the message advertises cannot
disagree with what the supervisor is timing.
One message for the whole batch, and one params field covering all of it, which is correct:
every SSA in a Session uses the same negotiated dimensions. The Entry enforces its own ceiling
on how many it will accept (SessionManagerConfig::max_ssas_per_ssa_request) and refuses an
over-cap batch in full, so a batch larger than the peer allows loses the Session — see
SupervisorConfig::ssas_per_request.
Installs no deadline of its own: every timeout that used to be armed here now belongs to the supervisor, which can see the whole cycle rather than just this one step.
The returned SsaCommitmentGuards own their registrations until the caller transfers or drops
them. Handing them out rather than retaining them here is what makes the failure path safe:
this function has fallible steps after the registrations exist — including partway through the
batch — and an early return from any of them releases every one of them. That matters more for
a batch than for a single SSA: without it one failed send would strand every index it had
registered, and since the supervisor does not reuse an index, the Session could never recover.
Sourceasync fn send_commitment_retransmission_request(
&self,
session_id: SessionId,
slot: &SessionSlot,
ssa_id: SsaId<HoprPseudonym>,
params: PixParams,
) -> Result<()>
async fn send_commitment_retransmission_request( &self, session_id: SessionId, slot: &SessionSlot, ssa_id: SsaId<HoprPseudonym>, params: PixParams, ) -> Result<()>
Asks the Entry to re-send the parts of one SSA’s commitment that never arrived.
The Entry’s answer to an SsaRequest is hundreds of packets and the reconstructor can use
none of it until every one has landed, so a single drop used to cost the Session on
max_ssa_delivery_time — after the Entry had already been told to fund the cycle. This is the
repair: the reconstructor says which polynomial runs are missing, and the Entry re-sends
exactly those.
Deliberately not built on send_ssa_request, whose every step is
wrong here. It registers no Exit commitment — the cycle already has one, and registering a
second for the same index is refused as a duplicate — allocates no index, and asks the deposit
pool for nothing, since the deposit data of this cycle travelled with the request that created
it. Nothing it does can earn the Entry a deposit, which is what makes it safe to repeat.
The scope is read here rather than carried in the action: what is missing changes as the burst continues to arrive, and the freshest answer is both smaller and the one that converges.
Sourcepub fn num_active_sessions(&self) -> usize
pub fn num_active_sessions(&self) -> usize
Returns the current number of active sessions.
Sourcepub fn active_sessions(&self) -> Vec<SessionId> ⓘ
pub fn active_sessions(&self) -> Vec<SessionId> ⓘ
Returns SessionIds of all currently active sessions.
Sourcepub fn close_session(&self, id: &SessionId) -> bool
pub fn close_session(&self, id: &SessionId) -> bool
Explicitly closes the session with the given id.
Removes the entry from the internal session cache, closes the data channel,
and aborts any auxiliary tasks. Returns true if a session was found and
closed, false otherwise.
This avoids waiting for the idle timeout (time_to_idle) or the LRU
capacity bound to evict the entry, which is the desired behaviour when
the caller (e.g. REST DELETE /session) knows the session is finished.
One case departs from that: an Exit-side PIX Session with
fill.drain_after_close on and spendable SURBs
left starts a SURB drain instead of being torn down (see begin_surb_drain). The data path
still ends at once, but the session slot and the keep-alive stream stay in place until the
supervisor closes the Session — whether because the funded cycle recovered or because the
drain was refused — and true then means “found, and now draining” rather than “found and
closed”.
Sourcefn begin_surb_drain(
&self,
id: &SessionId,
reason: ClosureReason,
) -> Option<bool>
fn begin_surb_drain( &self, id: &SessionId, reason: ClosureReason, ) -> Option<bool>
Answers an Exit-side close by draining the Session’s buffered SURBs, when that is worth doing.
A PIX Session can end long before the cycle it was funded for does. The deposit that cycle was paid is released only once its whole emission has ridden back to the Entry, and the address it sits at derives from both nodes’ commitments — so tearing the Session down mid-cycle strands money both sides have already parted with. When the Exit still holds enough SURBs to finish, it keeps the keep-alive stream running instead, and the Session’s slot with it, until the supervisor closes the Session for good.
Returns None when this close was not answered here, which is every case the caller must
handle exactly as it always has; Some(v) is the value
close_session_with_reason should return. Some currently
always carries true — a drain is only ever started for a Session that was found — but the
value is returned rather than assumed so that the two outcomes stay distinguishable if a
future refusal path wants to report one.
The order of the checks is load-bearing:
- the configuration, so that with
drain_after_close(orfill.enabled) off nothing below runs and every close path is byte-for-byte what it was; - the slot, by
getrather thanremove— the lookup also refreshes the idle timer the drain lives on; - an Exit-side PIX Session, so an Entry-side or non-PIX Session falls straight through;
- an already-draining Session: a
WriteClosed/EmptyReadis the end-of-stream our own ingress abort produces and is absorbed, while anything else — a second operator close, or a peerSessionError— forces the hard teardown, which is the operator’s way to overrule a drain; - a PIX failure already tearing the Session down, which nothing here should interfere with;
- the SURB estimate, which is the one half of the predicate this layer can answer — see the comment on it for why answering it here rather than leaving it all to the supervisor matters;
- and only then the stream is put into drain mode, before the supervisor is told, so that no packet can be classified as the reserve-exempt notification in the window between the supervisor deciding and the stream learning.
What it does not do is decrement active_sessions or remove the slot: the supervisor’s own
Close teardown does both, whether the drain completes or is refused a channel hop later.
Balancer, KeepAlive, SurbNotifyDelay, PixActionDriver and the deposit observers are
all left running; only the ingress is aborted, so the session server sees its end of the
stream as promptly as it would have.
Sourcefn close_session_with_reason(
&self,
id: &SessionId,
reason: ClosureReason,
) -> bool
fn close_session_with_reason( &self, id: &SessionId, reason: ClosureReason, ) -> bool
close_session with the reason spelled out.
The reason is what the operator reads when a PIX Session stops, and the failures are hard to tell apart from the outside, so a caller that knows which one it is says so.
true means the Session was found and dealt with. Since
begin_surb_drain, “dealt with” also covers “found, and now
draining its remaining SURBs into a funded cycle” — in which case the slot is deliberately
left in place until the supervisor’s own teardown removes it.
Sourcepub fn update_surb_balancer_config(
&self,
id: &SessionId,
config: SurbBalancerConfig,
) -> Result<()>
pub fn update_surb_balancer_config( &self, id: &SessionId, config: SurbBalancerConfig, ) -> Result<()>
Updates the configuration of the SURB balancer on the given SessionId.
Returns an error if the Session with the given id does not exist, or
if it does not use SURB balancing.
Sourcepub fn get_surb_balancer_config(
&self,
id: &SessionId,
) -> Result<Option<SurbBalancerConfig>>
pub fn get_surb_balancer_config( &self, id: &SessionId, ) -> Result<Option<SurbBalancerConfig>>
Retrieves the configuration of SURB balancing for the given Session.
Returns an error if the Session with the given id does not exist.
Sourcepub fn get_surb_level_estimates(&self, id: &SessionId) -> Result<(u64, u64)>
pub fn get_surb_level_estimates(&self, id: &SessionId) -> Result<(u64, u64)>
Gets estimations produced/received and consumed SURBs by the Session.
For an outgoing Session (Entry) the pair is the number of SURBs sent (by us) and used (by the Exit). For an incoming Session (Exit) the pair is the number of SURBs received (from Entry) and used (by us).
Returns an error if the Session with the given id does not exist.
Sourcepub async fn dispatch_pix_event(
&self,
event: HoprSessionInPixEvent,
) -> Result<()>
pub async fn dispatch_pix_event( &self, event: HoprSessionInPixEvent, ) -> Result<()>
Forwards a PIX protocol observation to the Session’s supervisor.
The manager no longer interprets these. Deciding what an early-recovery signal or a failed share means for a Session’s lifecycle needs the whole picture — which SSAs are in flight, what phase each is in, how much service has been consumed — and that lives in the supervisor.
A NonExistingSession error is expected traffic rather than a fault: acknowledgements
outlive their Session by up to the reconstructor’s ack window, so events for a just-closed
Session are routine. Callers distinguish it for exactly that reason.
Sourcepub fn mark_return_path_degraded(
&self,
destination: &NodeId,
grace: Duration,
) -> Vec<HoprPseudonym> ⓘ
pub fn mark_return_path_degraded( &self, destination: &NodeId, grace: Duration, ) -> Vec<HoprPseudonym> ⓘ
Marks the return path to destination as degraded on every Session routed there.
The Session layer cannot tell a dead return path from a peer with nothing to say – both
simply stop consuming SURBs – so the judgement is made where sibling paths can be compared
and delivered here. Sessions that did not opt in ignore the mark; the rest stop trusting
their counterparty buffer estimate for grace.
Returns the pseudonyms of the Sessions that were marked (empty when nothing routes there).
The caller uses these to advance each Session’s SURB generation: the return path to
destination has just been re-planned, so the fresh SURB batch must supersede the SURBs the
counterparty still holds for the dead path.
Sourcepub fn dispatch_message(
&self,
pseudonym: HoprPseudonym,
in_data: ApplicationDataIn,
) -> Result<DispatchResult>
pub fn dispatch_message( &self, pseudonym: HoprPseudonym, in_data: ApplicationDataIn, ) -> Result<DispatchResult>
The main method to be called whenever data are received.
It tries to recognize the message and correctly dispatches either the Session protocol or Start protocol messages.
If the data are not recognized, they are returned as DispatchResult::Unrelated.
Sourcefn reserve_cycle_budget(
&self,
params: &PixParams,
ssas_per_request: usize,
) -> Option<Arc<CycleBudgetReservation>>
fn reserve_cycle_budget( &self, params: &PixParams, ssas_per_request: usize, ) -> Option<Arc<CycleBudgetReservation>>
Charges params’ worth of reconstructor state against the node-wide budget.
Returns None if the node has already committed to as much as
IncomingSessionPixConfig::max_live_cycle_bytes allows — the caller must then refuse the
Session, because nothing later in establishment can give the memory back.
A CAS loop rather than an unconditional fetch_add with a rollback: an add that is
provisionally over the ceiling is briefly visible to every concurrent initiation, and with
enough of them arriving at once the budget would appear exhausted to Sessions that do fit.
Sourceasync fn request_admission(
&self,
session_id: SessionId,
req: &StartInitiation<SessionTarget, HoprSessionCapabilities>,
offered: Option<PixParams>,
) -> Result<SessionAdmissionDecision, StartErrorReason>
async fn request_admission( &self, session_id: SessionId, req: &StartInitiation<SessionTarget, HoprSessionCapabilities>, offered: Option<PixParams>, ) -> Result<SessionAdmissionDecision, StartErrorReason>
Asks the session server on what terms this Session may be admitted.
Fails closed. A refusal, a server that does not answer in time, a full request channel and a dropped reply all end the Session here rather than falling back to the node’s own terms: falling back would mean an overloaded or wedged server silently disables every rule the operator wrote, and the peer would be served on terms nobody chose.
The two failure kinds are reported differently because they call for different responses. A
server that answered Err has decided, and re-asking will not change the answer, so the peer
is told StartErrorReason::TargetNotAdmitted. Everything else is this node being unable to
ask right now, which is transient, so the peer is told StartErrorReason::Busy and may
retry.
A node with no session server has nobody to ask, and admits on its own terms.
Sourcefn check_pix_params(
&self,
challenge: StartChallenge,
offered: Option<PixParams>,
policy: &EffectivePixAdmission,
) -> Option<(PixParams, usize)>
fn check_pix_params( &self, challenge: StartChallenge, offered: Option<PixParams>, policy: &EffectivePixAdmission, ) -> Option<(PixParams, usize)>
Checks the offer against the terms this Session was admitted on.
Judged against policy — the node’s configuration as narrowed by the session server’s
decision for this Session — rather than against the node’s configuration directly, which is
what makes the accepted quota a property of the target as well as of the node.
offered is what decode_pix_offer read, so this is only the
policy question; the wire has already been judged.
Returns the validated parameters and selected SSA batch size, or None if the offer cannot
satisfy that policy.
async fn handle_incoming_session_initiation( &self, pseudonym: HoprPseudonym, session_req: StartInitiation<SessionTarget, HoprSessionCapabilities>, ) -> Result<()>
async fn handle_session_established( &self, est: StartEstablished<SessionId>, ) -> Result<()>
async fn handle_session_error( &self, error_type: StartErrorType<SessionId>, ) -> Result<()>
async fn handle_keep_alive( &self, msg: KeepAliveMessage<SessionId>, ) -> Result<()>
Sourceasync fn handle_ssa_commit(
&self,
pseudonym: HoprPseudonym,
msg: SsaClientCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixCommitmentProof>,
) -> Result<()>
async fn handle_ssa_commit( &self, pseudonym: HoprPseudonym, msg: SsaClientCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixCommitmentProof>, ) -> Result<()>
Handled by the Exit, when Entry replies with PIX commitment
Sourceasync fn refuse_ssa_request(
&self,
session_id: SessionId,
routing: DestinationRouting,
)
async fn refuse_ssa_request( &self, session_id: SessionId, routing: DestinationRouting, )
Tells the Exit that its SsaServerCommitmentMessage was refused, and tears down this half
of the Session.
Without the notice the refusal is invisible to the Exit, and it has no way to recover from it.
Its supervisor registered an Exit commitment per requested index and armed each cycle’s
commitment deadline the moment the request went out; it will never receive an SsaCommit, and
nothing can make it re-ask, because a new RequestSsa only comes from share-recovery events and
no shares exist for a cycle the Entry never committed to. So it serves the Session
unincentivized for the whole batch-scaled max_ssa_delivery_time and then closes it as
CommitmentTimeout — a reason that names the clock rather than the refusal, on the one node
whose operator can act on it.
Closing this half too is what stops the Entry sitting on a Session that can never make PIX progress: a refusal is terminal either way, since the Exit re-derives every request from state that cannot drift within a Session, so a later one would be refused identically. Left alone the slot would survive until the idle timeout.
No new capability is handed to an attacker by closing on a refusal: an SsaRequest only reaches
here Sphinx-authenticated and with pseudonym == session_id, so only the Exit can produce one —
and the Exit can already close the Session whenever it likes.
Sourceasync fn handle_ssa_recommit(
&self,
pseudonym: HoprPseudonym,
session_slot: &SessionSlot,
msg: SsaServerCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixDepositPayload>,
) -> Result<()>
async fn handle_ssa_recommit( &self, pseudonym: HoprPseudonym, session_slot: &SessionSlot, msg: SsaServerCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixDepositPayload>, ) -> Result<()>
Handled by the Entry, when the Exit says it never received parts of a commitment this node already published.
Re-sends the named polynomial commitments and nothing else. Emits no
ReadyToDeposit: the deposit address of a cycle is
fixed when it is first committed, and the caller was told it then, so announcing it again
would invite a second deposit against one quota.
A failure here is reported to the caller of the Start-message dispatcher and otherwise
dropped. It is deliberately not refuse_ssa_request, which closes
the Session: an Exit that asks for a cycle this node cannot serve is asking for something
already lost, and its own commitment deadline ends the Session either way — whereas closing on
a request that merely raced our own state would throw away a Session that was about to work.
Two separate bounds keep this from being a packet-amplification lever, because one request may
name a whole cycle’s polynomials and the answer to that is the entire commitment burst again.
The generator bounds how often it answers for one cycle
(MAX_COMMITMENT_RETRANSMISSIONS); this
function bounds how many cycles one request may set going. Neither stands in for the other —
the first bounds repeats, the second bounds breadth.
Sourceasync fn handle_ssa_request(
&self,
pseudonym: HoprPseudonym,
msg: SsaServerCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixDepositPayload>,
) -> Result<()>
async fn handle_ssa_request( &self, pseudonym: HoprPseudonym, msg: SsaServerCommitmentMessage<SessionId, HoprPixGroupElement, HoprPixDepositPayload>, ) -> Result<()>
Handled by the Entry, when the Exit sends PIX initiation request
Trait Implementations§
Auto Trait Implementations§
impl<S> !RefUnwindSafe for SessionManager<S>
impl<S> !UnwindSafe for SessionManager<S>
impl<S> Freeze for SessionManager<S>
impl<S> Send for SessionManager<S>
impl<S> Sync for SessionManager<S>
impl<S> Unpin for SessionManager<S>
impl<S> UnsafeUnpin for SessionManager<S>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Conv for T
impl<T> Conv for T
§impl<T> FmtForward for T
impl<T> FmtForward for T
§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.§fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
§impl<T> FutureExt for T
impl<T> FutureExt for T
§fn with_context(self, otel_cx: Context) -> WithContext<Self>
fn with_context(self, otel_cx: Context) -> WithContext<Self>
§fn with_current_context(self) -> WithContext<Self>
fn with_current_context(self) -> WithContext<Self>
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read more§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read more§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.§impl<T> Pointable for T
impl<T> Pointable for T
§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
§impl<T> Tap for T
impl<T> Tap for T
§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read more§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read more§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read more§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read more§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read more§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read more§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.