@@ -17,12 +17,12 @@ use std::path::Path;
1717use std:: sync:: Arc ;
1818use std:: time:: Duration ;
1919
20- use talon_core:: { ObjectId , RequestId , Version } ;
20+ use talon_core:: { BlockId , ObjectId , RequestId , Version } ;
2121use talon_transport:: frame:: { FrameHeader , MsgType , HEADER_LEN } ;
2222use talon_transport:: {
23- decode_error_payload, encode_cached_request , encode_delete , encode_put_header , encode_request ,
24- CachedRangeRequest , DataPlaneError , DeleteRequest , Flags , PutRequest , RangeRequest ,
25- MAX_CONTROL_PAYLOAD_LEN ,
23+ decode_error_payload, encode_cached_block_put_header , encode_cached_request , encode_delete ,
24+ encode_put_header , encode_request , CachedBlockPutRequest , CachedRangeRequest , DataPlaneError ,
25+ DeleteRequest , Flags , PutRequest , RangeRequest , MAX_CONTROL_PAYLOAD_LEN ,
2626} ;
2727use tokio:: io:: { AsyncReadExt , AsyncWriteExt } ;
2828use tokio:: net:: TcpStream ;
@@ -197,6 +197,74 @@ impl WorkerClient {
197197 }
198198 }
199199
200+ /// Admit one complete, versioned block without asking the worker to access
201+ /// its configured backend.
202+ pub async fn admit_cached_block (
203+ & self ,
204+ block : & BlockId ,
205+ object_len : u64 ,
206+ body : & [ u8 ] ,
207+ ) -> Result < ( ) , WorkerError > {
208+ let request_id = RequestId :: next ( ) ;
209+ let header = encode_cached_block_put_header (
210+ request_id. 0 ,
211+ & CachedBlockPutRequest {
212+ block : block. clone ( ) ,
213+ object_len,
214+ body_len : body. len ( ) as u64 ,
215+ } ,
216+ ) ?;
217+ match self . admit_exchange ( & header, body) . await {
218+ Ok ( ( ) ) => Ok ( ( ) ) ,
219+ Err ( ( true , _) ) => {
220+ let mut stream = self . pool . fresh ( & self . addr ) . await ?;
221+ self . pool
222+ . with_deadline (
223+ "worker admit_cached_block retry" ,
224+ streamed_put_timeout ( body. len ( ) as u64 ) ,
225+ async {
226+ stream. write_all ( & header) . await ?;
227+ stream. write_all ( body) . await ?;
228+ stream. flush ( ) . await ?;
229+ read_range_reply ( & mut stream, 0 ) . await . map ( |_| ( ) )
230+ } ,
231+ )
232+ . await ?;
233+ self . pool . release ( & self . addr , stream) ;
234+ Ok ( ( ) )
235+ }
236+ Err ( ( false , error) ) => Err ( error) ,
237+ }
238+ }
239+
240+ async fn admit_exchange ( & self , header : & [ u8 ] , body : & [ u8 ] ) -> Result < ( ) , ( bool , WorkerError ) > {
241+ let ( mut stream, reused) = self
242+ . pool
243+ . checkout ( & self . addr )
244+ . await
245+ . map_err ( |error| ( false , WorkerError :: from ( error) ) ) ?;
246+ let result = self
247+ . pool
248+ . with_deadline (
249+ "worker admit_cached_block" ,
250+ streamed_put_timeout ( body. len ( ) as u64 ) ,
251+ async {
252+ stream. write_all ( header) . await ?;
253+ stream. write_all ( body) . await ?;
254+ stream. flush ( ) . await ?;
255+ read_range_reply ( & mut stream, 0 ) . await . map ( |_| ( ) )
256+ } ,
257+ )
258+ . await ;
259+ match result {
260+ Ok ( ( ) ) => {
261+ self . pool . release ( & self . addr , stream) ;
262+ Ok ( ( ) )
263+ }
264+ Err ( error) => Err ( ( reused, error) ) ,
265+ }
266+ }
267+
200268 /// One request/response over a pooled-or-fresh connection.
201269 ///
202270 /// On success releases the connection for reuse. On error returns
@@ -575,14 +643,50 @@ mod tests {
575643 use std:: time:: Duration ;
576644 use talon_core:: Backend ;
577645 use talon_transport:: {
578- decode_cached_request, decode_request, encode_error, response_header_ok,
646+ decode_cached_block_put_header, decode_cached_request, decode_request, encode_error,
647+ response_header_ok,
579648 } ;
580649 use tokio:: net:: TcpListener ;
581650
582651 fn object ( ) -> ObjectId {
583652 ObjectId :: new ( Backend :: Azure , "container" , "path/to/blob.bin" )
584653 }
585654
655+ #[ tokio:: test]
656+ async fn cache_admission_sends_exact_versioned_block_and_body ( ) {
657+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . await . unwrap ( ) ;
658+ let addr = listener. local_addr ( ) . unwrap ( ) . to_string ( ) ;
659+ let block = BlockId :: new ( object ( ) , 8 , 8 , Version :: new ( "etag-v2" ) ) ;
660+ let expected = block. clone ( ) ;
661+ tokio:: spawn ( async move {
662+ let ( mut socket, _) = listener. accept ( ) . await . unwrap ( ) ;
663+ let mut header_bytes = [ 0_u8 ; HEADER_LEN ] ;
664+ socket. read_exact ( & mut header_bytes) . await . unwrap ( ) ;
665+ let header = FrameHeader :: decode ( & header_bytes) . unwrap ( ) ;
666+ assert_eq ! ( header. msg_type, MsgType :: AdmitCachedBlock ) ;
667+ let mut payload = vec ! [ 0_u8 ; header. length as usize ] ;
668+ socket. read_exact ( & mut payload) . await . unwrap ( ) ;
669+ let mut frame = header_bytes. to_vec ( ) ;
670+ frame. extend_from_slice ( & payload) ;
671+ let ( _, request) = decode_cached_block_put_header ( & frame) . unwrap ( ) ;
672+ assert_eq ! ( request. block, expected) ;
673+ assert_eq ! ( request. object_len, 13 ) ;
674+ assert_eq ! ( request. body_len, 5 ) ;
675+ let mut body = vec ! [ 0_u8 ; 5 ] ;
676+ socket. read_exact ( & mut body) . await . unwrap ( ) ;
677+ assert_eq ! ( body, b"tail!" ) ;
678+ socket
679+ . write_all ( & response_header_ok ( header. request_id , 0 ) )
680+ . await
681+ . unwrap ( ) ;
682+ } ) ;
683+
684+ WorkerClient :: new ( addr)
685+ . admit_cached_block ( & block, 13 , b"tail!" )
686+ . await
687+ . unwrap ( ) ;
688+ }
689+
586690 /// What a mock write-worker records: the object and body it received.
587691 type RecordedPut = Arc < std:: sync:: Mutex < Option < ( ObjectId , Vec < u8 > ) > > > ;
588692
0 commit comments