From 3a54647e1770156cbe07a07a108ca7063c30f10a Mon Sep 17 00:00:00 2001 From: jiuker Date: Thu, 25 Jun 2026 16:06:12 +0800 Subject: [PATCH 1/8] refactor refactor --- include/miniocpp/baseclient.h | 94 +++++++ include/miniocpp/client.h | 11 + src/baseclient.cc | 371 +++++++++++++++++++++++++++ src/client.cc | 47 ++++ tests/tests.cc | 460 ++++++++++++++++++++++++++++++++++ 5 files changed, 983 insertions(+) diff --git a/include/miniocpp/baseclient.h b/include/miniocpp/baseclient.h index 82f47805..1b9c3364 100644 --- a/include/miniocpp/baseclient.h +++ b/include/miniocpp/baseclient.h @@ -18,6 +18,7 @@ #ifndef MINIO_CPP_BASECLIENT_H_INCLUDED #define MINIO_CPP_BASECLIENT_H_INCLUDED +#include #include #include #include @@ -171,6 +172,99 @@ class BaseClient { UploadPartResponse UploadPart(UploadPartArgs args); UploadPartCopyResponse UploadPartCopy(UploadPartCopyArgs args); + // Async overloads — return std::future backed by std::async. + // These are additive and non-breaking; sync methods remain unchanged. + std::future AbortMultipartUploadAsync( + AbortMultipartUploadArgs args); + std::future BucketExistsAsync(BucketExistsArgs args); + std::future CompleteMultipartUploadAsync( + CompleteMultipartUploadArgs args); + std::future CreateMultipartUploadAsync( + CreateMultipartUploadArgs args); + std::future DeleteBucketEncryptionAsync( + DeleteBucketEncryptionArgs args); + std::future DeleteBucketLifecycleAsync( + DeleteBucketLifecycleArgs args); + std::future DeleteBucketNotificationAsync( + DeleteBucketNotificationArgs args); + std::future DeleteBucketPolicyAsync( + DeleteBucketPolicyArgs args); + std::future DeleteBucketReplicationAsync( + DeleteBucketReplicationArgs args); + std::future DeleteBucketTagsAsync( + DeleteBucketTagsArgs args); + std::future DeleteObjectLockConfigAsync( + DeleteObjectLockConfigArgs args); + std::future DeleteObjectTagsAsync( + DeleteObjectTagsArgs args); + std::future DisableObjectLegalHoldAsync( + DisableObjectLegalHoldArgs args); + std::future EnableObjectLegalHoldAsync( + EnableObjectLegalHoldArgs args); + std::future GetBucketEncryptionAsync( + GetBucketEncryptionArgs args); + std::future GetBucketLifecycleAsync( + GetBucketLifecycleArgs args); + std::future GetBucketNotificationAsync( + GetBucketNotificationArgs args); + std::future GetBucketPolicyAsync( + GetBucketPolicyArgs args); + std::future GetBucketReplicationAsync( + GetBucketReplicationArgs args); + std::future GetBucketTagsAsync(GetBucketTagsArgs args); + std::future GetBucketVersioningAsync( + GetBucketVersioningArgs args); + std::future GetObjectAsync(GetObjectArgs args); + std::future GetObjectLockConfigAsync( + GetObjectLockConfigArgs args); + std::future GetObjectRetentionAsync( + GetObjectRetentionArgs args); + std::future GetObjectTagsAsync(GetObjectTagsArgs args); + std::future GetPresignedObjectUrlAsync( + GetPresignedObjectUrlArgs args); + std::future GetPresignedPostFormDataAsync( + PostPolicy policy); + std::future IsObjectLegalHoldEnabledAsync( + IsObjectLegalHoldEnabledArgs args); + std::future ListBucketsAsync(ListBucketsArgs args); + std::future ListBucketsAsync(); + std::future ListenBucketNotificationAsync( + ListenBucketNotificationArgs args); + std::future ListObjectsV1Async(ListObjectsV1Args args); + std::future ListObjectsV2Async(ListObjectsV2Args args); + std::future ListObjectVersionsAsync( + ListObjectVersionsArgs args); + std::future MakeBucketAsync(MakeBucketArgs args); + std::future PutObjectAsync(PutObjectApiArgs args); + std::future RemoveBucketAsync(RemoveBucketArgs args); + std::future RemoveObjectAsync(RemoveObjectArgs args); + std::future RemoveObjectsAsync( + RemoveObjectsApiArgs args); + std::future SelectObjectContentAsync( + SelectObjectContentArgs args); + std::future SetBucketEncryptionAsync( + SetBucketEncryptionArgs args); + std::future SetBucketLifecycleAsync( + SetBucketLifecycleArgs args); + std::future SetBucketNotificationAsync( + SetBucketNotificationArgs args); + std::future SetBucketPolicyAsync( + SetBucketPolicyArgs args); + std::future SetBucketReplicationAsync( + SetBucketReplicationArgs args); + std::future SetBucketTagsAsync(SetBucketTagsArgs args); + std::future SetBucketVersioningAsync( + SetBucketVersioningArgs args); + std::future SetObjectLockConfigAsync( + SetObjectLockConfigArgs args); + std::future SetObjectRetentionAsync( + SetObjectRetentionArgs args); + std::future SetObjectTagsAsync(SetObjectTagsArgs args); + std::future StatObjectAsync(StatObjectArgs args); + std::future UploadPartAsync(UploadPartArgs args); + std::future UploadPartCopyAsync( + UploadPartCopyArgs args); + // Windows API fix: // // Windows API headers define `GetObject()` as a macro that expands to either diff --git a/include/miniocpp/client.h b/include/miniocpp/client.h index 563f1542..4f01a1d1 100644 --- a/include/miniocpp/client.h +++ b/include/miniocpp/client.h @@ -18,6 +18,7 @@ #ifndef MINIO_CPP_CLIENT_H_INCLUDED #define MINIO_CPP_CLIENT_H_INCLUDED +#include #include #include @@ -138,6 +139,16 @@ class Client : public BaseClient { GetObjectResponse GetObject(GetObjectArgs args); UploadObjectResponse UploadObject(UploadObjectArgs args); RemoveObjectsResult RemoveObjects(RemoveObjectsArgs args); + + // Async overloads — return std::future backed by std::async. + std::future ComposeObjectAsync(ComposeObjectArgs args); + std::future CopyObjectAsync(CopyObjectArgs args); + std::future DownloadObjectAsync( + DownloadObjectArgs args); + std::future GetObjectAsync(GetObjectArgs args); + std::future ListObjectsAsync(ListObjectsArgs args); + std::future PutObjectAsync(PutObjectArgs args); + std::future UploadObjectAsync(UploadObjectArgs args); }; // class Client } // namespace minio::s3 diff --git a/src/baseclient.cc b/src/baseclient.cc index 155a61ac..7bdc402d 100644 --- a/src/baseclient.cc +++ b/src/baseclient.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -2049,4 +2050,374 @@ UploadPartCopyResponse BaseClient::UploadPartCopy(UploadPartCopyArgs args) { return resp; } +// ---- Async overloads ---- + +std::future BaseClient::AbortMultipartUploadAsync( + AbortMultipartUploadArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return AbortMultipartUpload(args); + }); +} + +std::future BaseClient::BucketExistsAsync( + BucketExistsArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return BucketExists(args); + }); +} + +std::future +BaseClient::CompleteMultipartUploadAsync(CompleteMultipartUploadArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return CompleteMultipartUpload(args); + }); +} + +std::future +BaseClient::CreateMultipartUploadAsync(CreateMultipartUploadArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return CreateMultipartUpload(args); + }); +} + +std::future +BaseClient::DeleteBucketEncryptionAsync(DeleteBucketEncryptionArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return DeleteBucketEncryption(args); + }); +} + +std::future +BaseClient::DeleteBucketLifecycleAsync(DeleteBucketLifecycleArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return DeleteBucketLifecycle(args); + }); +} + +std::future +BaseClient::DeleteBucketNotificationAsync(DeleteBucketNotificationArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return DeleteBucketNotification(args); + }); +} + +std::future BaseClient::DeleteBucketPolicyAsync( + DeleteBucketPolicyArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return DeleteBucketPolicy(args); + }); +} + +std::future +BaseClient::DeleteBucketReplicationAsync(DeleteBucketReplicationArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return DeleteBucketReplication(args); + }); +} + +std::future BaseClient::DeleteBucketTagsAsync( + DeleteBucketTagsArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return DeleteBucketTags(args); + }); +} + +std::future +BaseClient::DeleteObjectLockConfigAsync(DeleteObjectLockConfigArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return DeleteObjectLockConfig(args); + }); +} + +std::future BaseClient::DeleteObjectTagsAsync( + DeleteObjectTagsArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return DeleteObjectTags(args); + }); +} + +std::future +BaseClient::DisableObjectLegalHoldAsync(DisableObjectLegalHoldArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return DisableObjectLegalHold(args); + }); +} + +std::future +BaseClient::EnableObjectLegalHoldAsync(EnableObjectLegalHoldArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return EnableObjectLegalHold(args); + }); +} + +std::future BaseClient::GetBucketEncryptionAsync( + GetBucketEncryptionArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetBucketEncryption(args); + }); +} + +std::future BaseClient::GetBucketLifecycleAsync( + GetBucketLifecycleArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetBucketLifecycle(args); + }); +} + +std::future +BaseClient::GetBucketNotificationAsync(GetBucketNotificationArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetBucketNotification(args); + }); +} + +std::future BaseClient::GetBucketPolicyAsync( + GetBucketPolicyArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetBucketPolicy(args); + }); +} + +std::future BaseClient::GetBucketReplicationAsync( + GetBucketReplicationArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetBucketReplication(args); + }); +} + +std::future BaseClient::GetBucketTagsAsync( + GetBucketTagsArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetBucketTags(args); + }); +} + +std::future BaseClient::GetBucketVersioningAsync( + GetBucketVersioningArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetBucketVersioning(args); + }); +} + +std::future BaseClient::GetObjectAsync(GetObjectArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetObject(args); + }); +} + +std::future BaseClient::GetObjectLockConfigAsync( + GetObjectLockConfigArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetObjectLockConfig(args); + }); +} + +std::future BaseClient::GetObjectRetentionAsync( + GetObjectRetentionArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetObjectRetention(args); + }); +} + +std::future BaseClient::GetObjectTagsAsync( + GetObjectTagsArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetObjectTags(args); + }); +} + +std::future +BaseClient::GetPresignedObjectUrlAsync(GetPresignedObjectUrlArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return GetPresignedObjectUrl(args); + }); +} + +std::future +BaseClient::GetPresignedPostFormDataAsync(PostPolicy policy) { + return std::async(std::launch::async, + [this, policy = std::move(policy)]() mutable { + return GetPresignedPostFormData(std::move(policy)); + }); +} + +std::future +BaseClient::IsObjectLegalHoldEnabledAsync(IsObjectLegalHoldEnabledArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return IsObjectLegalHoldEnabled(args); + }); +} + +std::future BaseClient::ListBucketsAsync( + ListBucketsArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return ListBuckets(args); + }); +} + +std::future BaseClient::ListBucketsAsync() { + return std::async(std::launch::async, [this]() { return ListBuckets(); }); +} + +std::future +BaseClient::ListenBucketNotificationAsync(ListenBucketNotificationArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return ListenBucketNotification(args); + }); +} + +std::future BaseClient::ListObjectsV1Async( + ListObjectsV1Args args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return ListObjectsV1(args); + }); +} + +std::future BaseClient::ListObjectsV2Async( + ListObjectsV2Args args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return ListObjectsV2(args); + }); +} + +std::future BaseClient::ListObjectVersionsAsync( + ListObjectVersionsArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return ListObjectVersions(args); + }); +} + +std::future BaseClient::MakeBucketAsync( + MakeBucketArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return MakeBucket(args); + }); +} + +std::future BaseClient::PutObjectAsync( + PutObjectApiArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return BaseClient::PutObject(args); + }); +} + +std::future BaseClient::RemoveBucketAsync( + RemoveBucketArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return RemoveBucket(args); + }); +} + +std::future BaseClient::RemoveObjectAsync( + RemoveObjectArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return RemoveObject(args); + }); +} + +std::future BaseClient::RemoveObjectsAsync( + RemoveObjectsApiArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return BaseClient::RemoveObjects(args); + }); +} + +std::future BaseClient::SelectObjectContentAsync( + SelectObjectContentArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return SelectObjectContent(args); + }); +} + +std::future BaseClient::SetBucketEncryptionAsync( + SetBucketEncryptionArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return SetBucketEncryption(args); + }); +} + +std::future BaseClient::SetBucketLifecycleAsync( + SetBucketLifecycleArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return SetBucketLifecycle(args); + }); +} + +std::future +BaseClient::SetBucketNotificationAsync(SetBucketNotificationArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return SetBucketNotification(args); + }); +} + +std::future BaseClient::SetBucketPolicyAsync( + SetBucketPolicyArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return SetBucketPolicy(args); + }); +} + +std::future BaseClient::SetBucketReplicationAsync( + SetBucketReplicationArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return SetBucketReplication(args); + }); +} + +std::future BaseClient::SetBucketTagsAsync( + SetBucketTagsArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return SetBucketTags(args); + }); +} + +std::future BaseClient::SetBucketVersioningAsync( + SetBucketVersioningArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return SetBucketVersioning(args); + }); +} + +std::future BaseClient::SetObjectLockConfigAsync( + SetObjectLockConfigArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return SetObjectLockConfig(args); + }); +} + +std::future BaseClient::SetObjectRetentionAsync( + SetObjectRetentionArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return SetObjectRetention(args); + }); +} + +std::future BaseClient::SetObjectTagsAsync( + SetObjectTagsArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return SetObjectTags(args); + }); +} + +std::future BaseClient::StatObjectAsync( + StatObjectArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return StatObject(args); + }); +} + +std::future BaseClient::UploadPartAsync( + UploadPartArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return UploadPart(args); + }); +} + +std::future BaseClient::UploadPartCopyAsync( + UploadPartCopyArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return UploadPartCopy(args); + }); +} + } // namespace minio::s3 diff --git a/src/client.cc b/src/client.cc index a543fecd..c3f78f80 100644 --- a/src/client.cc +++ b/src/client.cc @@ -1429,4 +1429,51 @@ RemoveObjectsResult Client::RemoveObjects(RemoveObjectsArgs args) { return RemoveObjectsResult(this, std::move(args)); } +// ---- Async overloads ---- + +std::future Client::ComposeObjectAsync( + ComposeObjectArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return ComposeObject(std::move(args)); + }); +} + +std::future Client::CopyObjectAsync(CopyObjectArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return CopyObject(std::move(args)); + }); +} + +std::future Client::DownloadObjectAsync( + DownloadObjectArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return DownloadObject(std::move(args)); + }); +} + +std::future Client::GetObjectAsync(GetObjectArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return Client::GetObject(std::move(args)); + }); +} + +std::future Client::ListObjectsAsync(ListObjectsArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return ListObjects(std::move(args)); + }); +} + +std::future Client::PutObjectAsync(PutObjectArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return PutObject(std::move(args)); + }); +} + +std::future Client::UploadObjectAsync( + UploadObjectArgs args) { + return std::async(std::launch::async, [this, args = std::move(args)]() { + return UploadObject(std::move(args)); + }); +} + } // namespace minio::s3 diff --git a/tests/tests.cc b/tests/tests.cc index d9c3e255..88a5d488 100644 --- a/tests/tests.cc +++ b/tests/tests.cc @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -780,6 +781,464 @@ class Tests { } } } + + void TestAsyncOperations() { + std::cout << "TestAsyncOperations()" << std::endl; + + // --- async MakeBucket + BucketExists --- + { + std::string bucket_name = RandBucketName(); + + { + minio::s3::MakeBucketArgs args; + args.bucket = bucket_name; + auto make_fut = client_.MakeBucketAsync(args); + minio::s3::MakeBucketResponse make_resp = make_fut.get(); + if (!make_resp) { + throw std::runtime_error("MakeBucketAsync(): " + + make_resp.Error().String()); + } + } + + { + minio::s3::BucketExistsArgs args; + args.bucket = bucket_name; + auto exists_fut = client_.BucketExistsAsync(args); + minio::s3::BucketExistsResponse exists_resp = exists_fut.get(); + if (!exists_resp) { + throw std::runtime_error("BucketExistsAsync(): " + + exists_resp.Error().String()); + } + if (!exists_resp.exist) { + throw std::runtime_error( + "BucketExistsAsync(): expected bucket to exist"); + } + } + + { + minio::s3::RemoveBucketArgs args; + args.bucket = bucket_name; + auto rm_fut = client_.RemoveBucketAsync(args); + minio::s3::RemoveBucketResponse rm_resp = rm_fut.get(); + if (!rm_resp) { + throw std::runtime_error("RemoveBucketAsync(): " + + rm_resp.Error().String()); + } + } + } + + // --- async PutObject + GetObject + StatObject + RemoveObject --- + { + std::string object_name = RandObjectName(); + std::string data = "TestAsyncOperations"; + { + std::stringstream ss(data); + minio::s3::PutObjectArgs args(ss, static_cast(data.length()), + 0); + args.bucket = bucket_name_; + args.object = object_name; + auto put_fut = client_.PutObjectAsync(std::move(args)); + minio::s3::PutObjectResponse put_resp = put_fut.get(); + if (!put_resp) { + throw std::runtime_error("PutObjectAsync(): " + + put_resp.Error().String()); + } + } + + // async StatObject + try { + { + minio::s3::StatObjectArgs args; + args.bucket = bucket_name_; + args.object = object_name; + auto stat_fut = client_.StatObjectAsync(std::move(args)); + minio::s3::StatObjectResponse stat_resp = stat_fut.get(); + if (!stat_resp) { + throw std::runtime_error("StatObjectAsync(): " + + stat_resp.Error().String()); + } + if (stat_resp.size != data.length()) { + throw std::runtime_error("StatObjectAsync(): expected size: " + + std::to_string(data.length()) + "; got: " + + std::to_string(stat_resp.size)); + } + } + + // async GetObject + { + std::string content; + minio::s3::GetObjectArgs gargs; + gargs.bucket = bucket_name_; + gargs.object = object_name; + gargs.datafunc = + [&content](minio::http::DataFunctionArgs args) -> bool { + content += args.datachunk; + return true; + }; + auto get_fut = client_.GetObjectAsync(std::move(gargs)); + minio::s3::GetObjectResponse get_resp = get_fut.get(); + if (!get_resp) { + throw std::runtime_error("GetObjectAsync(): " + + get_resp.Error().String()); + } + if (data != content) { + throw std::runtime_error("GetObjectAsync(): expected: " + data + + "; got: " + content); + } + } + + // async RemoveObject + { + minio::s3::RemoveObjectArgs args; + args.bucket = bucket_name_; + args.object = object_name; + auto rm_fut = client_.RemoveObjectAsync(std::move(args)); + minio::s3::RemoveObjectResponse rm_resp = rm_fut.get(); + if (!rm_resp) { + throw std::runtime_error("RemoveObjectAsync(): " + + rm_resp.Error().String()); + } + } + } catch (const std::runtime_error&) { + RemoveObject(bucket_name_, object_name); + throw; + } + } + + // --- async ListBuckets --- + { + auto fut = client_.ListBucketsAsync(minio::s3::ListBucketsArgs()); + minio::s3::ListBucketsResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("ListBucketsAsync(): " + + resp.Error().String()); + } + } + + // --- async CopyObject --- + { + std::string src_object = RandObjectName(); + std::string dst_object = RandObjectName(); + std::string data = "CopyObjectAsync-test"; + try { + { + std::stringstream ss(data); + minio::s3::PutObjectArgs args( + ss, static_cast(data.length()), 0); + args.bucket = bucket_name_; + args.object = src_object; + minio::s3::PutObjectResponse resp = client_.PutObject(args); + if (!resp) { + throw std::runtime_error("PutObject(): " + resp.Error().String()); + } + } + + minio::s3::CopySource source; + source.bucket = bucket_name_; + source.object = src_object; + minio::s3::CopyObjectArgs cargs; + cargs.bucket = bucket_name_; + cargs.object = dst_object; + cargs.source = source; + auto copy_fut = client_.CopyObjectAsync(std::move(cargs)); + minio::s3::CopyObjectResponse copy_resp = copy_fut.get(); + if (!copy_resp) { + throw std::runtime_error("CopyObjectAsync(): " + + copy_resp.Error().String()); + } + + RemoveObject(bucket_name_, src_object); + RemoveObject(bucket_name_, dst_object); + } catch (const std::runtime_error&) { + RemoveObject(bucket_name_, src_object); + RemoveObject(bucket_name_, dst_object); + throw; + } + } + + // --- async UploadObject --- + { + std::string data = "UploadObjectAsync-test"; + std::string filename = RandObjectName(); + { + std::ofstream file(filename); + file << data; + } + + std::string object_name = RandObjectName(); + try { + minio::s3::UploadObjectArgs args; + args.bucket = bucket_name_; + args.object = object_name; + args.filename = filename; + auto up_fut = client_.UploadObjectAsync(std::move(args)); + minio::s3::UploadObjectResponse up_resp = up_fut.get(); + if (!up_resp) { + throw std::runtime_error("UploadObjectAsync(): " + + up_resp.Error().String()); + } + std::filesystem::remove(filename); + RemoveObject(bucket_name_, object_name); + } catch (const std::runtime_error&) { + std::filesystem::remove(filename); + RemoveObject(bucket_name_, object_name); + throw; + } + } + + // --- async GetPresignedObjectUrl --- + { + std::string object_name = RandObjectName(); + std::string data = "PresignedAsync"; + std::stringstream ss(data); + { + minio::s3::PutObjectArgs args(ss, static_cast(data.length()), + 0); + args.bucket = bucket_name_; + args.object = object_name; + minio::s3::PutObjectResponse resp = client_.PutObject(args); + if (!resp) { + throw std::runtime_error("PutObject(): " + resp.Error().String()); + } + } + + try { + minio::s3::GetPresignedObjectUrlArgs args; + args.bucket = bucket_name_; + args.object = object_name; + args.method = minio::http::Method::kGet; + auto url_fut = client_.GetPresignedObjectUrlAsync(std::move(args)); + minio::s3::GetPresignedObjectUrlResponse url_resp = url_fut.get(); + if (!url_resp) { + throw std::runtime_error("GetPresignedObjectUrlAsync(): " + + url_resp.Error().String()); + } + if (url_resp.url.empty()) { + throw std::runtime_error( + "GetPresignedObjectUrlAsync(): url is empty"); + } + RemoveObject(bucket_name_, object_name); + } catch (const std::runtime_error&) { + RemoveObject(bucket_name_, object_name); + throw; + } + } + + // --- async Bucket versioning (set + get) --- + { + std::string bucket_name = RandBucketName(); + { + minio::s3::MakeBucketArgs args; + args.bucket = bucket_name; + auto fut = client_.MakeBucketAsync(args); + minio::s3::MakeBucketResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("MakeBucketAsync(): " + + resp.Error().String()); + } + } + + try { + // Set versioning enabled. + { + minio::s3::SetBucketVersioningArgs args; + args.bucket = bucket_name; + minio::s3::Boolean status(true); + args.status = status; + auto fut = client_.SetBucketVersioningAsync(std::move(args)); + minio::s3::SetBucketVersioningResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("SetBucketVersioningAsync(): " + + resp.Error().String()); + } + } + + // Get and verify versioning status. + { + minio::s3::GetBucketVersioningArgs args; + args.bucket = bucket_name; + auto fut = client_.GetBucketVersioningAsync(std::move(args)); + minio::s3::GetBucketVersioningResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("GetBucketVersioningAsync(): " + + resp.Error().String()); + } + if (!resp.status || !resp.status.Get()) { + throw std::runtime_error( + "GetBucketVersioningAsync(): expected Enabled"); + } + } + + { + minio::s3::RemoveBucketArgs args; + args.bucket = bucket_name; + auto fut = client_.RemoveBucketAsync(args); + minio::s3::RemoveBucketResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("RemoveBucketAsync(): " + + resp.Error().String()); + } + } + } catch (const std::runtime_error&) { + minio::s3::RemoveBucketArgs args; + args.bucket = bucket_name; + client_.RemoveBucket(args); + throw; + } + } + + // --- async Object tags (set + get + delete) --- + { + std::string object_name = RandObjectName(); + std::string data = "TagsAsync"; + std::stringstream ss(data); + { + minio::s3::PutObjectArgs args(ss, static_cast(data.length()), + 0); + args.bucket = bucket_name_; + args.object = object_name; + minio::s3::PutObjectResponse resp = client_.PutObject(args); + if (!resp) { + throw std::runtime_error("PutObject(): " + resp.Error().String()); + } + } + + try { + // Set tags. + { + minio::s3::SetObjectTagsArgs args; + args.bucket = bucket_name_; + args.object = object_name; + args.tags = {{"key1", "value1"}, {"key2", "value2"}}; + auto fut = client_.SetObjectTagsAsync(std::move(args)); + minio::s3::SetObjectTagsResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("SetObjectTagsAsync(): " + + resp.Error().String()); + } + } + + // Get and verify tags. + { + minio::s3::GetObjectTagsArgs args; + args.bucket = bucket_name_; + args.object = object_name; + auto fut = client_.GetObjectTagsAsync(std::move(args)); + minio::s3::GetObjectTagsResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("GetObjectTagsAsync(): " + + resp.Error().String()); + } + if (resp.tags.size() != 2 || resp.tags["key1"] != "value1" || + resp.tags["key2"] != "value2") { + throw std::runtime_error("GetObjectTagsAsync(): tag mismatch"); + } + } + + // Delete tags. + { + minio::s3::DeleteObjectTagsArgs args; + args.bucket = bucket_name_; + args.object = object_name; + auto fut = client_.DeleteObjectTagsAsync(std::move(args)); + minio::s3::DeleteObjectTagsResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("DeleteObjectTagsAsync(): " + + resp.Error().String()); + } + } + + RemoveObject(bucket_name_, object_name); + } catch (const std::runtime_error&) { + RemoveObject(bucket_name_, object_name); + throw; + } + } + + // --- async Bucket tags (set + get + delete) --- + { + std::string bucket_name = RandBucketName(); + { + minio::s3::MakeBucketArgs args; + args.bucket = bucket_name; + auto fut = client_.MakeBucketAsync(args); + minio::s3::MakeBucketResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("MakeBucketAsync(): " + + resp.Error().String()); + } + } + + try { + // Set bucket tags. + { + minio::s3::SetBucketTagsArgs args; + args.bucket = bucket_name; + args.tags = {{"department", "engineering"}}; + auto fut = client_.SetBucketTagsAsync(std::move(args)); + minio::s3::SetBucketTagsResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("SetBucketTagsAsync(): " + + resp.Error().String()); + } + } + + // Get and verify bucket tags. + { + minio::s3::GetBucketTagsArgs args; + args.bucket = bucket_name; + auto fut = client_.GetBucketTagsAsync(std::move(args)); + minio::s3::GetBucketTagsResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("GetBucketTagsAsync(): " + + resp.Error().String()); + } + if (resp.tags.size() != 1 || + resp.tags["department"] != "engineering") { + throw std::runtime_error("GetBucketTagsAsync(): tag mismatch"); + } + } + + // Delete bucket tags. + { + minio::s3::DeleteBucketTagsArgs args; + args.bucket = bucket_name; + auto fut = client_.DeleteBucketTagsAsync(std::move(args)); + minio::s3::DeleteBucketTagsResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("DeleteBucketTagsAsync(): " + + resp.Error().String()); + } + } + + { + minio::s3::RemoveBucketArgs args; + args.bucket = bucket_name; + auto fut = client_.RemoveBucketAsync(args); + minio::s3::RemoveBucketResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("RemoveBucketAsync(): " + + resp.Error().String()); + } + } + } catch (const std::runtime_error&) { + minio::s3::RemoveBucketArgs args; + args.bucket = bucket_name; + client_.RemoveBucket(args); + throw; + } + } + + // --- async ListBuckets (no-arg overload) --- + { + auto fut = client_.ListBucketsAsync(); + minio::s3::ListBucketsResponse resp = fut.get(); + if (!resp) { + throw std::runtime_error("ListBucketsAsync() (no-arg): " + + resp.Error().String()); + } + } + } // TestAsyncOperations }; // class Tests int main(int /*argc*/, char* /*argv*/[]) { @@ -838,6 +1297,7 @@ int main(int /*argc*/, char* /*argv*/[]) { tests.RemoveObjects(); tests.SelectObjectContent(); tests.ListenBucketNotification(); + tests.TestAsyncOperations(); return EXIT_SUCCESS; } From 897b3885ac5cfff16297b88a8a1e3989a610c61b Mon Sep 17 00:00:00 2001 From: jiuker Date: Thu, 25 Jun 2026 16:25:37 +0800 Subject: [PATCH 2/8] apply suggestion apply suggestion --- src/baseclient.cc | 440 +++++++++++++++++++++++++++++----------------- src/client.cc | 49 +++--- tests/tests.cc | 249 ++++++++++++++++++++++++++ 3 files changed, 554 insertions(+), 184 deletions(-) diff --git a/src/baseclient.cc b/src/baseclient.cc index 7bdc402d..c3613985 100644 --- a/src/baseclient.cc +++ b/src/baseclient.cc @@ -2054,183 +2054,209 @@ UploadPartCopyResponse BaseClient::UploadPartCopy(UploadPartCopyArgs args) { std::future BaseClient::AbortMultipartUploadAsync( AbortMultipartUploadArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return AbortMultipartUpload(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return AbortMultipartUpload(std::move(args)); + }); } std::future BaseClient::BucketExistsAsync( BucketExistsArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return BucketExists(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return BucketExists(std::move(args)); + }); } std::future BaseClient::CompleteMultipartUploadAsync(CompleteMultipartUploadArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return CompleteMultipartUpload(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return CompleteMultipartUpload(std::move(args)); + }); } std::future BaseClient::CreateMultipartUploadAsync(CreateMultipartUploadArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return CreateMultipartUpload(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return CreateMultipartUpload(std::move(args)); + }); } std::future BaseClient::DeleteBucketEncryptionAsync(DeleteBucketEncryptionArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return DeleteBucketEncryption(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return DeleteBucketEncryption(std::move(args)); + }); } std::future BaseClient::DeleteBucketLifecycleAsync(DeleteBucketLifecycleArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return DeleteBucketLifecycle(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return DeleteBucketLifecycle(std::move(args)); + }); } std::future BaseClient::DeleteBucketNotificationAsync(DeleteBucketNotificationArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return DeleteBucketNotification(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return DeleteBucketNotification(std::move(args)); + }); } std::future BaseClient::DeleteBucketPolicyAsync( DeleteBucketPolicyArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return DeleteBucketPolicy(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return DeleteBucketPolicy(std::move(args)); + }); } std::future BaseClient::DeleteBucketReplicationAsync(DeleteBucketReplicationArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return DeleteBucketReplication(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return DeleteBucketReplication(std::move(args)); + }); } std::future BaseClient::DeleteBucketTagsAsync( DeleteBucketTagsArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return DeleteBucketTags(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return DeleteBucketTags(std::move(args)); + }); } std::future BaseClient::DeleteObjectLockConfigAsync(DeleteObjectLockConfigArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return DeleteObjectLockConfig(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return DeleteObjectLockConfig(std::move(args)); + }); } std::future BaseClient::DeleteObjectTagsAsync( DeleteObjectTagsArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return DeleteObjectTags(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return DeleteObjectTags(std::move(args)); + }); } std::future BaseClient::DisableObjectLegalHoldAsync(DisableObjectLegalHoldArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return DisableObjectLegalHold(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return DisableObjectLegalHold(std::move(args)); + }); } std::future BaseClient::EnableObjectLegalHoldAsync(EnableObjectLegalHoldArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return EnableObjectLegalHold(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return EnableObjectLegalHold(std::move(args)); + }); } std::future BaseClient::GetBucketEncryptionAsync( GetBucketEncryptionArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetBucketEncryption(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetBucketEncryption(std::move(args)); + }); } std::future BaseClient::GetBucketLifecycleAsync( GetBucketLifecycleArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetBucketLifecycle(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetBucketLifecycle(std::move(args)); + }); } std::future BaseClient::GetBucketNotificationAsync(GetBucketNotificationArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetBucketNotification(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetBucketNotification(std::move(args)); + }); } std::future BaseClient::GetBucketPolicyAsync( GetBucketPolicyArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetBucketPolicy(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetBucketPolicy(std::move(args)); + }); } std::future BaseClient::GetBucketReplicationAsync( GetBucketReplicationArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetBucketReplication(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetBucketReplication(std::move(args)); + }); } std::future BaseClient::GetBucketTagsAsync( GetBucketTagsArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetBucketTags(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetBucketTags(std::move(args)); + }); } std::future BaseClient::GetBucketVersioningAsync( GetBucketVersioningArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetBucketVersioning(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetBucketVersioning(std::move(args)); + }); } std::future BaseClient::GetObjectAsync(GetObjectArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetObject(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetObject(std::move(args)); + }); } std::future BaseClient::GetObjectLockConfigAsync( GetObjectLockConfigArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetObjectLockConfig(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetObjectLockConfig(std::move(args)); + }); } std::future BaseClient::GetObjectRetentionAsync( GetObjectRetentionArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetObjectRetention(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetObjectRetention(std::move(args)); + }); } std::future BaseClient::GetObjectTagsAsync( GetObjectTagsArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetObjectTags(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetObjectTags(std::move(args)); + }); } std::future BaseClient::GetPresignedObjectUrlAsync(GetPresignedObjectUrlArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return GetPresignedObjectUrl(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return GetPresignedObjectUrl(std::move(args)); + }); } std::future @@ -2243,16 +2269,18 @@ BaseClient::GetPresignedPostFormDataAsync(PostPolicy policy) { std::future BaseClient::IsObjectLegalHoldEnabledAsync(IsObjectLegalHoldEnabledArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return IsObjectLegalHoldEnabled(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return IsObjectLegalHoldEnabled(std::move(args)); + }); } std::future BaseClient::ListBucketsAsync( ListBucketsArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return ListBuckets(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return ListBuckets(std::move(args)); + }); } std::future BaseClient::ListBucketsAsync() { @@ -2261,163 +2289,249 @@ std::future BaseClient::ListBucketsAsync() { std::future BaseClient::ListenBucketNotificationAsync(ListenBucketNotificationArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return ListenBucketNotification(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return ListenBucketNotification(std::move(args)); + }); } std::future BaseClient::ListObjectsV1Async( ListObjectsV1Args args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return ListObjectsV1(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return ListObjectsV1(std::move(args)); + }); } std::future BaseClient::ListObjectsV2Async( ListObjectsV2Args args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return ListObjectsV2(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return ListObjectsV2(std::move(args)); + }); } std::future BaseClient::ListObjectVersionsAsync( ListObjectVersionsArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return ListObjectVersions(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return ListObjectVersions(std::move(args)); + }); } std::future BaseClient::MakeBucketAsync( MakeBucketArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return MakeBucket(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return MakeBucket(std::move(args)); + }); } std::future BaseClient::PutObjectAsync( PutObjectApiArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return BaseClient::PutObject(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return BaseClient::PutObject(std::move(args)); + }); } std::future BaseClient::RemoveBucketAsync( RemoveBucketArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return RemoveBucket(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return RemoveBucket(std::move(args)); + }); } std::future BaseClient::RemoveObjectAsync( RemoveObjectArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return RemoveObject(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return RemoveObject(std::move(args)); + }); } std::future BaseClient::RemoveObjectsAsync( RemoveObjectsApiArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return BaseClient::RemoveObjects(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return BaseClient::RemoveObjects(std::move(args)); + }); } +// Reference-holding arg: SelectObjectContentArgs has SelectRequest& request. +// Capture owned copy of the request so the async lambda doesn't dangle. std::future BaseClient::SelectObjectContentAsync( SelectObjectContentArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return SelectObjectContent(args); - }); -} - + auto owned_request = std::make_shared(args.request); + auto owned_func = std::move(args.resultfunc); + return std::async( + std::launch::async, + [this, owned_request, owned_func = std::move(owned_func), + bucket = std::move(args.bucket), region = std::move(args.region), + object = std::move(args.object), version_id = std::move(args.version_id), + ssec = args.ssec, extra_headers = std::move(args.extra_headers), + extra_query_params = std::move(args.extra_query_params)]() mutable { + SelectObjectContentArgs new_args(*owned_request, std::move(owned_func)); + new_args.bucket = std::move(bucket); + new_args.region = std::move(region); + new_args.object = std::move(object); + new_args.version_id = std::move(version_id); + new_args.ssec = ssec; + new_args.extra_headers = std::move(extra_headers); + new_args.extra_query_params = std::move(extra_query_params); + return SelectObjectContent(std::move(new_args)); + }); +} + +// Reference-holding arg: SetBucketEncryptionArgs has SseConfig& config. std::future BaseClient::SetBucketEncryptionAsync( SetBucketEncryptionArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return SetBucketEncryption(args); - }); -} - + auto owned_config = std::make_shared(args.config); + return std::async( + std::launch::async, + [this, owned_config, bucket = std::move(args.bucket), + region = std::move(args.region), + extra_headers = std::move(args.extra_headers), + extra_query_params = std::move(args.extra_query_params)]() mutable { + SetBucketEncryptionArgs new_args(*owned_config); + new_args.bucket = std::move(bucket); + new_args.region = std::move(region); + new_args.extra_headers = std::move(extra_headers); + new_args.extra_query_params = std::move(extra_query_params); + return SetBucketEncryption(std::move(new_args)); + }); +} + +// Reference-holding arg: SetBucketLifecycleArgs has LifecycleConfig& config. std::future BaseClient::SetBucketLifecycleAsync( SetBucketLifecycleArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return SetBucketLifecycle(args); - }); -} - + auto owned_config = std::make_shared(args.config); + return std::async( + std::launch::async, + [this, owned_config, bucket = std::move(args.bucket), + region = std::move(args.region), + extra_headers = std::move(args.extra_headers), + extra_query_params = std::move(args.extra_query_params)]() mutable { + SetBucketLifecycleArgs new_args(*owned_config); + new_args.bucket = std::move(bucket); + new_args.region = std::move(region); + new_args.extra_headers = std::move(extra_headers); + new_args.extra_query_params = std::move(extra_query_params); + return SetBucketLifecycle(std::move(new_args)); + }); +} + +// Reference-holding arg: SetBucketNotificationArgs has NotificationConfig& +// config. std::future BaseClient::SetBucketNotificationAsync(SetBucketNotificationArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return SetBucketNotification(args); - }); + auto owned_config = std::make_shared(args.config); + return std::async( + std::launch::async, + [this, owned_config, bucket = std::move(args.bucket), + region = std::move(args.region), + extra_headers = std::move(args.extra_headers), + extra_query_params = std::move(args.extra_query_params)]() mutable { + SetBucketNotificationArgs new_args(*owned_config); + new_args.bucket = std::move(bucket); + new_args.region = std::move(region); + new_args.extra_headers = std::move(extra_headers); + new_args.extra_query_params = std::move(extra_query_params); + return SetBucketNotification(std::move(new_args)); + }); +} + +// Reference-holding arg: SetBucketReplicationArgs has ReplicationConfig& +// config. +std::future BaseClient::SetBucketReplicationAsync( + SetBucketReplicationArgs args) { + auto owned_config = std::make_shared(args.config); + return std::async( + std::launch::async, + [this, owned_config, bucket = std::move(args.bucket), + region = std::move(args.region), + extra_headers = std::move(args.extra_headers), + extra_query_params = std::move(args.extra_query_params)]() mutable { + SetBucketReplicationArgs new_args(*owned_config); + new_args.bucket = std::move(bucket); + new_args.region = std::move(region); + new_args.extra_headers = std::move(extra_headers); + new_args.extra_query_params = std::move(extra_query_params); + return SetBucketReplication(std::move(new_args)); + }); } std::future BaseClient::SetBucketPolicyAsync( SetBucketPolicyArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return SetBucketPolicy(args); - }); -} - -std::future BaseClient::SetBucketReplicationAsync( - SetBucketReplicationArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return SetBucketReplication(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return SetBucketPolicy(std::move(args)); + }); } std::future BaseClient::SetBucketTagsAsync( SetBucketTagsArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return SetBucketTags(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return SetBucketTags(std::move(args)); + }); } std::future BaseClient::SetBucketVersioningAsync( SetBucketVersioningArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return SetBucketVersioning(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return SetBucketVersioning(std::move(args)); + }); } std::future BaseClient::SetObjectLockConfigAsync( SetObjectLockConfigArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return SetObjectLockConfig(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return SetObjectLockConfig(std::move(args)); + }); } std::future BaseClient::SetObjectRetentionAsync( SetObjectRetentionArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return SetObjectRetention(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return SetObjectRetention(std::move(args)); + }); } std::future BaseClient::SetObjectTagsAsync( SetObjectTagsArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return SetObjectTags(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return SetObjectTags(std::move(args)); + }); } std::future BaseClient::StatObjectAsync( StatObjectArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return StatObject(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return StatObject(std::move(args)); + }); } std::future BaseClient::UploadPartAsync( UploadPartArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return UploadPart(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return UploadPart(std::move(args)); + }); } std::future BaseClient::UploadPartCopyAsync( UploadPartCopyArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return UploadPartCopy(args); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return UploadPartCopy(std::move(args)); + }); } } // namespace minio::s3 diff --git a/src/client.cc b/src/client.cc index c3f78f80..82388b0c 100644 --- a/src/client.cc +++ b/src/client.cc @@ -1433,47 +1433,54 @@ RemoveObjectsResult Client::RemoveObjects(RemoveObjectsArgs args) { std::future Client::ComposeObjectAsync( ComposeObjectArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return ComposeObject(std::move(args)); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return ComposeObject(std::move(args)); + }); } std::future Client::CopyObjectAsync(CopyObjectArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return CopyObject(std::move(args)); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return CopyObject(std::move(args)); + }); } std::future Client::DownloadObjectAsync( DownloadObjectArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return DownloadObject(std::move(args)); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return DownloadObject(std::move(args)); + }); } std::future Client::GetObjectAsync(GetObjectArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return Client::GetObject(std::move(args)); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return Client::GetObject(std::move(args)); + }); } std::future Client::ListObjectsAsync(ListObjectsArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return ListObjects(std::move(args)); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return ListObjects(std::move(args)); + }); } std::future Client::PutObjectAsync(PutObjectArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return PutObject(std::move(args)); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return PutObject(std::move(args)); + }); } std::future Client::UploadObjectAsync( UploadObjectArgs args) { - return std::async(std::launch::async, [this, args = std::move(args)]() { - return UploadObject(std::move(args)); - }); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return UploadObject(std::move(args)); + }); } } // namespace minio::s3 diff --git a/tests/tests.cc b/tests/tests.cc index 88a5d488..8782bb3f 100644 --- a/tests/tests.cc +++ b/tests/tests.cc @@ -1238,6 +1238,255 @@ class Tests { resp.Error().String()); } } + + // --- Error path: async call on non-existent object --- + { + minio::s3::StatObjectArgs args; + args.bucket = bucket_name_; + args.object = "__nonexistent_object_async_test__"; + auto fut = client_.StatObjectAsync(std::move(args)); + minio::s3::StatObjectResponse resp = fut.get(); + // Must fail — object does not exist. + if (resp) { + throw std::runtime_error( + "StatObjectAsync() on nonexistent object: expected failure"); + } + // Error must carry a valid code from the server. + if (resp.Error().String().empty()) { + throw std::runtime_error( + "StatObjectAsync() on nonexistent object: expected error message"); + } + } + + // --- Concurrency: start multiple async ops before any get() --- + { + // Create three independent temp buckets asynchronously. + std::string b1 = RandBucketName(); + std::string b2 = RandBucketName(); + std::string b3 = RandBucketName(); + + minio::s3::MakeBucketArgs a1, a2, a3; + a1.bucket = b1; + a2.bucket = b2; + a3.bucket = b3; + + auto f1 = client_.MakeBucketAsync(std::move(a1)); + auto f2 = client_.MakeBucketAsync(std::move(a2)); + auto f3 = client_.MakeBucketAsync(std::move(a3)); + + // Now collect all results. + auto r1 = f1.get(); + auto r2 = f2.get(); + auto r3 = f3.get(); + + if (!r1 || !r2 || !r3) { + throw std::runtime_error("concurrent MakeBucketAsync(): one failed"); + } + + // Verify they all exist. + minio::s3::BucketExistsArgs be1, be2, be3; + be1.bucket = b1; + be2.bucket = b2; + be3.bucket = b3; + auto fe1 = client_.BucketExistsAsync(std::move(be1)); + auto fe2 = client_.BucketExistsAsync(std::move(be2)); + auto fe3 = client_.BucketExistsAsync(std::move(be3)); + if (!fe1.get().exist || !fe2.get().exist || !fe3.get().exist) { + throw std::runtime_error( + "concurrent BucketExistsAsync(): expected true"); + } + + // Clean up. + auto clean = [this](const std::string& b) { + minio::s3::RemoveBucketArgs args; + args.bucket = b; + client_.RemoveBucket(args); + }; + try { + clean(b1); + clean(b2); + clean(b3); + } catch (...) { + } + } + + // --- Reference-holding arg: SetBucketEncryptionAsync --- + { + std::string bucket_name = RandBucketName(); + { + minio::s3::MakeBucketArgs args; + args.bucket = bucket_name; + auto fut = client_.MakeBucketAsync(args); + if (!fut.get()) { + throw std::runtime_error("MakeBucketAsync(): failed"); + } + } + + // SseConfig lives in caller's stack — async must own a copy. + minio::s3::SseConfig sse_config = minio::s3::SseConfig::S3(); + minio::s3::SetBucketEncryptionArgs enc_args(sse_config); + enc_args.bucket = bucket_name; + + auto enc_fut = client_.SetBucketEncryptionAsync(std::move(enc_args)); + minio::s3::SetBucketEncryptionResponse enc_resp = enc_fut.get(); + // If the server supports encryption, this should succeed. + // Some test servers may not; treat any failure as non-fatal. + if (!enc_resp) { + std::cout << " SetBucketEncryptionAsync skipped: " + << enc_resp.Error().String() << std::endl; + } + + { + minio::s3::RemoveBucketArgs args; + args.bucket = bucket_name; + client_.RemoveBucket(args); + } + } + + // --- Reference-holding arg: SetBucketLifecycleAsync --- + { + std::string bucket_name = RandBucketName(); + { + minio::s3::MakeBucketArgs args; + args.bucket = bucket_name; + auto fut = client_.MakeBucketAsync(args); + if (!fut.get()) { + throw std::runtime_error("MakeBucketAsync(): failed"); + } + } + + minio::s3::LifecycleConfig lc_config; + minio::s3::SetBucketLifecycleArgs lc_args(lc_config); + lc_args.bucket = bucket_name; + + auto lc_fut = client_.SetBucketLifecycleAsync(std::move(lc_args)); + minio::s3::SetBucketLifecycleResponse lc_resp = lc_fut.get(); + if (!lc_resp) { + std::cout << " SetBucketLifecycleAsync skipped: " + << lc_resp.Error().String() << std::endl; + } + + { + minio::s3::RemoveBucketArgs args; + args.bucket = bucket_name; + client_.RemoveBucket(args); + } + } + + // --- Reference-holding arg: SetBucketNotificationAsync --- + { + std::string bucket_name = RandBucketName(); + { + minio::s3::MakeBucketArgs args; + args.bucket = bucket_name; + auto fut = client_.MakeBucketAsync(args); + if (!fut.get()) { + throw std::runtime_error("MakeBucketAsync(): failed"); + } + } + + minio::s3::NotificationConfig notif_config; + minio::s3::SetBucketNotificationArgs notif_args(notif_config); + notif_args.bucket = bucket_name; + + auto notif_fut = + client_.SetBucketNotificationAsync(std::move(notif_args)); + minio::s3::SetBucketNotificationResponse notif_resp = notif_fut.get(); + if (!notif_resp) { + std::cout << " SetBucketNotificationAsync skipped: " + << notif_resp.Error().String() << std::endl; + } + + { + minio::s3::RemoveBucketArgs args; + args.bucket = bucket_name; + client_.RemoveBucket(args); + } + } + + // --- Reference-holding arg: SetBucketReplicationAsync --- + { + std::string bucket_name = RandBucketName(); + { + minio::s3::MakeBucketArgs args; + args.bucket = bucket_name; + auto fut = client_.MakeBucketAsync(args); + if (!fut.get()) { + throw std::runtime_error("MakeBucketAsync(): failed"); + } + } + + minio::s3::ReplicationConfig repl_config; + minio::s3::SetBucketReplicationArgs repl_args(repl_config); + repl_args.bucket = bucket_name; + + auto repl_fut = client_.SetBucketReplicationAsync(std::move(repl_args)); + minio::s3::SetBucketReplicationResponse repl_resp = repl_fut.get(); + if (!repl_resp) { + std::cout << " SetBucketReplicationAsync skipped: " + << repl_resp.Error().String() << std::endl; + } + + { + minio::s3::RemoveBucketArgs args; + args.bucket = bucket_name; + client_.RemoveBucket(args); + } + } + + // --- Reference-holding arg: SelectObjectContentAsync --- + { + std::string object_name = RandObjectName(); + std::string data = + "1997,Ford,E350,\"ac, abs, moon\",3000.00\n" + "1999,Chevy,\"Venture \"\"Extended Edition\"\"\",,4900.00\n"; + std::stringstream ss("Year,Make,Model,Description,Price\n" + data); + { + minio::s3::PutObjectArgs args( + ss, static_cast(ss.str().length()), 0); + args.bucket = bucket_name_; + args.object = object_name; + auto fut = client_.PutObjectAsync(std::move(args)); + if (!fut.get()) { + throw std::runtime_error("PutObject(): failed"); + } + } + + std::string expression = "select * from S3Object"; + minio::s3::CsvInputSerialization csv_input; + minio::s3::FileHeaderInfo file_header_info = + minio::s3::FileHeaderInfo::kUse; + csv_input.file_header_info = &file_header_info; + minio::s3::CsvOutputSerialization csv_output; + minio::s3::QuoteFields quote_fields = minio::s3::QuoteFields::kAsNeeded; + csv_output.quote_fields = "e_fields; + + // SelectRequest and serialization objects live in caller's stack. + // SelectObjectContentAsync must own copies to avoid dangling. + minio::s3::SelectRequest request(expression, &csv_input, &csv_output); + std::string records; + auto func = [&records](minio::s3::SelectResult result) -> bool { + if (result.err) return false; + records += result.records; + return true; + }; + + minio::s3::SelectObjectContentArgs sel_args(request, func); + sel_args.bucket = bucket_name_; + sel_args.object = object_name; + + auto sel_fut = client_.SelectObjectContentAsync(std::move(sel_args)); + minio::s3::SelectObjectContentResponse sel_resp = sel_fut.get(); + if (!sel_resp && sel_resp.code == "MethodNotAllowed") { + std::cout << " SelectObjectContentAsync skipped: server does not " + "implement S3 Select" + << std::endl; + } else if (!sel_resp) { + throw std::runtime_error("SelectObjectContentAsync(): " + + sel_resp.Error().String()); + } + RemoveObject(bucket_name_, object_name); + } } // TestAsyncOperations }; // class Tests From 8ec6e3c60e396a9530f67cc0fc1d7890d9d342bd Mon Sep 17 00:00:00 2001 From: jiuker Date: Thu, 25 Jun 2026 16:46:09 +0800 Subject: [PATCH 3/8] apply suggestion apply suggestion --- src/baseclient.cc | 75 ++++++++++++++++++++- tests/tests.cc | 167 ++++++++++++++++++++++++++-------------------- 2 files changed, 167 insertions(+), 75 deletions(-) diff --git a/src/baseclient.cc b/src/baseclient.cc index c3613985..1458bbd2 100644 --- a/src/baseclient.cc +++ b/src/baseclient.cc @@ -1374,6 +1374,7 @@ MakeBucketResponse BaseClient::MakeBucket(MakeBucketArgs args) { Response resp = Execute(req); if (resp) { + std::unique_lock lock(region_map_mutex_); region_map_[args.bucket] = region; } return MakeBucketResponse(resp); @@ -2365,14 +2366,84 @@ std::future BaseClient::SelectObjectContentAsync( SelectObjectContentArgs args) { auto owned_request = std::make_shared(args.request); auto owned_func = std::move(args.resultfunc); + + // Deep-copy serialization objects that SelectRequest points to. + auto owned_csv_input = owned_request->csv_input + ? std::make_shared(*owned_request->csv_input) + : nullptr; + auto owned_json_input = owned_request->json_input + ? std::make_shared(*owned_request->json_input) + : nullptr; + auto owned_parquet_input = owned_request->parquet_input + ? std::make_shared( + *owned_request->parquet_input) + : nullptr; + auto owned_csv_output = owned_request->csv_output + ? std::make_shared(*owned_request->csv_output) + : nullptr; + auto owned_json_output = owned_request->json_output + ? std::make_shared(*owned_request->json_output) + : nullptr; + + // Deep-copy inner pointees within serialization objects. + auto owned_file_header_info = + owned_csv_input && owned_csv_input->file_header_info + ? std::make_shared( + *owned_csv_input->file_header_info) + : nullptr; + auto owned_csv_compression = + owned_csv_input && owned_csv_input->compression_type + ? std::make_shared( + *owned_csv_input->compression_type) + : nullptr; + auto owned_quote_fields = owned_csv_output && owned_csv_output->quote_fields + ? std::make_shared(*owned_csv_output->quote_fields) + : nullptr; + auto owned_json_type = owned_json_input && owned_json_input->json_type + ? std::make_shared(*owned_json_input->json_type) + : nullptr; + auto owned_json_compression = + owned_json_input && owned_json_input->compression_type + ? std::make_shared( + *owned_json_input->compression_type) + : nullptr; + return std::async( std::launch::async, - [this, owned_request, owned_func = std::move(owned_func), + [this, owned_request, owned_func = std::move(owned_func), owned_csv_input, + owned_json_input, owned_parquet_input, owned_csv_output, + owned_json_output, owned_file_header_info, owned_csv_compression, + owned_quote_fields, owned_json_type, owned_json_compression, bucket = std::move(args.bucket), region = std::move(args.region), object = std::move(args.object), version_id = std::move(args.version_id), ssec = args.ssec, extra_headers = std::move(args.extra_headers), extra_query_params = std::move(args.extra_query_params)]() mutable { - SelectObjectContentArgs new_args(*owned_request, std::move(owned_func)); + // Patch serialization pointers to owned copies. + if (owned_csv_input) + owned_request->csv_input = owned_csv_input.get(); + if (owned_json_input) + owned_request->json_input = owned_json_input.get(); + if (owned_parquet_input) + owned_request->parquet_input = owned_parquet_input.get(); + if (owned_csv_output) + owned_request->csv_output = owned_csv_output.get(); + if (owned_json_output) + owned_request->json_output = owned_json_output.get(); + + // Patch inner pointers within serialization objects. + if (owned_file_header_info && owned_csv_input) + owned_csv_input->file_header_info = owned_file_header_info.get(); + if (owned_csv_compression && owned_csv_input) + owned_csv_input->compression_type = owned_csv_compression.get(); + if (owned_quote_fields && owned_csv_output) + owned_csv_output->quote_fields = owned_quote_fields.get(); + if (owned_json_type && owned_json_input) + owned_json_input->json_type = owned_json_type.get(); + if (owned_json_compression && owned_json_input) + owned_json_input->compression_type = owned_json_compression.get(); + + SelectObjectContentArgs new_args(*owned_request, + std::move(owned_func)); new_args.bucket = std::move(bucket); new_args.region = std::move(region); new_args.object = std::move(object); diff --git a/tests/tests.cc b/tests/tests.cc index 8782bb3f..aab337e3 100644 --- a/tests/tests.cc +++ b/tests/tests.cc @@ -1246,12 +1246,10 @@ class Tests { args.object = "__nonexistent_object_async_test__"; auto fut = client_.StatObjectAsync(std::move(args)); minio::s3::StatObjectResponse resp = fut.get(); - // Must fail — object does not exist. if (resp) { throw std::runtime_error( "StatObjectAsync() on nonexistent object: expected failure"); } - // Error must carry a valid code from the server. if (resp.Error().String().empty()) { throw std::runtime_error( "StatObjectAsync() on nonexistent object: expected error message"); @@ -1260,7 +1258,6 @@ class Tests { // --- Concurrency: start multiple async ops before any get() --- { - // Create three independent temp buckets asynchronously. std::string b1 = RandBucketName(); std::string b2 = RandBucketName(); std::string b3 = RandBucketName(); @@ -1274,39 +1271,46 @@ class Tests { auto f2 = client_.MakeBucketAsync(std::move(a2)); auto f3 = client_.MakeBucketAsync(std::move(a3)); - // Now collect all results. - auto r1 = f1.get(); - auto r2 = f2.get(); - auto r3 = f3.get(); + auto cleanup = [this](const std::string& b1, const std::string& b2, + const std::string& b3) { + for (auto& b : {b1, b2, b3}) { + try { + minio::s3::RemoveBucketArgs args; + args.bucket = b; + client_.RemoveBucket(args); + } catch (...) { + } + } + }; - if (!r1 || !r2 || !r3) { - throw std::runtime_error("concurrent MakeBucketAsync(): one failed"); - } + try { + auto r1 = f1.get(); + auto r2 = f2.get(); + auto r3 = f3.get(); - // Verify they all exist. - minio::s3::BucketExistsArgs be1, be2, be3; - be1.bucket = b1; - be2.bucket = b2; - be3.bucket = b3; - auto fe1 = client_.BucketExistsAsync(std::move(be1)); - auto fe2 = client_.BucketExistsAsync(std::move(be2)); - auto fe3 = client_.BucketExistsAsync(std::move(be3)); - if (!fe1.get().exist || !fe2.get().exist || !fe3.get().exist) { - throw std::runtime_error( - "concurrent BucketExistsAsync(): expected true"); - } + if (!r1 || !r2 || !r3) { + cleanup(b1, b2, b3); + throw std::runtime_error( + "concurrent MakeBucketAsync(): one failed"); + } - // Clean up. - auto clean = [this](const std::string& b) { - minio::s3::RemoveBucketArgs args; - args.bucket = b; - client_.RemoveBucket(args); - }; - try { - clean(b1); - clean(b2); - clean(b3); + minio::s3::BucketExistsArgs be1, be2, be3; + be1.bucket = b1; + be2.bucket = b2; + be3.bucket = b3; + auto fe1 = client_.BucketExistsAsync(std::move(be1)); + auto fe2 = client_.BucketExistsAsync(std::move(be2)); + auto fe3 = client_.BucketExistsAsync(std::move(be3)); + if (!fe1.get().exist || !fe2.get().exist || !fe3.get().exist) { + cleanup(b1, b2, b3); + throw std::runtime_error( + "concurrent BucketExistsAsync(): expected true"); + } + + cleanup(b1, b2, b3); } catch (...) { + cleanup(b1, b2, b3); + throw; } } @@ -1322,15 +1326,17 @@ class Tests { } } - // SseConfig lives in caller's stack — async must own a copy. - minio::s3::SseConfig sse_config = minio::s3::SseConfig::S3(); - minio::s3::SetBucketEncryptionArgs enc_args(sse_config); - enc_args.bucket = bucket_name; + std::future enc_fut; + { + // SseConfig lives only in this scope; async must own a copy. + minio::s3::SseConfig sse_config = minio::s3::SseConfig::S3(); + minio::s3::SetBucketEncryptionArgs enc_args(sse_config); + enc_args.bucket = bucket_name; + enc_fut = client_.SetBucketEncryptionAsync(std::move(enc_args)); + // sse_config and enc_args go out of scope here. + } - auto enc_fut = client_.SetBucketEncryptionAsync(std::move(enc_args)); minio::s3::SetBucketEncryptionResponse enc_resp = enc_fut.get(); - // If the server supports encryption, this should succeed. - // Some test servers may not; treat any failure as non-fatal. if (!enc_resp) { std::cout << " SetBucketEncryptionAsync skipped: " << enc_resp.Error().String() << std::endl; @@ -1355,11 +1361,15 @@ class Tests { } } - minio::s3::LifecycleConfig lc_config; - minio::s3::SetBucketLifecycleArgs lc_args(lc_config); - lc_args.bucket = bucket_name; + std::future lc_fut; + { + minio::s3::LifecycleConfig lc_config; + minio::s3::SetBucketLifecycleArgs lc_args(lc_config); + lc_args.bucket = bucket_name; + lc_fut = client_.SetBucketLifecycleAsync(std::move(lc_args)); + // lc_config and lc_args go out of scope here. + } - auto lc_fut = client_.SetBucketLifecycleAsync(std::move(lc_args)); minio::s3::SetBucketLifecycleResponse lc_resp = lc_fut.get(); if (!lc_resp) { std::cout << " SetBucketLifecycleAsync skipped: " @@ -1385,12 +1395,16 @@ class Tests { } } - minio::s3::NotificationConfig notif_config; - minio::s3::SetBucketNotificationArgs notif_args(notif_config); - notif_args.bucket = bucket_name; + std::future notif_fut; + { + minio::s3::NotificationConfig notif_config; + minio::s3::SetBucketNotificationArgs notif_args(notif_config); + notif_args.bucket = bucket_name; + notif_fut = + client_.SetBucketNotificationAsync(std::move(notif_args)); + // notif_config and notif_args go out of scope here. + } - auto notif_fut = - client_.SetBucketNotificationAsync(std::move(notif_args)); minio::s3::SetBucketNotificationResponse notif_resp = notif_fut.get(); if (!notif_resp) { std::cout << " SetBucketNotificationAsync skipped: " @@ -1416,11 +1430,15 @@ class Tests { } } - minio::s3::ReplicationConfig repl_config; - minio::s3::SetBucketReplicationArgs repl_args(repl_config); - repl_args.bucket = bucket_name; + std::future repl_fut; + { + minio::s3::ReplicationConfig repl_config; + minio::s3::SetBucketReplicationArgs repl_args(repl_config); + repl_args.bucket = bucket_name; + repl_fut = client_.SetBucketReplicationAsync(std::move(repl_args)); + // repl_config and repl_args go out of scope here. + } - auto repl_fut = client_.SetBucketReplicationAsync(std::move(repl_args)); minio::s3::SetBucketReplicationResponse repl_resp = repl_fut.get(); if (!repl_resp) { std::cout << " SetBucketReplicationAsync skipped: " @@ -1452,30 +1470,33 @@ class Tests { } } - std::string expression = "select * from S3Object"; - minio::s3::CsvInputSerialization csv_input; - minio::s3::FileHeaderInfo file_header_info = - minio::s3::FileHeaderInfo::kUse; - csv_input.file_header_info = &file_header_info; - minio::s3::CsvOutputSerialization csv_output; - minio::s3::QuoteFields quote_fields = minio::s3::QuoteFields::kAsNeeded; - csv_output.quote_fields = "e_fields; - - // SelectRequest and serialization objects live in caller's stack. - // SelectObjectContentAsync must own copies to avoid dangling. - minio::s3::SelectRequest request(expression, &csv_input, &csv_output); + std::future sel_fut; std::string records; - auto func = [&records](minio::s3::SelectResult result) -> bool { - if (result.err) return false; - records += result.records; - return true; - }; + { + std::string expression = "select * from S3Object"; + minio::s3::CsvInputSerialization csv_input; + minio::s3::FileHeaderInfo file_header_info = + minio::s3::FileHeaderInfo::kUse; + csv_input.file_header_info = &file_header_info; + minio::s3::CsvOutputSerialization csv_output; + minio::s3::QuoteFields quote_fields = minio::s3::QuoteFields::kAsNeeded; + csv_output.quote_fields = "e_fields; + + minio::s3::SelectRequest request(expression, &csv_input, &csv_output); + auto func = [&records](minio::s3::SelectResult result) -> bool { + if (result.err) return false; + records += result.records; + return true; + }; - minio::s3::SelectObjectContentArgs sel_args(request, func); - sel_args.bucket = bucket_name_; - sel_args.object = object_name; + minio::s3::SelectObjectContentArgs sel_args(request, func); + sel_args.bucket = bucket_name_; + sel_args.object = object_name; + + sel_fut = client_.SelectObjectContentAsync(std::move(sel_args)); + // csv_input, csv_output, request, etc. go out of scope here. + } - auto sel_fut = client_.SelectObjectContentAsync(std::move(sel_args)); minio::s3::SelectObjectContentResponse sel_resp = sel_fut.get(); if (!sel_resp && sel_resp.code == "MethodNotAllowed") { std::cout << " SelectObjectContentAsync skipped: server does not " From dda62d810b5369782d24f05d1996340d263d02e5 Mon Sep 17 00:00:00 2001 From: jiuker Date: Thu, 25 Jun 2026 16:52:16 +0800 Subject: [PATCH 4/8] apply suggestion apply suggestion --- include/miniocpp/args.h | 3 +++ include/miniocpp/client.h | 4 +++ src/baseclient.cc | 55 +++++++++++++++++++++------------------ tests/tests.cc | 6 ++--- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/include/miniocpp/args.h b/include/miniocpp/args.h index 13548ce4..96823988 100644 --- a/include/miniocpp/args.h +++ b/include/miniocpp/args.h @@ -337,6 +337,9 @@ struct PutObjectArgs : public PutObjectBaseArgs { // Exactly one of (stream, buf) must be set; Validate() enforces. // When buf is set, the call attempts RDMA and falls back to a // streaming HTTP upload from the same buffer on RDMA decline. + // + // For async callers (PutObjectAsync): *stream must outlive the + // returned std::future, exactly as it must for sync PutObject. std::istream* stream = nullptr; char* buf = nullptr; std::optional size; diff --git a/include/miniocpp/client.h b/include/miniocpp/client.h index 4f01a1d1..dd4e3e3a 100644 --- a/include/miniocpp/client.h +++ b/include/miniocpp/client.h @@ -141,6 +141,10 @@ class Client : public BaseClient { RemoveObjectsResult RemoveObjects(RemoveObjectsArgs args); // Async overloads — return std::future backed by std::async. + // + // Lifetime note for PutObjectAsync: the caller must ensure + // args.stream (if set) outlives the returned std::future, + // exactly as it must for the synchronous PutObject call. std::future ComposeObjectAsync(ComposeObjectArgs args); std::future CopyObjectAsync(CopyObjectArgs args); std::future DownloadObjectAsync( diff --git a/src/baseclient.cc b/src/baseclient.cc index 1458bbd2..1dc620ba 100644 --- a/src/baseclient.cc +++ b/src/baseclient.cc @@ -2368,40 +2368,45 @@ std::future BaseClient::SelectObjectContentAsync( auto owned_func = std::move(args.resultfunc); // Deep-copy serialization objects that SelectRequest points to. - auto owned_csv_input = owned_request->csv_input - ? std::make_shared(*owned_request->csv_input) - : nullptr; - auto owned_json_input = owned_request->json_input - ? std::make_shared(*owned_request->json_input) - : nullptr; + auto owned_csv_input = + owned_request->csv_input + ? std::make_shared(*owned_request->csv_input) + : nullptr; + auto owned_json_input = + owned_request->json_input + ? std::make_shared(*owned_request->json_input) + : nullptr; auto owned_parquet_input = owned_request->parquet_input - ? std::make_shared( - *owned_request->parquet_input) - : nullptr; - auto owned_csv_output = owned_request->csv_output - ? std::make_shared(*owned_request->csv_output) - : nullptr; + ? std::make_shared( + *owned_request->parquet_input) + : nullptr; + auto owned_csv_output = + owned_request->csv_output + ? std::make_shared(*owned_request->csv_output) + : nullptr; auto owned_json_output = owned_request->json_output - ? std::make_shared(*owned_request->json_output) - : nullptr; + ? std::make_shared( + *owned_request->json_output) + : nullptr; // Deep-copy inner pointees within serialization objects. auto owned_file_header_info = owned_csv_input && owned_csv_input->file_header_info - ? std::make_shared( - *owned_csv_input->file_header_info) + ? std::make_shared(*owned_csv_input->file_header_info) : nullptr; auto owned_csv_compression = owned_csv_input && owned_csv_input->compression_type ? std::make_shared( *owned_csv_input->compression_type) : nullptr; - auto owned_quote_fields = owned_csv_output && owned_csv_output->quote_fields - ? std::make_shared(*owned_csv_output->quote_fields) - : nullptr; - auto owned_json_type = owned_json_input && owned_json_input->json_type - ? std::make_shared(*owned_json_input->json_type) - : nullptr; + auto owned_quote_fields = + owned_csv_output && owned_csv_output->quote_fields + ? std::make_shared(*owned_csv_output->quote_fields) + : nullptr; + auto owned_json_type = + owned_json_input && owned_json_input->json_type + ? std::make_shared(*owned_json_input->json_type) + : nullptr; auto owned_json_compression = owned_json_input && owned_json_input->compression_type ? std::make_shared( @@ -2419,8 +2424,7 @@ std::future BaseClient::SelectObjectContentAsync( ssec = args.ssec, extra_headers = std::move(args.extra_headers), extra_query_params = std::move(args.extra_query_params)]() mutable { // Patch serialization pointers to owned copies. - if (owned_csv_input) - owned_request->csv_input = owned_csv_input.get(); + if (owned_csv_input) owned_request->csv_input = owned_csv_input.get(); if (owned_json_input) owned_request->json_input = owned_json_input.get(); if (owned_parquet_input) @@ -2442,8 +2446,7 @@ std::future BaseClient::SelectObjectContentAsync( if (owned_json_compression && owned_json_input) owned_json_input->compression_type = owned_json_compression.get(); - SelectObjectContentArgs new_args(*owned_request, - std::move(owned_func)); + SelectObjectContentArgs new_args(*owned_request, std::move(owned_func)); new_args.bucket = std::move(bucket); new_args.region = std::move(region); new_args.object = std::move(object); diff --git a/tests/tests.cc b/tests/tests.cc index aab337e3..fd0ccf3f 100644 --- a/tests/tests.cc +++ b/tests/tests.cc @@ -1290,8 +1290,7 @@ class Tests { if (!r1 || !r2 || !r3) { cleanup(b1, b2, b3); - throw std::runtime_error( - "concurrent MakeBucketAsync(): one failed"); + throw std::runtime_error("concurrent MakeBucketAsync(): one failed"); } minio::s3::BucketExistsArgs be1, be2, be3; @@ -1400,8 +1399,7 @@ class Tests { minio::s3::NotificationConfig notif_config; minio::s3::SetBucketNotificationArgs notif_args(notif_config); notif_args.bucket = bucket_name; - notif_fut = - client_.SetBucketNotificationAsync(std::move(notif_args)); + notif_fut = client_.SetBucketNotificationAsync(std::move(notif_args)); // notif_config and notif_args go out of scope here. } From dc809e6078e5dd964d45791afe02eef71e0ff6c0 Mon Sep 17 00:00:00 2001 From: jiuker Date: Fri, 26 Jun 2026 08:41:44 +0800 Subject: [PATCH 5/8] simply simply --- examples/SelectObjectContent.cc | 5 ++- include/miniocpp/types.h | 11 ++--- src/baseclient.cc | 78 ++------------------------------- tests/tests.cc | 12 +++-- 4 files changed, 20 insertions(+), 86 deletions(-) diff --git a/examples/SelectObjectContent.cc b/examples/SelectObjectContent.cc index 78731b20..5a17e680 100644 --- a/examples/SelectObjectContent.cc +++ b/examples/SelectObjectContent.cc @@ -32,10 +32,11 @@ int main() { std::string expression = "select * from S3Object"; minio::s3::CsvInputSerialization csv_input; minio::s3::FileHeaderInfo file_header_info = minio::s3::FileHeaderInfo::kUse; - csv_input.file_header_info = &file_header_info; + csv_input.file_header_info = + std::make_shared(file_header_info); minio::s3::CsvOutputSerialization csv_output; minio::s3::QuoteFields quote_fields = minio::s3::QuoteFields::kAsNeeded; - csv_output.quote_fields = "e_fields; + csv_output.quote_fields = std::make_shared(quote_fields); minio::s3::SelectRequest request(expression, &csv_input, &csv_output); std::string records; diff --git a/include/miniocpp/types.h b/include/miniocpp/types.h index 54234c06..262bca50 100644 --- a/include/miniocpp/types.h +++ b/include/miniocpp/types.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -191,11 +192,11 @@ constexpr const char* QuoteFieldsToString(QuoteFields qtype) noexcept { } struct CsvInputSerialization { - CompressionType* compression_type = nullptr; + std::shared_ptr compression_type; bool allow_quoted_record_delimiter = false; char comments = 0; char field_delimiter = 0; - FileHeaderInfo* file_header_info = nullptr; + std::shared_ptr file_header_info; char quote_character = 0; char quote_escape_character = 0; char record_delimiter = 0; @@ -205,8 +206,8 @@ struct CsvInputSerialization { }; // struct CsvInputSerialization struct JsonInputSerialization { - CompressionType* compression_type = nullptr; - JsonType* json_type = nullptr; + std::shared_ptr compression_type; + std::shared_ptr json_type; JsonInputSerialization() = default; ~JsonInputSerialization() = default; @@ -221,7 +222,7 @@ struct CsvOutputSerialization { char field_delimiter = 0; char quote_character = 0; char quote_escape_character = 0; - QuoteFields* quote_fields = nullptr; + std::shared_ptr quote_fields; char record_delimiter = 0; CsvOutputSerialization() = default; diff --git a/src/baseclient.cc b/src/baseclient.cc index 1dc620ba..52fd692f 100644 --- a/src/baseclient.cc +++ b/src/baseclient.cc @@ -2361,91 +2361,19 @@ std::future BaseClient::RemoveObjectsAsync( } // Reference-holding arg: SelectObjectContentArgs has SelectRequest& request. -// Capture owned copy of the request so the async lambda doesn't dangle. +// shared_ptr in SelectRequest now auto-deep-copies the pointer chain, +// so a single make_shared is sufficient. std::future BaseClient::SelectObjectContentAsync( SelectObjectContentArgs args) { auto owned_request = std::make_shared(args.request); auto owned_func = std::move(args.resultfunc); - - // Deep-copy serialization objects that SelectRequest points to. - auto owned_csv_input = - owned_request->csv_input - ? std::make_shared(*owned_request->csv_input) - : nullptr; - auto owned_json_input = - owned_request->json_input - ? std::make_shared(*owned_request->json_input) - : nullptr; - auto owned_parquet_input = owned_request->parquet_input - ? std::make_shared( - *owned_request->parquet_input) - : nullptr; - auto owned_csv_output = - owned_request->csv_output - ? std::make_shared(*owned_request->csv_output) - : nullptr; - auto owned_json_output = owned_request->json_output - ? std::make_shared( - *owned_request->json_output) - : nullptr; - - // Deep-copy inner pointees within serialization objects. - auto owned_file_header_info = - owned_csv_input && owned_csv_input->file_header_info - ? std::make_shared(*owned_csv_input->file_header_info) - : nullptr; - auto owned_csv_compression = - owned_csv_input && owned_csv_input->compression_type - ? std::make_shared( - *owned_csv_input->compression_type) - : nullptr; - auto owned_quote_fields = - owned_csv_output && owned_csv_output->quote_fields - ? std::make_shared(*owned_csv_output->quote_fields) - : nullptr; - auto owned_json_type = - owned_json_input && owned_json_input->json_type - ? std::make_shared(*owned_json_input->json_type) - : nullptr; - auto owned_json_compression = - owned_json_input && owned_json_input->compression_type - ? std::make_shared( - *owned_json_input->compression_type) - : nullptr; - return std::async( std::launch::async, - [this, owned_request, owned_func = std::move(owned_func), owned_csv_input, - owned_json_input, owned_parquet_input, owned_csv_output, - owned_json_output, owned_file_header_info, owned_csv_compression, - owned_quote_fields, owned_json_type, owned_json_compression, + [this, owned_request, owned_func = std::move(owned_func), bucket = std::move(args.bucket), region = std::move(args.region), object = std::move(args.object), version_id = std::move(args.version_id), ssec = args.ssec, extra_headers = std::move(args.extra_headers), extra_query_params = std::move(args.extra_query_params)]() mutable { - // Patch serialization pointers to owned copies. - if (owned_csv_input) owned_request->csv_input = owned_csv_input.get(); - if (owned_json_input) - owned_request->json_input = owned_json_input.get(); - if (owned_parquet_input) - owned_request->parquet_input = owned_parquet_input.get(); - if (owned_csv_output) - owned_request->csv_output = owned_csv_output.get(); - if (owned_json_output) - owned_request->json_output = owned_json_output.get(); - - // Patch inner pointers within serialization objects. - if (owned_file_header_info && owned_csv_input) - owned_csv_input->file_header_info = owned_file_header_info.get(); - if (owned_csv_compression && owned_csv_input) - owned_csv_input->compression_type = owned_csv_compression.get(); - if (owned_quote_fields && owned_csv_output) - owned_csv_output->quote_fields = owned_quote_fields.get(); - if (owned_json_type && owned_json_input) - owned_json_input->json_type = owned_json_type.get(); - if (owned_json_compression && owned_json_input) - owned_json_input->compression_type = owned_json_compression.get(); - SelectObjectContentArgs new_args(*owned_request, std::move(owned_func)); new_args.bucket = std::move(bucket); new_args.region = std::move(region); diff --git a/tests/tests.cc b/tests/tests.cc index fd0ccf3f..f12de124 100644 --- a/tests/tests.cc +++ b/tests/tests.cc @@ -599,10 +599,12 @@ class Tests { minio::s3::CsvInputSerialization csv_input; minio::s3::FileHeaderInfo file_header_info = minio::s3::FileHeaderInfo::kUse; - csv_input.file_header_info = &file_header_info; + csv_input.file_header_info = + std::make_shared(file_header_info); minio::s3::CsvOutputSerialization csv_output; minio::s3::QuoteFields quote_fields = minio::s3::QuoteFields::kAsNeeded; - csv_output.quote_fields = "e_fields; + csv_output.quote_fields = + std::make_shared(quote_fields); minio::s3::SelectRequest request(expression, &csv_input, &csv_output); try { @@ -1475,10 +1477,12 @@ class Tests { minio::s3::CsvInputSerialization csv_input; minio::s3::FileHeaderInfo file_header_info = minio::s3::FileHeaderInfo::kUse; - csv_input.file_header_info = &file_header_info; + csv_input.file_header_info = + std::make_shared(file_header_info); minio::s3::CsvOutputSerialization csv_output; minio::s3::QuoteFields quote_fields = minio::s3::QuoteFields::kAsNeeded; - csv_output.quote_fields = "e_fields; + csv_output.quote_fields = + std::make_shared(quote_fields); minio::s3::SelectRequest request(expression, &csv_input, &csv_output); auto func = [&records](minio::s3::SelectResult result) -> bool { From c8db1135b2780bce1976329ac609de955a2a1878 Mon Sep 17 00:00:00 2001 From: jiuker Date: Fri, 26 Jun 2026 08:57:08 +0800 Subject: [PATCH 6/8] simply simply --- include/miniocpp/args.h | 21 +++---- src/args.cc | 10 ++-- src/baseclient.cc | 124 +++++++++++++--------------------------- 3 files changed, 55 insertions(+), 100 deletions(-) diff --git a/include/miniocpp/args.h b/include/miniocpp/args.h index 96823988..ef4a5505 100644 --- a/include/miniocpp/args.h +++ b/include/miniocpp/args.h @@ -428,11 +428,11 @@ struct RemoveObjectsArgs : public BucketArgs { }; // struct RemoveObjectsArgs struct SelectObjectContentArgs : public ObjectReadArgs { - SelectRequest& request; + SelectRequest* request; SelectResultFunction resultfunc = nullptr; SelectObjectContentArgs(SelectRequest& req, SelectResultFunction func) - : request(req), resultfunc(func) {} + : request(&req), resultfunc(func) {} ~SelectObjectContentArgs() = default; @@ -469,10 +469,10 @@ using DeleteBucketNotificationArgs = BucketArgs; using GetBucketNotificationArgs = BucketArgs; struct SetBucketNotificationArgs : public BucketArgs { - NotificationConfig& config; + NotificationConfig* config; explicit SetBucketNotificationArgs(NotificationConfig& configvalue) - : config(configvalue) {} + : config(&configvalue) {} ~SetBucketNotificationArgs() = default; }; // struct SetBucketNotification @@ -482,9 +482,9 @@ using DeleteBucketEncryptionArgs = BucketArgs; using GetBucketEncryptionArgs = BucketArgs; struct SetBucketEncryptionArgs : public BucketArgs { - SseConfig& config; + SseConfig* config; - explicit SetBucketEncryptionArgs(SseConfig& sseconfig) : config(sseconfig) {} + explicit SetBucketEncryptionArgs(SseConfig& sseconfig) : config(&sseconfig) {} ~SetBucketEncryptionArgs() = default; @@ -508,9 +508,10 @@ using DeleteBucketReplicationArgs = BucketArgs; using GetBucketReplicationArgs = BucketArgs; struct SetBucketReplicationArgs : public BucketArgs { - ReplicationConfig& config; + ReplicationConfig* config; - explicit SetBucketReplicationArgs(ReplicationConfig& value) : config(value) {} + explicit SetBucketReplicationArgs(ReplicationConfig& value) + : config(&value) {} ~SetBucketReplicationArgs() = default; }; // struct SetBucketReplication @@ -520,9 +521,9 @@ using DeleteBucketLifecycleArgs = BucketArgs; using GetBucketLifecycleArgs = BucketArgs; struct SetBucketLifecycleArgs : public BucketArgs { - LifecycleConfig& config; + LifecycleConfig* config; - explicit SetBucketLifecycleArgs(LifecycleConfig& value) : config(value) {} + explicit SetBucketLifecycleArgs(LifecycleConfig& value) : config(&value) {} ~SetBucketLifecycleArgs() = default; }; // struct SetBucketLifecycle diff --git a/src/args.cc b/src/args.cc index 2a4bef4a..e0f00c80 100644 --- a/src/args.cc +++ b/src/args.cc @@ -462,17 +462,17 @@ error::Error SelectObjectContentArgs::Validate() const { if (error::Error err = ObjectReadArgs::Validate()) { return err; } - if (!utils::CheckNonEmptyString(request.expr)) { + if (!utils::CheckNonEmptyString(request->expr)) { return error::Error("SQL expression must not be empty"); } - if (!((request.csv_input != nullptr) ^ (request.json_input != nullptr) ^ - (request.parquet_input != nullptr))) { + if (!((request->csv_input != nullptr) ^ (request->json_input != nullptr) ^ + (request->parquet_input != nullptr))) { return error::Error( "One of CSV, JSON or Parquet input serialization must be set"); } - if (!((request.csv_output != nullptr) ^ (request.json_output != nullptr))) { + if (!((request->csv_output != nullptr) ^ (request->json_output != nullptr))) { return error::Error("One of CSV or JSON output serialization must be set"); } @@ -507,7 +507,7 @@ error::Error SetBucketEncryptionArgs::Validate() const { if (error::Error err = BucketArgs::Validate()) { return err; } - if (!config) { + if (!config || !*config) { return error::Error("bucket encryption configuration cannot be empty"); } diff --git a/src/baseclient.cc b/src/baseclient.cc index 52fd692f..8ef1d78f 100644 --- a/src/baseclient.cc +++ b/src/baseclient.cc @@ -1552,7 +1552,7 @@ SelectObjectContentResponse BaseClient::SelectObjectContent( req.object_name = args.object; req.query_params.Add("select", ""); req.query_params.Add("select-type", "2"); - std::string body = args.request.ToXML(); + std::string body = args.request->ToXML(); req.headers.Add("Content-MD5", utils::Md5sumHash(body)); req.body = body; @@ -1579,9 +1579,9 @@ SetBucketEncryptionResponse BaseClient::SetBucketEncryption( std::stringstream ss; ss << ""; ss << ""; - ss << "" << args.config.sse_algorithm << ""; - if (!args.config.kms_master_key_id.empty()) { - ss << "" << args.config.kms_master_key_id + ss << "" << args.config->sse_algorithm << ""; + if (!args.config->kms_master_key_id.empty()) { + ss << "" << args.config->kms_master_key_id << ""; } ss << ""; @@ -1611,7 +1611,7 @@ SetBucketLifecycleResponse BaseClient::SetBucketLifecycle( return SetBucketLifecycleResponse(resp); } - std::string body = args.config.ToXML(); + std::string body = args.config->ToXML(); Request req(http::Method::kPut, region, base_url_, args.extra_headers, args.extra_query_params); @@ -1636,7 +1636,7 @@ SetBucketNotificationResponse BaseClient::SetBucketNotification( return SetBucketNotificationResponse(resp); } - std::string body = args.config.ToXML(); + std::string body = args.config->ToXML(); Request req(http::Method::kPut, region, base_url_, args.extra_headers, args.extra_query_params); @@ -1683,7 +1683,7 @@ SetBucketReplicationResponse BaseClient::SetBucketReplication( return SetBucketReplicationResponse(resp); } - std::string body = args.config.ToXML(); + std::string body = args.config->ToXML(); Request req(http::Method::kPut, region, base_url_, args.extra_headers, args.extra_query_params); @@ -2360,108 +2360,62 @@ std::future BaseClient::RemoveObjectsAsync( }); } -// Reference-holding arg: SelectObjectContentArgs has SelectRequest& request. -// shared_ptr in SelectRequest now auto-deep-copies the pointer chain, -// so a single make_shared is sufficient. +// Reference-holding arg: SelectObjectContentArgs has SelectRequest* request. +// shared_ptr in SelectRequest now auto-deep-copies the pointer chain. std::future BaseClient::SelectObjectContentAsync( SelectObjectContentArgs args) { - auto owned_request = std::make_shared(args.request); - auto owned_func = std::move(args.resultfunc); - return std::async( - std::launch::async, - [this, owned_request, owned_func = std::move(owned_func), - bucket = std::move(args.bucket), region = std::move(args.region), - object = std::move(args.object), version_id = std::move(args.version_id), - ssec = args.ssec, extra_headers = std::move(args.extra_headers), - extra_query_params = std::move(args.extra_query_params)]() mutable { - SelectObjectContentArgs new_args(*owned_request, std::move(owned_func)); - new_args.bucket = std::move(bucket); - new_args.region = std::move(region); - new_args.object = std::move(object); - new_args.version_id = std::move(version_id); - new_args.ssec = ssec; - new_args.extra_headers = std::move(extra_headers); - new_args.extra_query_params = std::move(extra_query_params); - return SelectObjectContent(std::move(new_args)); - }); + auto owned_request = std::make_shared(*args.request); + return std::async(std::launch::async, + [this, owned_request, args = std::move(args)]() mutable { + args.request = owned_request.get(); + return SelectObjectContent(std::move(args)); + }); } // Reference-holding arg: SetBucketEncryptionArgs has SseConfig& config. std::future BaseClient::SetBucketEncryptionAsync( SetBucketEncryptionArgs args) { - auto owned_config = std::make_shared(args.config); - return std::async( - std::launch::async, - [this, owned_config, bucket = std::move(args.bucket), - region = std::move(args.region), - extra_headers = std::move(args.extra_headers), - extra_query_params = std::move(args.extra_query_params)]() mutable { - SetBucketEncryptionArgs new_args(*owned_config); - new_args.bucket = std::move(bucket); - new_args.region = std::move(region); - new_args.extra_headers = std::move(extra_headers); - new_args.extra_query_params = std::move(extra_query_params); - return SetBucketEncryption(std::move(new_args)); - }); + auto owned_config = std::make_shared(*args.config); + args.config = owned_config.get(); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return SetBucketEncryption(std::move(args)); + }); } // Reference-holding arg: SetBucketLifecycleArgs has LifecycleConfig& config. std::future BaseClient::SetBucketLifecycleAsync( SetBucketLifecycleArgs args) { - auto owned_config = std::make_shared(args.config); - return std::async( - std::launch::async, - [this, owned_config, bucket = std::move(args.bucket), - region = std::move(args.region), - extra_headers = std::move(args.extra_headers), - extra_query_params = std::move(args.extra_query_params)]() mutable { - SetBucketLifecycleArgs new_args(*owned_config); - new_args.bucket = std::move(bucket); - new_args.region = std::move(region); - new_args.extra_headers = std::move(extra_headers); - new_args.extra_query_params = std::move(extra_query_params); - return SetBucketLifecycle(std::move(new_args)); - }); + auto owned_config = std::make_shared(*args.config); + args.config = owned_config.get(); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return SetBucketLifecycle(std::move(args)); + }); } // Reference-holding arg: SetBucketNotificationArgs has NotificationConfig& // config. std::future BaseClient::SetBucketNotificationAsync(SetBucketNotificationArgs args) { - auto owned_config = std::make_shared(args.config); - return std::async( - std::launch::async, - [this, owned_config, bucket = std::move(args.bucket), - region = std::move(args.region), - extra_headers = std::move(args.extra_headers), - extra_query_params = std::move(args.extra_query_params)]() mutable { - SetBucketNotificationArgs new_args(*owned_config); - new_args.bucket = std::move(bucket); - new_args.region = std::move(region); - new_args.extra_headers = std::move(extra_headers); - new_args.extra_query_params = std::move(extra_query_params); - return SetBucketNotification(std::move(new_args)); - }); + auto owned_config = std::make_shared(*args.config); + args.config = owned_config.get(); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return SetBucketNotification(std::move(args)); + }); } // Reference-holding arg: SetBucketReplicationArgs has ReplicationConfig& // config. std::future BaseClient::SetBucketReplicationAsync( SetBucketReplicationArgs args) { - auto owned_config = std::make_shared(args.config); - return std::async( - std::launch::async, - [this, owned_config, bucket = std::move(args.bucket), - region = std::move(args.region), - extra_headers = std::move(args.extra_headers), - extra_query_params = std::move(args.extra_query_params)]() mutable { - SetBucketReplicationArgs new_args(*owned_config); - new_args.bucket = std::move(bucket); - new_args.region = std::move(region); - new_args.extra_headers = std::move(extra_headers); - new_args.extra_query_params = std::move(extra_query_params); - return SetBucketReplication(std::move(new_args)); - }); + auto owned_config = std::make_shared(*args.config); + args.config = owned_config.get(); + return std::async(std::launch::async, + [this, args = std::move(args)]() mutable { + return SetBucketReplication(std::move(args)); + }); } std::future BaseClient::SetBucketPolicyAsync( From e976f276b29b7611f52ced90d756d206f3371644 Mon Sep 17 00:00:00 2001 From: jiuker Date: Fri, 26 Jun 2026 09:09:46 +0800 Subject: [PATCH 7/8] simply --- include/miniocpp/args.h | 21 ++++++++++----------- src/args.cc | 10 +++++----- src/baseclient.cc | 39 ++++++++++++++------------------------- 3 files changed, 29 insertions(+), 41 deletions(-) diff --git a/include/miniocpp/args.h b/include/miniocpp/args.h index ef4a5505..19870928 100644 --- a/include/miniocpp/args.h +++ b/include/miniocpp/args.h @@ -428,11 +428,11 @@ struct RemoveObjectsArgs : public BucketArgs { }; // struct RemoveObjectsArgs struct SelectObjectContentArgs : public ObjectReadArgs { - SelectRequest* request; + SelectRequest request; SelectResultFunction resultfunc = nullptr; SelectObjectContentArgs(SelectRequest& req, SelectResultFunction func) - : request(&req), resultfunc(func) {} + : request(req), resultfunc(func) {} ~SelectObjectContentArgs() = default; @@ -469,10 +469,10 @@ using DeleteBucketNotificationArgs = BucketArgs; using GetBucketNotificationArgs = BucketArgs; struct SetBucketNotificationArgs : public BucketArgs { - NotificationConfig* config; + NotificationConfig config; explicit SetBucketNotificationArgs(NotificationConfig& configvalue) - : config(&configvalue) {} + : config(configvalue) {} ~SetBucketNotificationArgs() = default; }; // struct SetBucketNotification @@ -482,9 +482,9 @@ using DeleteBucketEncryptionArgs = BucketArgs; using GetBucketEncryptionArgs = BucketArgs; struct SetBucketEncryptionArgs : public BucketArgs { - SseConfig* config; + SseConfig config; - explicit SetBucketEncryptionArgs(SseConfig& sseconfig) : config(&sseconfig) {} + explicit SetBucketEncryptionArgs(SseConfig& sseconfig) : config(sseconfig) {} ~SetBucketEncryptionArgs() = default; @@ -508,10 +508,9 @@ using DeleteBucketReplicationArgs = BucketArgs; using GetBucketReplicationArgs = BucketArgs; struct SetBucketReplicationArgs : public BucketArgs { - ReplicationConfig* config; + ReplicationConfig config; - explicit SetBucketReplicationArgs(ReplicationConfig& value) - : config(&value) {} + explicit SetBucketReplicationArgs(ReplicationConfig& value) : config(value) {} ~SetBucketReplicationArgs() = default; }; // struct SetBucketReplication @@ -521,9 +520,9 @@ using DeleteBucketLifecycleArgs = BucketArgs; using GetBucketLifecycleArgs = BucketArgs; struct SetBucketLifecycleArgs : public BucketArgs { - LifecycleConfig* config; + LifecycleConfig config; - explicit SetBucketLifecycleArgs(LifecycleConfig& value) : config(&value) {} + explicit SetBucketLifecycleArgs(LifecycleConfig& value) : config(value) {} ~SetBucketLifecycleArgs() = default; }; // struct SetBucketLifecycle diff --git a/src/args.cc b/src/args.cc index e0f00c80..2a4bef4a 100644 --- a/src/args.cc +++ b/src/args.cc @@ -462,17 +462,17 @@ error::Error SelectObjectContentArgs::Validate() const { if (error::Error err = ObjectReadArgs::Validate()) { return err; } - if (!utils::CheckNonEmptyString(request->expr)) { + if (!utils::CheckNonEmptyString(request.expr)) { return error::Error("SQL expression must not be empty"); } - if (!((request->csv_input != nullptr) ^ (request->json_input != nullptr) ^ - (request->parquet_input != nullptr))) { + if (!((request.csv_input != nullptr) ^ (request.json_input != nullptr) ^ + (request.parquet_input != nullptr))) { return error::Error( "One of CSV, JSON or Parquet input serialization must be set"); } - if (!((request->csv_output != nullptr) ^ (request->json_output != nullptr))) { + if (!((request.csv_output != nullptr) ^ (request.json_output != nullptr))) { return error::Error("One of CSV or JSON output serialization must be set"); } @@ -507,7 +507,7 @@ error::Error SetBucketEncryptionArgs::Validate() const { if (error::Error err = BucketArgs::Validate()) { return err; } - if (!config || !*config) { + if (!config) { return error::Error("bucket encryption configuration cannot be empty"); } diff --git a/src/baseclient.cc b/src/baseclient.cc index 8ef1d78f..afac5dce 100644 --- a/src/baseclient.cc +++ b/src/baseclient.cc @@ -1552,7 +1552,7 @@ SelectObjectContentResponse BaseClient::SelectObjectContent( req.object_name = args.object; req.query_params.Add("select", ""); req.query_params.Add("select-type", "2"); - std::string body = args.request->ToXML(); + std::string body = args.request.ToXML(); req.headers.Add("Content-MD5", utils::Md5sumHash(body)); req.body = body; @@ -1579,9 +1579,9 @@ SetBucketEncryptionResponse BaseClient::SetBucketEncryption( std::stringstream ss; ss << ""; ss << ""; - ss << "" << args.config->sse_algorithm << ""; - if (!args.config->kms_master_key_id.empty()) { - ss << "" << args.config->kms_master_key_id + ss << "" << args.config.sse_algorithm << ""; + if (!args.config.kms_master_key_id.empty()) { + ss << "" << args.config.kms_master_key_id << ""; } ss << ""; @@ -1611,7 +1611,7 @@ SetBucketLifecycleResponse BaseClient::SetBucketLifecycle( return SetBucketLifecycleResponse(resp); } - std::string body = args.config->ToXML(); + std::string body = args.config.ToXML(); Request req(http::Method::kPut, region, base_url_, args.extra_headers, args.extra_query_params); @@ -1636,7 +1636,7 @@ SetBucketNotificationResponse BaseClient::SetBucketNotification( return SetBucketNotificationResponse(resp); } - std::string body = args.config->ToXML(); + std::string body = args.config.ToXML(); Request req(http::Method::kPut, region, base_url_, args.extra_headers, args.extra_query_params); @@ -1683,7 +1683,7 @@ SetBucketReplicationResponse BaseClient::SetBucketReplication( return SetBucketReplicationResponse(resp); } - std::string body = args.config->ToXML(); + std::string body = args.config.ToXML(); Request req(http::Method::kPut, region, base_url_, args.extra_headers, args.extra_query_params); @@ -2360,58 +2360,47 @@ std::future BaseClient::RemoveObjectsAsync( }); } -// Reference-holding arg: SelectObjectContentArgs has SelectRequest* request. -// shared_ptr in SelectRequest now auto-deep-copies the pointer chain. +// SelectObjectContentArgs owns request by value (deep-copied via +// SelectRequest's shared_ptr chain). Direct move is safe. std::future BaseClient::SelectObjectContentAsync( SelectObjectContentArgs args) { - auto owned_request = std::make_shared(*args.request); return std::async(std::launch::async, - [this, owned_request, args = std::move(args)]() mutable { - args.request = owned_request.get(); + [this, args = std::move(args)]() mutable { return SelectObjectContent(std::move(args)); }); } -// Reference-holding arg: SetBucketEncryptionArgs has SseConfig& config. +// SetBucketEncryptionArgs owns config by value. std::future BaseClient::SetBucketEncryptionAsync( SetBucketEncryptionArgs args) { - auto owned_config = std::make_shared(*args.config); - args.config = owned_config.get(); return std::async(std::launch::async, [this, args = std::move(args)]() mutable { return SetBucketEncryption(std::move(args)); }); } -// Reference-holding arg: SetBucketLifecycleArgs has LifecycleConfig& config. +// SetBucketLifecycleArgs owns config by value. std::future BaseClient::SetBucketLifecycleAsync( SetBucketLifecycleArgs args) { - auto owned_config = std::make_shared(*args.config); - args.config = owned_config.get(); return std::async(std::launch::async, [this, args = std::move(args)]() mutable { return SetBucketLifecycle(std::move(args)); }); } -// Reference-holding arg: SetBucketNotificationArgs has NotificationConfig& +// SetBucketNotificationArgs owns config by value. // config. std::future BaseClient::SetBucketNotificationAsync(SetBucketNotificationArgs args) { - auto owned_config = std::make_shared(*args.config); - args.config = owned_config.get(); return std::async(std::launch::async, [this, args = std::move(args)]() mutable { return SetBucketNotification(std::move(args)); }); } -// Reference-holding arg: SetBucketReplicationArgs has ReplicationConfig& -// config. +// SetBucketReplicationArgs owns config by value. std::future BaseClient::SetBucketReplicationAsync( SetBucketReplicationArgs args) { - auto owned_config = std::make_shared(*args.config); - args.config = owned_config.get(); return std::async(std::launch::async, [this, args = std::move(args)]() mutable { return SetBucketReplication(std::move(args)); From dbc2b51fddc50a5b71bb3d3aece6727c04d5e94e Mon Sep 17 00:00:00 2001 From: jiuker Date: Fri, 26 Jun 2026 09:21:53 +0800 Subject: [PATCH 8/8] apply suggestion --- examples/SelectObjectContent.cc | 5 ++- src/args.cc | 4 +- tests/tests.cc | 72 ++++++++++++++++++++------------- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/examples/SelectObjectContent.cc b/examples/SelectObjectContent.cc index 5a17e680..9c751098 100644 --- a/examples/SelectObjectContent.cc +++ b/examples/SelectObjectContent.cc @@ -33,10 +33,11 @@ int main() { minio::s3::CsvInputSerialization csv_input; minio::s3::FileHeaderInfo file_header_info = minio::s3::FileHeaderInfo::kUse; csv_input.file_header_info = - std::make_shared(file_header_info); + std::make_shared(file_header_info); minio::s3::CsvOutputSerialization csv_output; minio::s3::QuoteFields quote_fields = minio::s3::QuoteFields::kAsNeeded; - csv_output.quote_fields = std::make_shared(quote_fields); + csv_output.quote_fields = + std::make_shared(quote_fields); minio::s3::SelectRequest request(expression, &csv_input, &csv_output); std::string records; diff --git a/src/args.cc b/src/args.cc index 2a4bef4a..deb8e022 100644 --- a/src/args.cc +++ b/src/args.cc @@ -466,8 +466,8 @@ error::Error SelectObjectContentArgs::Validate() const { return error::Error("SQL expression must not be empty"); } - if (!((request.csv_input != nullptr) ^ (request.json_input != nullptr) ^ - (request.parquet_input != nullptr))) { + if (((request.csv_input != nullptr) + (request.json_input != nullptr) + + (request.parquet_input != nullptr)) != 1) { return error::Error( "One of CSV, JSON or Parquet input serialization must be set"); } diff --git a/tests/tests.cc b/tests/tests.cc index f12de124..6eb3c54c 100644 --- a/tests/tests.cc +++ b/tests/tests.cc @@ -791,41 +791,55 @@ class Tests { { std::string bucket_name = RandBucketName(); - { - minio::s3::MakeBucketArgs args; - args.bucket = bucket_name; - auto make_fut = client_.MakeBucketAsync(args); - minio::s3::MakeBucketResponse make_resp = make_fut.get(); - if (!make_resp) { - throw std::runtime_error("MakeBucketAsync(): " + - make_resp.Error().String()); + auto remove_bucket_best_effort = [this](const std::string& b) { + try { + minio::s3::RemoveBucketArgs args; + args.bucket = b; + client_.RemoveBucket(args); + } catch (...) { } - } + }; - { - minio::s3::BucketExistsArgs args; - args.bucket = bucket_name; - auto exists_fut = client_.BucketExistsAsync(args); - minio::s3::BucketExistsResponse exists_resp = exists_fut.get(); - if (!exists_resp) { - throw std::runtime_error("BucketExistsAsync(): " + - exists_resp.Error().String()); + try { + { + minio::s3::MakeBucketArgs args; + args.bucket = bucket_name; + auto make_fut = client_.MakeBucketAsync(args); + minio::s3::MakeBucketResponse make_resp = make_fut.get(); + if (!make_resp) { + throw std::runtime_error("MakeBucketAsync(): " + + make_resp.Error().String()); + } } - if (!exists_resp.exist) { - throw std::runtime_error( - "BucketExistsAsync(): expected bucket to exist"); + + { + minio::s3::BucketExistsArgs args; + args.bucket = bucket_name; + auto exists_fut = client_.BucketExistsAsync(args); + minio::s3::BucketExistsResponse exists_resp = exists_fut.get(); + if (!exists_resp) { + throw std::runtime_error("BucketExistsAsync(): " + + exists_resp.Error().String()); + } + if (!exists_resp.exist) { + throw std::runtime_error( + "BucketExistsAsync(): expected bucket to exist"); + } } - } - { - minio::s3::RemoveBucketArgs args; - args.bucket = bucket_name; - auto rm_fut = client_.RemoveBucketAsync(args); - minio::s3::RemoveBucketResponse rm_resp = rm_fut.get(); - if (!rm_resp) { - throw std::runtime_error("RemoveBucketAsync(): " + - rm_resp.Error().String()); + { + minio::s3::RemoveBucketArgs args; + args.bucket = bucket_name; + auto rm_fut = client_.RemoveBucketAsync(args); + minio::s3::RemoveBucketResponse rm_resp = rm_fut.get(); + if (!rm_resp) { + throw std::runtime_error("RemoveBucketAsync(): " + + rm_resp.Error().String()); + } } + } catch (...) { + remove_bucket_best_effort(bucket_name); + throw; } }