1111//! Serialization and file writes run on a dedicated writer thread so manifest
1212//! persistence does not occupy async runtime workers or Tokio's blocking pool.
1313
14- use std:: fs;
15- use std:: io:: ErrorKind ;
1614use std:: path:: { Path , PathBuf } ;
1715use std:: sync:: Arc ;
18- use std:: sync:: mpsc:: { self , SyncSender , TrySendError } ;
16+ use std:: sync:: mpsc:: { self , Sender } ;
1917use std:: thread:: JoinHandle ;
2018
2119use async_trait:: async_trait;
2220use serde:: Serialize ;
2321use utoo_ruborist:: model:: manifest:: CoreVersionManifest ;
2422use utoo_ruborist:: service:: { ManifestStore , VersionsInfo } ;
2523
26- use crate :: util:: json:: { read_json_file, write_compact_sync} ;
27-
28- /// Opportunistic writer backlog. If disk stalls beyond this, new cache writes
29- /// are dropped instead of letting resolver memory grow without bound.
30- const MANIFEST_WRITE_QUEUE_CAPACITY : usize = 1024 ;
24+ use crate :: util:: json:: read_json_file;
3125
3226pub struct DiskManifestStore {
3327 cache_dir : PathBuf ,
@@ -112,13 +106,13 @@ enum ManifestWriteJob {
112106}
113107
114108struct ManifestWriter {
115- tx : SyncSender < ManifestWriteJob > ,
109+ tx : Sender < ManifestWriteJob > ,
116110 handle : JoinHandle < ( ) > ,
117111}
118112
119113impl ManifestWriter {
120114 fn spawn ( ) -> Self {
121- let ( tx, rx) = mpsc:: sync_channel ( MANIFEST_WRITE_QUEUE_CAPACITY ) ;
115+ let ( tx, rx) = mpsc:: channel ( ) ;
122116 let handle = std:: thread:: Builder :: new ( )
123117 . name ( "utoo-manifest-store" . to_string ( ) )
124118 . spawn ( move || {
@@ -138,14 +132,8 @@ impl ManifestWriter {
138132 }
139133
140134 fn enqueue ( & self , job : ManifestWriteJob ) {
141- match self . tx . try_send ( job) {
142- Ok ( ( ) ) => { }
143- Err ( TrySendError :: Full ( _) ) => {
144- tracing:: debug!( "Manifest store writer queue full; dropping cache write" ) ;
145- }
146- Err ( TrySendError :: Disconnected ( _) ) => {
147- tracing:: debug!( "Manifest store writer stopped before accepting write" ) ;
148- }
135+ if self . tx . send ( job) . is_err ( ) {
136+ tracing:: debug!( "Manifest store writer stopped before accepting write" ) ;
149137 }
150138 }
151139
@@ -157,23 +145,27 @@ impl ManifestWriter {
157145 }
158146}
159147
160- /// Apply the manifest-cache write policy on top of
161- /// [`crate::util::json::write_compact_sync`]: on `NotFound`, create the
162- /// parent directory once and retry — this is how the resolver hot path
163- /// avoids the up-front `mkdir` syscall on every warm-cache rewrite. All
164- /// errors are swallowed at the `debug` log level because the disk cache is
165- /// opportunistic; a dropped write only costs a future cache miss.
148+ /// Serialize `value` and write to `path`. On `NotFound`, create the parent
149+ /// directory and retry once — saves the mkdir syscall on every warm-cache
150+ /// rewrite. Errors are logged at debug; disk cache is opportunistic.
166151fn write_json_sync < T : Serialize > ( path : & Path , value : & T ) {
167- match write_compact_sync ( path, value) {
152+ let bytes = match serde_json:: to_vec ( value) {
153+ Ok ( b) => b,
154+ Err ( e) => {
155+ tracing:: debug!( "Failed to serialize {path:?}: {e}" ) ;
156+ return ;
157+ }
158+ } ;
159+ match std:: fs:: write ( path, & bytes) {
168160 Ok ( ( ) ) => { }
169- Err ( e) if e. kind ( ) == ErrorKind :: NotFound => {
161+ Err ( e) if e. kind ( ) == std :: io :: ErrorKind :: NotFound => {
170162 if let Some ( parent) = path. parent ( )
171- && let Err ( e) = fs:: create_dir_all ( parent)
163+ && let Err ( e) = std :: fs:: create_dir_all ( parent)
172164 {
173165 tracing:: debug!( "Failed to create {parent:?}: {e}" ) ;
174166 return ;
175167 }
176- if let Err ( e) = write_compact_sync ( path, value ) {
168+ if let Err ( e) = std :: fs :: write ( path, & bytes ) {
177169 tracing:: debug!( "Failed to write {path:?}: {e}" ) ;
178170 }
179171 }
0 commit comments