mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-21 20:06:37 +00:00
3f13d098b4
Merge the hotpath-rs wall-time instrumentation from the backlog#936 analysis worktree behind an opt-in 'hotpath' cargo feature, keeping the default build at zero overhead and zero dependency. - hotpath is an optional dependency everywhere (dep:hotpath feature syntax); the default dependency tree contains no hotpath crate at all - 40+ measurement points across S3 handlers, ECStore/SetDisks object and multipart ops, erasure encode/decode, bitrot, LocalDisk I/O, FileMeta codec, and HashReader - attribute sites use #[cfg_attr(feature = "hotpath", hotpath::measure)]; async_trait bodies use per-crate hp_guard! macros (ecstore + rustfs bin); rio gates measure_block! behind hp_measure_block! - feature chain: rustfs -> rustfs-ecstore -> rustfs-rio / rustfs-filemeta, each crate owning its own gate - hotpath-alloc is intentionally not wired up (hotpath 0.21.x TLS panic on cross-thread guard drop under tokio, see backlog#935); mimalloc stays the unconditional global allocator - docs/development/hotpath-profiling.md documents building, HOTPATH_* env vars, SIGTERM report flow, and how to reproduce the backlog#936 timing reports Refs: https://github.com/rustfs/backlog/issues/935 (HP-14, item 2), https://github.com/rustfs/backlog/issues/936 Co-authored-by: heihutu <heihutu@gmail.com>
344 lines
10 KiB
Rust
344 lines
10 KiB
Rust
// Copyright 2024 RustFS Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use super::*;
|
|
use crate::storage_api_contracts::multipart::MultipartOperations as _;
|
|
|
|
impl ECStore {
|
|
#[instrument(skip(self))]
|
|
pub(super) async fn handle_list_object_parts(
|
|
&self,
|
|
bucket: &str,
|
|
object: &str,
|
|
upload_id: &str,
|
|
part_number_marker: Option<usize>,
|
|
max_parts: usize,
|
|
opts: &ObjectOptions,
|
|
) -> Result<ListPartsInfo> {
|
|
check_list_parts_args(bucket, object, upload_id)?;
|
|
|
|
// TODO: nslock
|
|
|
|
if self.single_pool() {
|
|
return self.pools[0]
|
|
.list_object_parts(bucket, object, upload_id, part_number_marker, max_parts, opts)
|
|
.await;
|
|
}
|
|
|
|
for pool in self.pools.iter() {
|
|
if self.is_suspended(pool.pool_idx).await {
|
|
continue;
|
|
}
|
|
return match pool
|
|
.list_object_parts(bucket, object, upload_id, part_number_marker, max_parts, opts)
|
|
.await
|
|
{
|
|
Ok(res) => Ok(res),
|
|
Err(err) => {
|
|
if is_err_invalid_upload_id(&err) {
|
|
continue;
|
|
}
|
|
Err(err)
|
|
}
|
|
};
|
|
}
|
|
|
|
Err(StorageError::InvalidUploadID(bucket.to_owned(), object.to_owned(), upload_id.to_owned()))
|
|
}
|
|
|
|
#[instrument(skip(self))]
|
|
pub(super) async fn handle_list_multipart_uploads(
|
|
&self,
|
|
bucket: &str,
|
|
prefix: &str,
|
|
key_marker: Option<String>,
|
|
upload_id_marker: Option<String>,
|
|
delimiter: Option<String>,
|
|
max_uploads: usize,
|
|
) -> Result<ListMultipartsInfo> {
|
|
check_list_multipart_args(bucket, prefix, &key_marker, &upload_id_marker, &delimiter)?;
|
|
|
|
if prefix.is_empty() {
|
|
// TODO: return from cache
|
|
}
|
|
|
|
if self.single_pool() {
|
|
return self.pools[0]
|
|
.list_multipart_uploads(bucket, prefix, key_marker, upload_id_marker, delimiter, max_uploads)
|
|
.await;
|
|
}
|
|
|
|
let mut uploads = Vec::new();
|
|
|
|
for pool in self.pools.iter() {
|
|
if self.is_suspended(pool.pool_idx).await {
|
|
continue;
|
|
}
|
|
let res = pool
|
|
.list_multipart_uploads(
|
|
bucket,
|
|
prefix,
|
|
key_marker.clone(),
|
|
upload_id_marker.clone(),
|
|
delimiter.clone(),
|
|
max_uploads,
|
|
)
|
|
.await?;
|
|
uploads.extend(res.uploads);
|
|
}
|
|
|
|
Ok(ListMultipartsInfo {
|
|
key_marker,
|
|
upload_id_marker,
|
|
max_uploads,
|
|
uploads,
|
|
prefix: prefix.to_owned(),
|
|
delimiter: delimiter.to_owned(),
|
|
..Default::default()
|
|
})
|
|
}
|
|
|
|
#[instrument(skip(self))]
|
|
pub(super) async fn handle_new_multipart_upload(
|
|
&self,
|
|
bucket: &str,
|
|
object: &str,
|
|
opts: &ObjectOptions,
|
|
) -> Result<MultipartUploadResult> {
|
|
self.handle_new_multipart_upload_with_pool_idx(bucket, object, opts)
|
|
.await
|
|
.map(|(res, _)| res)
|
|
}
|
|
|
|
pub(crate) async fn handle_new_multipart_upload_with_pool_idx(
|
|
&self,
|
|
bucket: &str,
|
|
object: &str,
|
|
opts: &ObjectOptions,
|
|
) -> Result<(MultipartUploadResult, usize)> {
|
|
check_new_multipart_args(bucket, object)?;
|
|
|
|
if self.single_pool() {
|
|
return self.pools[0]
|
|
.new_multipart_upload(bucket, object, opts)
|
|
.await
|
|
.map(|res| (res, 0));
|
|
}
|
|
|
|
for (idx, pool) in self.pools.iter().enumerate() {
|
|
if self.is_suspended(idx).await || self.is_pool_rebalancing(idx).await {
|
|
continue;
|
|
}
|
|
let res = pool
|
|
.list_multipart_uploads(bucket, object, None, None, None, MAX_UPLOADS_LIST)
|
|
.await?;
|
|
|
|
if !res.uploads.is_empty() {
|
|
let res = self.pools[idx].new_multipart_upload(bucket, object, opts).await?;
|
|
return Ok((res, idx));
|
|
}
|
|
}
|
|
let idx = self.get_pool_idx(bucket, object, -1).await?;
|
|
if opts.data_movement && idx == opts.src_pool_idx {
|
|
return Err(StorageError::DataMovementOverwriteErr(
|
|
bucket.to_owned(),
|
|
object.to_owned(),
|
|
"".to_owned(),
|
|
));
|
|
}
|
|
|
|
let res = self.pools[idx].new_multipart_upload(bucket, object, opts).await?;
|
|
Ok((res, idx))
|
|
}
|
|
|
|
#[instrument(skip(self))]
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub(super) async fn handle_copy_object_part(
|
|
&self,
|
|
src_bucket: &str,
|
|
src_object: &str,
|
|
_dst_bucket: &str,
|
|
_dst_object: &str,
|
|
_upload_id: &str,
|
|
_part_id: usize,
|
|
_start_offset: i64,
|
|
_length: i64,
|
|
_src_info: &ObjectInfo,
|
|
_src_opts: &ObjectOptions,
|
|
_dst_opts: &ObjectOptions,
|
|
) -> Result<()> {
|
|
check_new_multipart_args(src_bucket, src_object)?;
|
|
|
|
// The full UploadPartCopy path still requires the higher S3/request layer to
|
|
// derive encryption, compression, and multipart checksum write semantics.
|
|
Err(StorageError::NotImplemented)
|
|
}
|
|
|
|
#[instrument(skip(self, data))]
|
|
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
|
pub(super) async fn handle_put_object_part(
|
|
&self,
|
|
bucket: &str,
|
|
object: &str,
|
|
upload_id: &str,
|
|
part_id: usize,
|
|
data: &mut PutObjReader,
|
|
opts: &ObjectOptions,
|
|
) -> Result<PartInfo> {
|
|
check_put_object_part_args(bucket, object, upload_id)?;
|
|
|
|
if self.single_pool() {
|
|
return self.pools[0]
|
|
.put_object_part(bucket, object, upload_id, part_id, data, opts)
|
|
.await;
|
|
}
|
|
|
|
for pool in self.pools.iter() {
|
|
if self.is_suspended(pool.pool_idx).await {
|
|
continue;
|
|
}
|
|
let err = match pool.put_object_part(bucket, object, upload_id, part_id, data, opts).await {
|
|
Ok(res) => return Ok(res),
|
|
Err(err) => {
|
|
if is_err_invalid_upload_id(&err) {
|
|
None
|
|
} else {
|
|
Some(err)
|
|
}
|
|
}
|
|
};
|
|
|
|
if let Some(err) = err {
|
|
error!("put_object_part err: {:?}", err);
|
|
return Err(err);
|
|
}
|
|
}
|
|
|
|
Err(StorageError::InvalidUploadID(bucket.to_owned(), object.to_owned(), upload_id.to_owned()))
|
|
}
|
|
|
|
#[instrument(skip(self))]
|
|
pub(super) async fn handle_get_multipart_info(
|
|
&self,
|
|
bucket: &str,
|
|
object: &str,
|
|
upload_id: &str,
|
|
opts: &ObjectOptions,
|
|
) -> Result<MultipartInfo> {
|
|
check_list_parts_args(bucket, object, upload_id)?;
|
|
if self.single_pool() {
|
|
return self.pools[0].get_multipart_info(bucket, object, upload_id, opts).await;
|
|
}
|
|
|
|
for pool in self.pools.iter() {
|
|
if self.is_suspended(pool.pool_idx).await {
|
|
continue;
|
|
}
|
|
|
|
return match pool.get_multipart_info(bucket, object, upload_id, opts).await {
|
|
Ok(res) => Ok(res),
|
|
Err(err) => {
|
|
if is_err_invalid_upload_id(&err) {
|
|
continue;
|
|
}
|
|
|
|
Err(err)
|
|
}
|
|
};
|
|
}
|
|
|
|
Err(StorageError::InvalidUploadID(bucket.to_owned(), object.to_owned(), upload_id.to_owned()))
|
|
}
|
|
|
|
#[instrument(skip(self))]
|
|
pub(super) async fn handle_abort_multipart_upload(
|
|
&self,
|
|
bucket: &str,
|
|
object: &str,
|
|
upload_id: &str,
|
|
opts: &ObjectOptions,
|
|
) -> Result<()> {
|
|
check_abort_multipart_args(bucket, object, upload_id)?;
|
|
|
|
// TODO: defer DeleteUploadID
|
|
|
|
if self.single_pool() {
|
|
return self.pools[0].abort_multipart_upload(bucket, object, upload_id, opts).await;
|
|
}
|
|
|
|
for pool in self.pools.iter() {
|
|
if self.is_suspended(pool.pool_idx).await {
|
|
continue;
|
|
}
|
|
|
|
let err = match pool.abort_multipart_upload(bucket, object, upload_id, opts).await {
|
|
Ok(_) => return Ok(()),
|
|
Err(err) => {
|
|
//
|
|
if is_err_invalid_upload_id(&err) { None } else { Some(err) }
|
|
}
|
|
};
|
|
|
|
if let Some(er) = err {
|
|
return Err(er);
|
|
}
|
|
}
|
|
|
|
Err(StorageError::InvalidUploadID(bucket.to_owned(), object.to_owned(), upload_id.to_owned()))
|
|
}
|
|
|
|
#[instrument(skip(self))]
|
|
pub(super) async fn handle_complete_multipart_upload(
|
|
self: Arc<Self>,
|
|
bucket: &str,
|
|
object: &str,
|
|
upload_id: &str,
|
|
uploaded_parts: Vec<CompletePart>,
|
|
opts: &ObjectOptions,
|
|
) -> Result<ObjectInfo> {
|
|
check_complete_multipart_args(bucket, object, upload_id)?;
|
|
|
|
if self.single_pool() {
|
|
return self.pools[0]
|
|
.clone()
|
|
.complete_multipart_upload(bucket, object, upload_id, uploaded_parts, opts)
|
|
.await;
|
|
}
|
|
|
|
for pool in self.pools.iter() {
|
|
if self.is_suspended(pool.pool_idx).await {
|
|
continue;
|
|
}
|
|
|
|
let pool = pool.clone();
|
|
let err = match pool
|
|
.complete_multipart_upload(bucket, object, upload_id, uploaded_parts.clone(), opts)
|
|
.await
|
|
{
|
|
Ok(res) => return Ok(res),
|
|
Err(err) => {
|
|
//
|
|
if is_err_invalid_upload_id(&err) { None } else { Some(err) }
|
|
}
|
|
};
|
|
|
|
if let Some(er) = err {
|
|
return Err(er);
|
|
}
|
|
}
|
|
|
|
Err(StorageError::InvalidUploadID(bucket.to_owned(), object.to_owned(), upload_id.to_owned()))
|
|
}
|
|
}
|