pub(super) struct Reactor<R: Runtime, M: Mockable> {Show 14 fields
imm: Arc<Immutable<R, M>>,
dir_provider: Arc<dyn NetDirProvider>,
inner: Arc<Mutex<Inner>>,
ipt_watcher: IptsPublisherView,
config_rx: Receiver<Arc<OnionServiceConfig>>,
key_dirs_rx: FileEventReceiver,
key_dirs_tx: FileEventSender,
publish_status_rx: Receiver<PublishStatus>,
publish_status_tx: Sender<PublishStatus>,
upload_task_complete_rx: Receiver<TimePeriodUploadResult>,
upload_task_complete_tx: Sender<TimePeriodUploadResult>,
shutdown_tx: Sender<Void>,
path_resolver: Arc<CfgPathResolver>,
update_from_pow_manager_rx: Receiver<TimePeriod>,
}Expand description
A reactor for the HsDir Publisher
The entrypoint is Reactor::run.
Fields§
§imm: Arc<Immutable<R, M>>The immutable, shared inner state.
dir_provider: Arc<dyn NetDirProvider>A source for new network directories that we use to determine our HsDirs.
inner: Arc<Mutex<Inner>>The mutable inner state,
ipt_watcher: IptsPublisherViewA channel for receiving IPT change notifications.
config_rx: Receiver<Arc<OnionServiceConfig>>A channel for receiving onion service config change notifications.
key_dirs_rx: FileEventReceiverA channel for receiving restricted discovery key_dirs change notifications.
key_dirs_tx: FileEventSenderA channel for sending restricted discovery key_dirs change notifications.
A copy of this sender is handed out to every FileWatcher created.
publish_status_rx: Receiver<PublishStatus>A channel for receiving updates regarding our PublishStatus.
The main loop of the reactor watches for updates on this channel.
When the PublishStatus changes to UploadScheduled,
we can start publishing descriptors.
If the PublishStatus is AwaitingIpts, publishing is
paused until we receive a notification on ipt_watcher telling us the IPT manager has
established some introduction points.
publish_status_tx: Sender<PublishStatus>A sender for updating our PublishStatus.
When our PublishStatus changes to UploadScheduled,
we can start publishing descriptors.
upload_task_complete_rx: Receiver<TimePeriodUploadResult>A channel for sending upload completion notifications.
This channel is polled in the main loop of the reactor.
upload_task_complete_tx: Sender<TimePeriodUploadResult>A channel for receiving upload completion notifications.
A copy of this sender is handed to each upload task.
shutdown_tx: Sender<Void>A sender for notifying any pending upload tasks that the reactor is shutting down.
Receivers can use this channel to find out when reactor is dropped.
This is currently only used in upload_for_time_period.
Any future background tasks can also use this channel to detect if the reactor is dropped.
Closing this channel will cause any pending upload tasks to be dropped.
path_resolver: Arc<CfgPathResolver>Path resolver for configuration files.
update_from_pow_manager_rx: Receiver<TimePeriod>Queue on which we receive messages from the PowManager telling us that a seed has
rotated and thus we need to republish the descriptor for a particular time period.
Implementations§
Source§impl<R: Runtime, M: Mockable> Reactor<R, M>
impl<R: Runtime, M: Mockable> Reactor<R, M>
Sourcepub(super) fn new(
runtime: R,
nickname: HsNickname,
dir_provider: Arc<dyn NetDirProvider>,
mockable: M,
config: &OnionServiceConfig,
ipt_watcher: IptsPublisherView,
config_rx: Receiver<Arc<OnionServiceConfig>>,
status_tx: PublisherStatusSender,
keymgr: Arc<KeyMgr>,
path_resolver: Arc<CfgPathResolver>,
pow_manager: Arc<PowManagerGeneric<R, RendRequest>>,
update_from_pow_manager_rx: Receiver<TimePeriod>,
) -> Self
pub(super) fn new( runtime: R, nickname: HsNickname, dir_provider: Arc<dyn NetDirProvider>, mockable: M, config: &OnionServiceConfig, ipt_watcher: IptsPublisherView, config_rx: Receiver<Arc<OnionServiceConfig>>, status_tx: PublisherStatusSender, keymgr: Arc<KeyMgr>, path_resolver: Arc<CfgPathResolver>, pow_manager: Arc<PowManagerGeneric<R, RendRequest>>, update_from_pow_manager_rx: Receiver<TimePeriod>, ) -> Self
Create a new Reactor.
Sourcepub(super) async fn run(self) -> Result<(), FatalError>
pub(super) async fn run(self) -> Result<(), FatalError>
Start the reactor.
Under normal circumstances, this function runs indefinitely.
Note: this also spawns the “reminder task” that we use to reschedule uploads whenever an upload fails or is rate-limited.
Sourceasync fn run_once(&mut self) -> Result<ShutdownStatus, FatalError>
async fn run_once(&mut self) -> Result<ShutdownStatus, FatalError>
Run one iteration of the reactor loop.
Sourcefn status(&self) -> PublishStatus
fn status(&self) -> PublishStatus
Returns the current status of the publisher
Sourcefn handle_upload_results(&self, results: TimePeriodUploadResult)
fn handle_upload_results(&self, results: TimePeriodUploadResult)
Handle a batch of upload outcomes, possibly updating the status of the descriptor for the corresponding HSDirs.
Sourceasync fn handle_consensus_change(
&mut self,
netdir: Arc<NetDir>,
) -> Result<(), FatalError>
async fn handle_consensus_change( &mut self, netdir: Arc<NetDir>, ) -> Result<(), FatalError>
Maybe update our list of HsDirs.
Sourcefn recompute_hs_dirs(&self) -> Result<(), FatalError>
fn recompute_hs_dirs(&self) -> Result<(), FatalError>
Recompute the HsDirs for all relevant time periods.
Sourcefn compute_time_periods(
&self,
netdir: &Arc<NetDir>,
time_periods: &[TimePeriodContext],
) -> Result<Vec<TimePeriodContext>, FatalError>
fn compute_time_periods( &self, netdir: &Arc<NetDir>, time_periods: &[TimePeriodContext], ) -> Result<Vec<TimePeriodContext>, FatalError>
Compute the TimePeriodContexts for the time periods from the specified NetDir.
The specified time_periods are used to preserve the DescriptorStatus of the
HsDirs where possible.
Sourcefn replace_netdir(&self, new_netdir: Arc<NetDir>) -> Option<Arc<NetDir>>
fn replace_netdir(&self, new_netdir: Arc<NetDir>) -> Option<Arc<NetDir>>
Replace the old netdir with the new, returning the old.
Sourcefn replace_config_if_changed(
&self,
new_config: Arc<OnionServiceConfigPublisherView>,
) -> bool
fn replace_config_if_changed( &self, new_config: Arc<OnionServiceConfigPublisherView>, ) -> bool
Replace our view of the service config with new_config if new_config contains changes
that would cause us to generate a new descriptor.
Sourcefn update_file_watcher(&self)
fn update_file_watcher(&self)
Recreate the FileWatcher for watching the restricted discovery key_dirs.
Sourcefn note_ipt_change(&self) -> PublishStatus
fn note_ipt_change(&self) -> PublishStatus
Read the intro points from ipt_watcher, and decide whether we’re ready to start
uploading.
Sourceasync fn handle_ipt_change(
&mut self,
update: Option<Result<(), FatalError>>,
) -> Result<ShutdownStatus, FatalError>
async fn handle_ipt_change( &mut self, update: Option<Result<(), FatalError>>, ) -> Result<ShutdownStatus, FatalError>
Update our list of introduction points.
Sourceasync fn update_publish_status_unless_waiting(
&mut self,
new_state: PublishStatus,
) -> Result<(), FatalError>
async fn update_publish_status_unless_waiting( &mut self, new_state: PublishStatus, ) -> Result<(), FatalError>
Update the PublishStatus of the reactor with new_state,
unless the current state is AwaitingIpts.
Sourceasync fn update_publish_status_unless_rate_lim(
&mut self,
new_state: PublishStatus,
) -> Result<(), FatalError>
async fn update_publish_status_unless_rate_lim( &mut self, new_state: PublishStatus, ) -> Result<(), FatalError>
Update the PublishStatus of the reactor with new_state,
unless the current state is RateLimited.
Sourceasync fn update_publish_status(
&mut self,
new_state: PublishStatus,
) -> Result<(), Bug>
async fn update_publish_status( &mut self, new_state: PublishStatus, ) -> Result<(), Bug>
Unconditionally update the PublishStatus of the reactor with new_state.
Sourcefn upload_result_to_svc_status(&self) -> Result<(), FatalError>
fn upload_result_to_svc_status(&self) -> Result<(), FatalError>
Update the onion svc status based on the results of the last descriptor uploads.
Sourceasync fn handle_svc_config_change(
&mut self,
config: &OnionServiceConfig,
) -> Result<(), FatalError>
async fn handle_svc_config_change( &mut self, config: &OnionServiceConfig, ) -> Result<(), FatalError>
Update the descriptors based on the config change.
Sourceasync fn handle_key_dirs_change(
&mut self,
event: FileEvent,
) -> Result<(), FatalError>
async fn handle_key_dirs_change( &mut self, event: FileEvent, ) -> Result<(), FatalError>
Update the descriptors based on a restricted discovery key_dirs change.
If the authorized clients from the RestrictedDiscoveryConfig have changed,
this marks the descriptor as dirty for all time periods,
and schedules a reupload.
Recreate the authorized_clients based on the current config.
Returns true if the authorized clients have changed.
Read the authorized RestrictedDiscoveryKeys from config.
Sourcefn mark_all_dirty(&self)
fn mark_all_dirty(&self)
Mark the descriptor dirty for all time periods.
Sourcefn mark_dirty(&self, period: &TimePeriod) -> bool
fn mark_dirty(&self, period: &TimePeriod) -> bool
Mark the descriptor dirty for the specified time period.
Returns true if the specified period is still relevant, and false otherwise.
Sourceasync fn upload_all(&mut self) -> Result<(), FatalError>
async fn upload_all(&mut self) -> Result<(), FatalError>
Try to upload our descriptor to the HsDirs that need it.
If we’ve recently uploaded some descriptors, we return immediately and schedule the upload
to happen after UPLOAD_RATE_LIM_THRESHOLD.
Failed uploads are retried
(see upload_descriptor_with_retries).
If restricted discovery mode is enabled and there are no authorized clients,
we abort the upload and set our status to State::Broken.
For each current time period, we spawn a task that uploads the descriptor to
all the HsDirs on the HsDir ring of that time period.
Each task shuts down on completion, or when the reactor is dropped.
Each task reports its upload results (TimePeriodUploadResult)
via the upload_task_complete_tx channel.
The results are received and processed in the main loop of the reactor.
Returns an error if it fails to spawn a task, or if an internal error occurs.
Sourceasync fn upload_for_time_period(
hs_dirs: Vec<RelayIds>,
netdir: &Arc<NetDir>,
config: Arc<OnionServiceConfigPublisherView>,
params: HsDirParams,
imm: Arc<Immutable<R, M>>,
ipt_upload_view: IptsPublisherUploadView,
authorized_clients: Option<Arc<BTreeMap<HsClientNickname, HsClientDescEncKey>>>,
upload_task_complete_tx: Sender<TimePeriodUploadResult>,
shutdown_rx: Receiver<Void>,
) -> Result<(), FatalError>
async fn upload_for_time_period( hs_dirs: Vec<RelayIds>, netdir: &Arc<NetDir>, config: Arc<OnionServiceConfigPublisherView>, params: HsDirParams, imm: Arc<Immutable<R, M>>, ipt_upload_view: IptsPublisherUploadView, authorized_clients: Option<Arc<BTreeMap<HsClientNickname, HsClientDescEncKey>>>, upload_task_complete_tx: Sender<TimePeriodUploadResult>, shutdown_rx: Receiver<Void>, ) -> Result<(), FatalError>
Upload the descriptor for the time period specified in params.
Failed uploads are retried
(see upload_descriptor_with_retries).
Sourceasync fn upload_descriptor(
hsdesc: String,
netdir: &Arc<NetDir>,
hsdir: &Relay<'_>,
imm: Arc<Immutable<R, M>>,
) -> Result<(), UploadError>
async fn upload_descriptor( hsdesc: String, netdir: &Arc<NetDir>, hsdir: &Relay<'_>, imm: Arc<Immutable<R, M>>, ) -> Result<(), UploadError>
Upload a descriptor to the specified HSDir.
If an upload fails, this returns an Err. This function does not handle retries. It is up
to the caller to retry on failure.
This function does not handle timeouts.
Sourceasync fn upload_descriptor_with_retries(
hsdesc: String,
netdir: &Arc<NetDir>,
hsdir: &Relay<'_>,
ed_id: &str,
rsa_id: &str,
imm: Arc<Immutable<R, M>>,
) -> Result<(), DescUploadRetryError>
async fn upload_descriptor_with_retries( hsdesc: String, netdir: &Arc<NetDir>, hsdir: &Relay<'_>, ed_id: &str, rsa_id: &str, imm: Arc<Immutable<R, M>>, ) -> Result<(), DescUploadRetryError>
Upload a descriptor to the specified HSDir, retrying if appropriate.
Any failed uploads are retried according to a PublisherBackoffSchedule.
Each failed upload is retried until it succeeds, or until the overall timeout specified
by BackoffSchedule::overall_timeout elapses. Individual attempts are timed out
according to the BackoffSchedule::single_attempt_timeout.
This function gives up after the overall timeout elapses,
declaring the upload a failure, and never retrying it again.
See also BackoffSchedule.
Sourceasync fn start_rate_limit(&mut self, delay: Duration) -> Result<(), Bug>
async fn start_rate_limit(&mut self, delay: Duration) -> Result<(), Bug>
Stop publishing descriptors until the specified delay elapses.
Sourceasync fn expire_rate_limit(&mut self) -> Result<(), Bug>
async fn expire_rate_limit(&mut self) -> Result<(), Bug>
Handle the upload rate-limit being lifted.
Return the authorized clients, if restricted mode is enabled.
Returns Ok(None) if restricted discovery mode is disabled.
Returns an error if restricted discovery mode is enabled, but the client list is empty.
Auto Trait Implementations§
impl<R, M> !Freeze for Reactor<R, M>
impl<R, M> !RefUnwindSafe for Reactor<R, M>
impl<R, M> Send for Reactor<R, M>
impl<R, M> Sync for Reactor<R, M>
impl<R, M> Unpin for Reactor<R, M>
impl<R, M> UnsafeUnpin for Reactor<R, M>
impl<R, M> !UnwindSafe for Reactor<R, M>
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
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
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§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.Source§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.Source§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.Source§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.Source§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.Source§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.Source§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.Source§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.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§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 moreSource§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§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 moreSource§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 moreSource§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
Source§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
Source§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.Source§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.Source§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.Source§impl<T> PossiblyOption<T> for T
impl<T> PossiblyOption<T> for T
Source§impl<T> Tap for T
impl<T> Tap for T
Source§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 moreSource§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 moreSource§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 moreSource§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 moreSource§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 moreSource§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 moreSource§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.Source§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.Source§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.Source§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.Source§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.Source§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.Source§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.