@@ -125,7 +125,7 @@ fn produce_log_stream_blocking(
125125 for block_num in from..=to {
126126 // Check the deadline before starting each block so we
127127 // don't begin reading after the caller's timeout.
128- if std :: time :: Instant :: now ( ) > deadline {
128+ if Instant :: now ( ) > deadline {
129129 let _ = sender. blocking_send ( Err ( ColdStorageError :: StreamDeadlineExceeded ) ) ;
130130 return ;
131131 }
@@ -149,7 +149,7 @@ fn produce_log_stream_blocking(
149149 for result in iter {
150150 // Per-receipt deadline check bounds iteration cost across
151151 // blocks with many receipts.
152- if std :: time :: Instant :: now ( ) > deadline {
152+ if Instant :: now ( ) > deadline {
153153 let _ = sender. blocking_send ( Err ( ColdStorageError :: StreamDeadlineExceeded ) ) ;
154154 return ;
155155 }
@@ -165,7 +165,7 @@ fn produce_log_stream_blocking(
165165 // so without this check a single block with thousands
166166 // of matching logs can run arbitrarily past the
167167 // deadline.
168- if std :: time :: Instant :: now ( ) > deadline {
168+ if Instant :: now ( ) > deadline {
169169 let _ = sender. blocking_send ( Err ( ColdStorageError :: StreamDeadlineExceeded ) ) ;
170170 return ;
171171 }
@@ -226,10 +226,11 @@ fn produce_log_stream_blocking(
226226/// `tokio::time::timeout`. Callers that need fail-fast behavior on
227227/// stuck I/O should apply their own timeout at the call site.
228228/// - **Writes** (`append_block`, `append_blocks`, `truncate_above`,
229- /// `drain_above`) record elapsed time against `write_timeout` and
230- /// emit a [`tracing::warn!`] on overrun, but the commit is
231- /// uninterruptible: `write_timeout` is an SLO/alerting signal only,
232- /// not a hard abort.
229+ /// `drain_above`) are uninterruptible MDBX commits. The handle
230+ /// measures end-to-end latency (including `write_sem` wait and the
231+ /// read drain) against `write_timeout` and emits a `tracing::warn!`
232+ /// on overrun via the `ColdStorageBackend::write_timeout` accessor;
233+ /// `write_timeout` is an SLO/alerting signal only, not a hard abort.
233234#[ derive( Clone ) ]
234235pub struct MdbxColdBackend {
235236 /// The MDBX environment.
@@ -239,7 +240,7 @@ pub struct MdbxColdBackend {
239240 /// lookups do NOT consult this deadline — see the type-level docs.
240241 read_timeout : Duration ,
241242 /// Advisory deadline for write operations. Writes that exceed this are
242- /// logged via [ `tracing::warn!`] but still report success.
243+ /// logged via `tracing::warn!` but still report success.
243244 write_timeout : Duration ,
244245}
245246
@@ -262,17 +263,30 @@ impl MdbxColdBackend {
262263 /// lookups (`get_header`, `get_transaction`, etc.) do NOT consult
263264 /// this deadline — see the type-level docs on [`MdbxColdBackend`]
264265 /// for the exemption rationale and its operational implications.
266+ ///
267+ /// # Panics
268+ ///
269+ /// Panics if `read_timeout` is zero — a zero deadline is a
270+ /// configuration mistake, not a "disable" signal, and the trait
271+ /// contract requires a real bound.
265272 #[ must_use]
266- pub const fn with_read_timeout ( mut self , read_timeout : Duration ) -> Self {
273+ pub fn with_read_timeout ( mut self , read_timeout : Duration ) -> Self {
274+ assert ! ( !read_timeout. is_zero( ) , "read_timeout must be non-zero" ) ;
267275 self . read_timeout = read_timeout;
268276 self
269277 }
270278
271279 /// Set the advisory write deadline. Writes exceeding this threshold
272- /// emit a [ `tracing::warn!`] but still report success to the caller;
280+ /// emit a `tracing::warn!` but still report success to the caller;
273281 /// MDBX commits are uninterruptible.
282+ ///
283+ /// # Panics
284+ ///
285+ /// Panics if `write_timeout` is zero. See
286+ /// [`with_read_timeout`](Self::with_read_timeout).
274287 #[ must_use]
275- pub const fn with_write_timeout ( mut self , write_timeout : Duration ) -> Self {
288+ pub fn with_write_timeout ( mut self , write_timeout : Duration ) -> Self {
289+ assert ! ( !write_timeout. is_zero( ) , "write_timeout must be non-zero" ) ;
276290 self . write_timeout = write_timeout;
277291 self
278292 }
@@ -726,6 +740,13 @@ impl MdbxColdBackend {
726740 if !filter. matches ( & log) {
727741 continue ;
728742 }
743+ // Per-log deadline check: a single receipt with
744+ // thousands of matching logs would otherwise run
745+ // unchecked past the deadline. Mirrors the
746+ // streaming path in `produce_log_stream_blocking`.
747+ if Instant :: now ( ) > deadline {
748+ return Err ( MdbxColdError :: Timeout ( read_timeout) ) ;
749+ }
729750 if results. len ( ) >= max_logs {
730751 return Err ( MdbxColdError :: TooManyLogs ( max_logs) ) ;
731752 }
@@ -928,67 +949,47 @@ impl ColdStorageRead for MdbxColdBackend {
928949 }
929950}
930951
931- /// Log an advisory warning if a successful write exceeded the threshold.
932- ///
933- /// Only logs on success: a failed write that overran the threshold already
934- /// surfaces a `Backend` error to the caller, and a noisy overrun WARN would
935- /// poison SLO alerting built on this signal.
936- fn warn_on_overrun ( op : & ' static str , elapsed : Duration , threshold : Duration , is_ok : bool ) {
937- if is_ok && elapsed > threshold {
938- tracing:: warn!(
939- op,
940- elapsed_ms = elapsed. as_millis( ) as u64 ,
941- threshold_ms = threshold. as_millis( ) as u64 ,
942- "mdbx write exceeded advisory write timeout" ,
943- ) ;
944- }
945- }
946-
947952impl ColdStorageWrite for MdbxColdBackend {
948953 async fn append_block ( & self , data : BlockData ) -> ColdResult < ( ) > {
949- let threshold = self . write_timeout ;
950954 let this = self . clone ( ) ;
951- let start = Instant :: now ( ) ;
952- let result = tokio:: task:: spawn_blocking ( move || this. append_block_inner ( data) )
955+ tokio:: task:: spawn_blocking ( move || this. append_block_inner ( data) )
953956 . await
954- . map_err ( |_| ColdStorageError :: TaskTerminated ) ?;
955- warn_on_overrun ( "append_block" , start. elapsed ( ) , threshold, result. is_ok ( ) ) ;
956- Ok ( result?)
957+ . map_err ( |_| ColdStorageError :: TaskTerminated ) ?
958+ . map_err ( ColdStorageError :: from)
957959 }
958960
959961 async fn append_blocks ( & self , data : Vec < BlockData > ) -> ColdResult < ( ) > {
960- let threshold = self . write_timeout ;
961962 let this = self . clone ( ) ;
962- let start = Instant :: now ( ) ;
963- let result = tokio:: task:: spawn_blocking ( move || this. append_blocks_inner ( data) )
963+ tokio:: task:: spawn_blocking ( move || this. append_blocks_inner ( data) )
964964 . await
965- . map_err ( |_| ColdStorageError :: TaskTerminated ) ?;
966- warn_on_overrun ( "append_blocks" , start. elapsed ( ) , threshold, result. is_ok ( ) ) ;
967- Ok ( result?)
965+ . map_err ( |_| ColdStorageError :: TaskTerminated ) ?
966+ . map_err ( ColdStorageError :: from)
968967 }
969968
970969 async fn truncate_above ( & self , block : BlockNumber ) -> ColdResult < ( ) > {
971- let threshold = self . write_timeout ;
972970 let this = self . clone ( ) ;
973- let start = Instant :: now ( ) ;
974- let result = tokio:: task:: spawn_blocking ( move || this. truncate_above_inner ( block) )
971+ tokio:: task:: spawn_blocking ( move || this. truncate_above_inner ( block) )
975972 . await
976- . map_err ( |_| ColdStorageError :: TaskTerminated ) ?;
977- warn_on_overrun ( "truncate_above" , start. elapsed ( ) , threshold, result. is_ok ( ) ) ;
978- Ok ( result?)
973+ . map_err ( |_| ColdStorageError :: TaskTerminated ) ?
974+ . map_err ( ColdStorageError :: from)
979975 }
980976}
981977
982978impl ColdStorageBackend for MdbxColdBackend {
979+ fn read_timeout ( & self ) -> Option < Duration > {
980+ Some ( self . read_timeout )
981+ }
982+
983+ fn write_timeout ( & self ) -> Option < Duration > {
984+ Some ( self . write_timeout )
985+ }
986+
983987 async fn drain_above ( & self , block : BlockNumber ) -> ColdResult < Vec < Vec < ColdReceipt > > > {
984- let threshold = self . write_timeout ;
985988 let this = self . clone ( ) ;
986- let start = Instant :: now ( ) ;
987- let result = tokio:: task:: spawn_blocking ( move || this. drain_above_inner ( block) )
989+ tokio:: task:: spawn_blocking ( move || this. drain_above_inner ( block) )
988990 . await
989- . map_err ( |_| ColdStorageError :: TaskTerminated ) ?;
990- warn_on_overrun ( "drain_above" , start. elapsed ( ) , threshold, result. is_ok ( ) ) ;
991- Ok ( result?)
991+ . map_err ( |_| ColdStorageError :: TaskTerminated ) ?
992+ . map_err ( ColdStorageError :: from)
992993 }
993994}
994995
0 commit comments