@@ -1598,8 +1598,14 @@ impl Bucket {
15981598 s3_path : & str ,
15991599 content_type : & str ,
16001600 ) -> Result < PutStreamResponse , S3Error > {
1601- self . _put_object_stream_with_content_type_and_headers ( reader, s3_path, content_type, None )
1602- . await
1601+ self . _put_object_stream_with_content_type_and_headers (
1602+ reader,
1603+ s3_path,
1604+ content_type,
1605+ None ,
1606+ None ,
1607+ )
1608+ . await
16031609 }
16041610
16051611 /// Calculate the maximum number of concurrent chunks based on available memory.
@@ -1642,6 +1648,7 @@ impl Bucket {
16421648 s3_path : & str ,
16431649 content_type : & str ,
16441650 custom_headers : Option < http:: HeaderMap > ,
1651+ max_concurrent_chunks : Option < std:: num:: NonZeroUsize > ,
16451652 ) -> Result < PutStreamResponse , S3Error > {
16461653 // If the file is smaller CHUNK_SIZE, just do a regular upload.
16471654 // Otherwise perform a multi-part upload.
@@ -1675,9 +1682,117 @@ impl Bucket {
16751682 let path = msg. key ;
16761683 let upload_id = & msg. upload_id ;
16771684
1678- // Determine max concurrent chunks based on available memory
1679- let max_concurrent_chunks = Self :: calculate_max_concurrent_chunks ( ) ;
1685+ // use configured max_concurrent_chunks or determine max concurrent chunks based on available memory
1686+ let max_concurrent_chunks = max_concurrent_chunks. map_or_else (
1687+ Self :: calculate_max_concurrent_chunks,
1688+ std:: num:: NonZeroUsize :: get,
1689+ ) ;
1690+
1691+ let ( total_size, mut etags) = if max_concurrent_chunks == 1 {
1692+ self . _put_object_stream_chunks_sequential (
1693+ reader,
1694+ first_chunk,
1695+ & path,
1696+ upload_id,
1697+ content_type,
1698+ )
1699+ . await ?
1700+ } else {
1701+ self . _put_object_stream_chunks_concurrent (
1702+ reader,
1703+ first_chunk,
1704+ & path,
1705+ upload_id,
1706+ content_type,
1707+ max_concurrent_chunks,
1708+ )
1709+ . await ?
1710+ } ;
1711+
1712+ // Sort etags by part number to ensure correct order
1713+ etags. sort_by_key ( |k| k. 0 ) ;
1714+ let etags: Vec < String > = etags. into_iter ( ) . map ( |( _, etag) | etag) . collect ( ) ;
1715+
1716+ // Finish the upload
1717+ let inner_data = etags
1718+ . into_iter ( )
1719+ . enumerate ( )
1720+ . map ( |( i, x) | Part {
1721+ etag : x,
1722+ part_number : i as u32 + 1 ,
1723+ } )
1724+ . collect :: < Vec < Part > > ( ) ;
1725+ let response_data = self
1726+ . complete_multipart_upload ( & path, & msg. upload_id , inner_data)
1727+ . await ?;
1728+
1729+ Ok ( PutStreamResponse :: new (
1730+ response_data. status_code ( ) ,
1731+ total_size,
1732+ ) )
1733+ }
1734+
1735+ #[ maybe_async:: async_impl]
1736+ async fn _put_object_stream_chunks_sequential < R : AsyncRead + Unpin + ?Sized > (
1737+ & self ,
1738+ reader : & mut R ,
1739+ first_chunk : Vec < u8 > ,
1740+ path : & str ,
1741+ upload_id : & str ,
1742+ content_type : & str ,
1743+ ) -> Result < ( usize , Vec < ( u32 , String ) > ) , S3Error > {
1744+ let mut chunk = first_chunk;
1745+ let mut part_number: u32 = 0 ;
1746+ let mut total_size = 0 ;
1747+ let mut etags = Vec :: new ( ) ;
1748+
1749+ loop {
1750+ let chunk_len = chunk. len ( ) ;
1751+
1752+ if chunk_len == 0 {
1753+ break ;
1754+ }
1755+
1756+ part_number += 1 ;
1757+ total_size += chunk_len;
1758+
1759+ let current_part = part_number;
1760+ let is_last_chunk = chunk_len < CHUNK_SIZE ;
1761+
1762+ let response_data = self
1763+ . make_multipart_request ( path, chunk, current_part, upload_id, content_type)
1764+ . await ?;
1765+
1766+ if !( 200 ..300 ) . contains ( & response_data. status_code ( ) ) {
1767+ // it chunk upload failed - abort the upload
1768+ return match self . abort_upload ( path, upload_id) . await {
1769+ Ok ( _) => Err ( error_from_response_data ( response_data) ?) ,
1770+ Err ( error) => Err ( error) ,
1771+ } ;
1772+ }
1773+
1774+ etags. push ( ( current_part, response_data. as_str ( ) ?. to_string ( ) ) ) ;
16801775
1776+ if is_last_chunk {
1777+ break ;
1778+ }
1779+
1780+ chunk = crate :: utils:: read_chunk_async ( reader) . await ?;
1781+ }
1782+
1783+ Ok ( ( total_size, etags) )
1784+ }
1785+
1786+ #[ maybe_async:: async_impl]
1787+ async fn _put_object_stream_chunks_concurrent < R : AsyncRead + Unpin + ?Sized > (
1788+ & self ,
1789+ reader : & mut R ,
1790+ first_chunk : Vec < u8 > ,
1791+ path : & str ,
1792+ upload_id : & str ,
1793+ content_type : & str ,
1794+ max_concurrent_chunks : usize ,
1795+ ) -> Result < ( usize , Vec < ( u32 , String ) > ) , S3Error > {
16811796 // Use FuturesUnordered for bounded parallelism
16821797 use futures_util:: FutureExt ;
16831798 use futures_util:: stream:: { FuturesUnordered , StreamExt } ;
@@ -1697,21 +1812,10 @@ impl Bucket {
16971812 reading_done = true ;
16981813 }
16991814
1700- let path_clone = path. clone ( ) ;
1701- let upload_id_clone = upload_id. clone ( ) ;
1702- let content_type_clone = content_type. to_string ( ) ;
1703- let bucket_clone = self . clone ( ) ;
1704-
17051815 active_uploads. push (
17061816 async move {
1707- let result = bucket_clone
1708- . make_multipart_request (
1709- & path_clone,
1710- first_chunk,
1711- 1 ,
1712- & upload_id_clone,
1713- & content_type_clone,
1714- )
1817+ let result = self
1818+ . make_multipart_request ( path, first_chunk, 1 , upload_id, content_type)
17151819 . await ;
17161820 ( 1 , result)
17171821 }
@@ -1738,20 +1842,16 @@ impl Bucket {
17381842 }
17391843
17401844 let current_part = part_number;
1741- let path_clone = path. clone ( ) ;
1742- let upload_id_clone = upload_id. clone ( ) ;
1743- let content_type_clone = content_type. to_string ( ) ;
1744- let bucket_clone = self . clone ( ) ;
17451845
17461846 active_uploads. push (
17471847 async move {
1748- let result = bucket_clone
1848+ let result = self
17491849 . make_multipart_request (
1750- & path_clone ,
1850+ path ,
17511851 chunk,
17521852 current_part,
1753- & upload_id_clone ,
1754- & content_type_clone ,
1853+ upload_id ,
1854+ content_type ,
17551855 )
17561856 . await ;
17571857 ( current_part, result)
@@ -1765,7 +1865,7 @@ impl Bucket {
17651865 let response_data = result?;
17661866 if !( 200 ..300 ) . contains ( & response_data. status_code ( ) ) {
17671867 // if chunk upload failed - abort the upload
1768- match self . abort_upload ( & path, upload_id) . await {
1868+ match self . abort_upload ( path, upload_id) . await {
17691869 Ok ( _) => {
17701870 return Err ( error_from_response_data ( response_data) ?) ;
17711871 }
@@ -1781,28 +1881,7 @@ impl Bucket {
17811881 }
17821882 }
17831883
1784- // Sort etags by part number to ensure correct order
1785- etags. sort_by_key ( |k| k. 0 ) ;
1786- let etags: Vec < String > = etags. into_iter ( ) . map ( |( _, etag) | etag) . collect ( ) ;
1787-
1788- // Finish the upload
1789- let inner_data = etags
1790- . clone ( )
1791- . into_iter ( )
1792- . enumerate ( )
1793- . map ( |( i, x) | Part {
1794- etag : x,
1795- part_number : i as u32 + 1 ,
1796- } )
1797- . collect :: < Vec < Part > > ( ) ;
1798- let response_data = self
1799- . complete_multipart_upload ( & path, & msg. upload_id , inner_data)
1800- . await ?;
1801-
1802- Ok ( PutStreamResponse :: new (
1803- response_data. status_code ( ) ,
1804- total_size,
1805- ) )
1884+ Ok ( ( total_size, etags) )
18061885 }
18071886
18081887 #[ maybe_async:: sync_impl]
0 commit comments