33// the platform's `ErrorBody` and a success into its fragment_proto type.
44use crate :: auth:: Identity ;
55use anyhow:: { Context , Result } ;
6+ use bytes:: Bytes ;
67use fragment_proto:: { ErrorBody , ErrorCode } ;
78use serde:: de:: DeserializeOwned ;
89use serde_json:: Value ;
@@ -403,7 +404,7 @@ impl Client {
403404 }
404405
405406 fn request ( & self , method : & str , path : & str , body : Option < Vec < u8 > > ) -> Result < Resp > {
406- let body = body. unwrap_or_default ( ) ;
407+ let body = Bytes :: from ( body. unwrap_or_default ( ) ) ;
407408 let timeout = timeout_for ( body. len ( ) as u64 ) ;
408409 self . send ( method, path, body, Replay :: of ( method) , Signed :: Body , timeout, None )
409410 }
@@ -412,7 +413,7 @@ impl Client {
412413 /// allows (`send_retrying`); `content_type` names the body's type, when
413414 /// it has one.
414415 #[ allow( clippy:: too_many_arguments) ]
415- fn send ( & self , method : & str , path : & str , body : Vec < u8 > , replay : Replay , signed : Signed , timeout : Duration , content_type : Option < & str > ) -> Result < Resp > {
416+ fn send ( & self , method : & str , path : & str , body : Bytes , replay : Replay , signed : Signed , timeout : Duration , content_type : Option < & str > ) -> Result < Resp > {
416417 let path = & self . resolved ( path) ?;
417418 let url = self . url ( method, path) ;
418419 let verb: reqwest:: Method = method. parse ( ) ?;
@@ -423,7 +424,7 @@ impl Client {
423424 if let Some ( t) = content_type {
424425 req = req. header ( "content-type" , t) ;
425426 }
426- if body. is_empty ( ) { req } else { req. body ( body . clone ( ) ) }
427+ if body. is_empty ( ) { req } else { req. body ( attempt_body ( & body ) ) }
427428 } ;
428429 let ( host, coded) = ( & self . host , |code, msg| Err ( anyhow:: Error :: new ( CodedError { code, msg } ) ) ) ;
429430 match send_retrying ( build, replay, self . verbose . then_some ( what. as_str ( ) ) ) {
@@ -450,14 +451,14 @@ impl Client {
450451 /// A bounded slow operation, such as icon generation. It retains the
451452 /// normal non-idempotent POST retry policy.
452453 pub fn post_json_with_timeout ( & self , path : & str , v : & impl serde:: Serialize , timeout : Duration ) -> Result < Resp > {
453- self . send ( "POST" , path, serde_json:: to_vec ( v) ?, Replay :: ConnectOnly , Signed :: Body , timeout, None )
454+ self . send ( "POST" , path, Bytes :: from ( serde_json:: to_vec ( v) ?) , Replay :: ConnectOnly , Signed :: Body , timeout, None )
454455 }
455456 /// A POST whose body carries its idempotency id (an operation call):
456457 /// retried like a read (`Replay::ById`).
457458 pub fn post_json_by_id ( & self , path : & str , v : & impl serde:: Serialize ) -> Result < Resp > {
458459 let body = serde_json:: to_vec ( v) ?;
459460 let timeout = timeout_for ( body. len ( ) as u64 ) ;
460- self . send ( "POST" , path, body, Replay :: ById , Signed :: Body , timeout, None )
461+ self . send ( "POST" , path, Bytes :: from ( body) , Replay :: ById , Signed :: Body , timeout, None )
461462 }
462463 pub fn put_json ( & self , path : & str , v : & impl serde:: Serialize ) -> Result < Resp > {
463464 self . request ( "PUT" , path, Some ( serde_json:: to_vec ( v) ?) )
@@ -470,12 +471,12 @@ impl Client {
470471 /// `content_type` is what a page reading it gets (`__blob`).
471472 pub fn put_blob ( & self , path : & str , bytes : Vec < u8 > , content_type : Option < & str > ) -> Result < Resp > {
472473 let timeout = timeout_for ( bytes. len ( ) as u64 ) ;
473- self . send ( "PUT" , path, bytes, Replay :: Safe , Signed :: Url , timeout, content_type)
474+ self . send ( "PUT" , path, Bytes :: from ( bytes) , Replay :: Safe , Signed :: Url , timeout, content_type)
474475 }
475476 /// A GET whose answer is known to be about `bytes` long (a blob), given
476477 /// the time that takes.
477478 pub fn get_sized ( & self , path : & str , bytes : u64 ) -> Result < Resp > {
478- self . send ( "GET" , path, Vec :: new ( ) , Replay :: Safe , Signed :: Body , timeout_for ( bytes) , None )
479+ self . send ( "GET" , path, Bytes :: new ( ) , Replay :: Safe , Signed :: Body , timeout_for ( bytes) , None )
479480 }
480481 /// A signed GET whose answer goes to `to` as it arrives (an export's
481482 /// archive, which may be gigabytes), never held whole: its bytes. A
@@ -489,7 +490,7 @@ impl Client {
489490 let mut resp = self . http . get ( & url) . header ( header, value) . timeout ( Duration :: from_secs ( 6 * 3600 ) ) . send ( ) . with_context ( || format ! ( "GET {path}" ) ) ?;
490491 let status = resp. status ( ) . as_u16 ( ) ;
491492 if !( 200 ..300 ) . contains ( & status) {
492- let body = resp. bytes ( ) . map ( |b| b . to_vec ( ) ) . unwrap_or_default ( ) ;
493+ let body = resp. bytes ( ) . map ( owned ) . unwrap_or_default ( ) ;
493494 return Err ( anyhow:: Error :: new ( Resp { status, body } . refusal ( ) ) ) ;
494495 }
495496 let mut file = std:: fs:: File :: create ( to) . with_context ( || format ! ( "writing {}" , to. display( ) ) ) ?;
@@ -550,6 +551,21 @@ pub fn resolve_label(label: &str, listed: &[fragment_proto::ListedFragment]) ->
550551 }
551552}
552553
554+ /// The body one attempt of a request sends: the buffer kept for its
555+ /// retries itself, shared (a reference, not a copy). A blob's PUT (up to
556+ /// `BLOB_MAX_BYTES`) once held two whole copies, the kept one and the
557+ /// attempt's.
558+ pub fn attempt_body ( kept : & Bytes ) -> Bytes {
559+ kept. clone ( )
560+ }
561+
562+ /// An answer's bytes as the `Vec` a `Resp` holds: the buffer reqwest read
563+ /// them into, taken in place when the answer holds it alone (a body read
564+ /// whole does), not copied out beside it as a blob's GET once was.
565+ pub fn owned ( answer : Bytes ) -> Vec < u8 > {
566+ Vec :: from ( answer)
567+ }
568+
553569pub fn send_retrying ( build : impl Fn ( ) -> reqwest:: blocking:: RequestBuilder , replay : Replay , log : Option < & str > ) -> std:: result:: Result < ( u16 , Vec < u8 > ) , Failed > {
554570 let ( mut last, mut reached) = ( None , false ) ;
555571 for attempt in 0 ..REQUEST_ATTEMPTS {
@@ -562,7 +578,7 @@ pub fn send_retrying(build: impl Fn() -> reqwest::blocking::RequestBuilder, repl
562578 let answer = build ( ) . send ( ) . and_then ( |resp| {
563579 crate :: notice:: heard ( resp. url ( ) . host_str ( ) , resp. headers ( ) . get ( "x-fragment-cli" ) . and_then ( |v| v. to_str ( ) . ok ( ) ) ) ;
564580 let status = resp. status ( ) . as_u16 ( ) ;
565- resp. bytes ( ) . map ( |b| ( status, b . to_vec ( ) ) )
581+ resp. bytes ( ) . map ( |b| ( status, owned ( b ) ) )
566582 } ) ;
567583 let ms = t0. elapsed ( ) . as_millis ( ) ;
568584 match answer {
@@ -602,6 +618,38 @@ pub fn encode_q(s: &str) -> String {
602618mod tests {
603619 use super :: * ;
604620
621+ /// Goal: a transfer's bytes are one buffer, not two (Codex on
622+ /// 69037874: four 256 MiB blobs moving at once held 2 GiB). Method:
623+ /// the buffers themselves, by address. An attempt sends the body kept
624+ /// for its retries, shared; an answer read whole by this reqwest (a
625+ /// 4 MiB one from a local socket, in many chunks) becomes the `Vec` a
626+ /// `Resp` holds in place. Each fails with a copy (`clone` of a `Vec`,
627+ /// `to_vec`: checked).
628+ #[ test]
629+ fn a_transfer_holds_its_bytes_once ( ) {
630+ let kept = Bytes :: from ( vec ! [ 7u8 ; 1 << 20 ] ) ;
631+ assert_eq ! ( attempt_body( & kept) . as_ptr( ) , kept. as_ptr( ) , "an attempt's body is the kept buffer" ) ;
632+
633+ use std:: io:: { Read , Write } ;
634+ const LEN : usize = 4 << 20 ;
635+ let listener = std:: net:: TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
636+ let url = format ! ( "http://{}/blob" , listener. local_addr( ) . unwrap( ) ) ;
637+ let served = std:: thread:: spawn ( move || {
638+ let ( mut conn, _) = listener. accept ( ) . unwrap ( ) ;
639+ let mut asked = [ 0u8 ; 1024 ] ;
640+ let _ = conn. read ( & mut asked) . unwrap ( ) ;
641+ write ! ( conn, "HTTP/1.1 200 OK\r \n content-length: {LEN}\r \n connection: close\r \n \r \n " ) . unwrap ( ) ;
642+ for _ in 0 ..LEN / ( 64 << 10 ) {
643+ conn. write_all ( & [ 9u8 ; 64 << 10 ] ) . unwrap ( ) ;
644+ }
645+ } ) ;
646+ let answer = reqwest:: blocking:: get ( & url) . unwrap ( ) . bytes ( ) . unwrap ( ) ;
647+ let at = answer. as_ptr ( ) ;
648+ let body = owned ( answer) ;
649+ assert_eq ! ( ( body. as_ptr( ) , body. len( ) ) , ( at, LEN ) , "an answer's Vec is the buffer reqwest read it into" ) ;
650+ served. join ( ) . unwrap ( ) ;
651+ }
652+
605653 use fragment_fakes:: http:: { Response as FakeResponse , Server } ;
606654 use std:: sync:: atomic:: { AtomicU32 , Ordering } ;
607655 use std:: sync:: Arc ;
0 commit comments