@@ -2571,100 +2571,137 @@ Result<std::shared_ptr<io::OutputStream>> S3FileSystem::OpenAppendStream(
25712571
25722572namespace {
25732573
2574- std::mutex aws_init_lock;
2575- Aws::SDKOptions aws_options;
2576- std::atomic<bool > aws_initialized (false );
2574+ struct AwsInstance : public ::arrow::internal::Executor::Resource {
2575+ AwsInstance () : is_initialized_(false ), is_finalized_(false ) {}
2576+ ~AwsInstance () { Finalize (/* from_destructor=*/ true ); }
2577+
2578+ // Returns true iff the instance was newly initialized with `options`
2579+ Result<bool > EnsureInitialized (const S3GlobalOptions& options) {
2580+ bool expected = false ;
2581+ if (is_finalized_.load ()) {
2582+ return Status::Invalid (" Attempt to initialize S3 after it has been finalized" );
2583+ }
2584+ if (is_initialized_.compare_exchange_strong (expected, true )) {
2585+ DoInitialize (options);
2586+ return true ;
2587+ }
2588+ return false ;
2589+ }
25772590
2578- Status DoInitializeS3 (const S3GlobalOptions& options) {
2579- Aws::Utils::Logging::LogLevel aws_log_level;
2591+ bool IsInitialized () { return !is_finalized_ && is_initialized_; }
2592+
2593+ void Finalize (bool from_destructor = false ) {
2594+ bool expected = true ;
2595+ is_finalized_.store (true );
2596+ if (is_initialized_.compare_exchange_strong (expected, false )) {
2597+ if (from_destructor) {
2598+ ARROW_LOG (WARNING )
2599+ << " arrow::fs::FinalizeS3 was not called even though S3 was initialized. "
2600+ " This could lead to a segmentation fault at exit" ;
2601+ RegionResolver::ResetDefaultInstance ();
2602+ Aws::ShutdownAPI (aws_options_);
2603+ }
2604+ }
2605+ }
2606+
2607+ private:
2608+ void DoInitialize (const S3GlobalOptions& options) {
2609+ Aws::Utils::Logging::LogLevel aws_log_level;
25802610
25812611#define LOG_LEVEL_CASE (level_name ) \
25822612 case S3LogLevel::level_name: \
25832613 aws_log_level = Aws::Utils::Logging::LogLevel::level_name; \
25842614 break ;
25852615
2586- switch (options.log_level ) {
2587- LOG_LEVEL_CASE (Fatal)
2588- LOG_LEVEL_CASE (Error)
2589- LOG_LEVEL_CASE (Warn)
2590- LOG_LEVEL_CASE (Info)
2591- LOG_LEVEL_CASE (Debug)
2592- LOG_LEVEL_CASE (Trace)
2593- default :
2594- aws_log_level = Aws::Utils::Logging::LogLevel::Off;
2595- }
2616+ switch (options.log_level ) {
2617+ LOG_LEVEL_CASE (Fatal)
2618+ LOG_LEVEL_CASE (Error)
2619+ LOG_LEVEL_CASE (Warn)
2620+ LOG_LEVEL_CASE (Info)
2621+ LOG_LEVEL_CASE (Debug)
2622+ LOG_LEVEL_CASE (Trace)
2623+ default :
2624+ aws_log_level = Aws::Utils::Logging::LogLevel::Off;
2625+ }
25962626
25972627#undef LOG_LEVEL_CASE
25982628
25992629#ifdef ARROW_S3_HAS_CRT
2600- aws_options .ioOptions .clientBootstrap_create_fn =
2601- [ev_threads = options.num_event_loop_threads ]() {
2602- // https://github.com/aws/aws-sdk-cpp/blob/1.11.15/src/aws-cpp-sdk-core/source/Aws.cpp#L65
2603- Aws::Crt::Io::EventLoopGroup event_loop_group (ev_threads);
2604- Aws::Crt::Io::DefaultHostResolver default_host_resolver (
2605- event_loop_group, /* maxHosts=*/ 8 , /* maxTTL=*/ 30 );
2606- auto client_bootstrap = Aws::MakeShared<Aws::Crt::Io::ClientBootstrap>(
2607- " Aws_Init_Cleanup" , event_loop_group, default_host_resolver);
2608- client_bootstrap->EnableBlockingShutdown ();
2609- return client_bootstrap;
2610- };
2630+ aws_options_ .ioOptions .clientBootstrap_create_fn =
2631+ [ev_threads = options.num_event_loop_threads ]() {
2632+ // https://github.com/aws/aws-sdk-cpp/blob/1.11.15/src/aws-cpp-sdk-core/source/Aws.cpp#L65
2633+ Aws::Crt::Io::EventLoopGroup event_loop_group (ev_threads);
2634+ Aws::Crt::Io::DefaultHostResolver default_host_resolver (
2635+ event_loop_group, /* maxHosts=*/ 8 , /* maxTTL=*/ 30 );
2636+ auto client_bootstrap = Aws::MakeShared<Aws::Crt::Io::ClientBootstrap>(
2637+ " Aws_Init_Cleanup" , event_loop_group, default_host_resolver);
2638+ client_bootstrap->EnableBlockingShutdown ();
2639+ return client_bootstrap;
2640+ };
26112641#endif
2612-
2613- aws_options.loggingOptions .logLevel = aws_log_level;
2614- // By default the AWS SDK logs to files, log to console instead
2615- aws_options.loggingOptions .logger_create_fn = [] {
2616- return std::make_shared<Aws::Utils::Logging::ConsoleLogSystem>(
2617- aws_options.loggingOptions .logLevel );
2618- };
2642+ aws_options_.loggingOptions .logLevel = aws_log_level;
2643+ // By default the AWS SDK logs to files, log to console instead
2644+ aws_options_.loggingOptions .logger_create_fn = [this ] {
2645+ return std::make_shared<Aws::Utils::Logging::ConsoleLogSystem>(
2646+ aws_options_.loggingOptions .logLevel );
2647+ };
26192648#if (defined(AWS_SDK_VERSION_MAJOR) && \
26202649 (AWS_SDK_VERSION_MAJOR > 1 || AWS_SDK_VERSION_MINOR > 9 || \
26212650 (AWS_SDK_VERSION_MINOR == 9 && AWS_SDK_VERSION_PATCH >= 272 )))
2622- // ARROW-18290: escape all special chars for compatibility with non-AWS S3 backends.
2623- // This configuration options is only available with AWS SDK 1.9.272 and later.
2624- aws_options .httpOptions .compliantRfc3986Encoding = true ;
2651+ // ARROW-18290: escape all special chars for compatibility with non-AWS S3 backends.
2652+ // This configuration options is only available with AWS SDK 1.9.272 and later.
2653+ aws_options_ .httpOptions .compliantRfc3986Encoding = true ;
26252654#endif
2626- Aws::InitAPI (aws_options);
2627- aws_initialized.store (true );
2628- return Status::OK ();
2655+ Aws::InitAPI (aws_options_);
2656+ }
2657+
2658+ Aws::SDKOptions aws_options_;
2659+ std::atomic<bool > is_initialized_;
2660+ std::atomic<bool > is_finalized_;
2661+ };
2662+
2663+ std::shared_ptr<AwsInstance> CreateAwsInstance () {
2664+ auto instance = std::make_shared<AwsInstance>();
2665+ // Don't let S3 be shutdown until all Arrow threads are done using it
2666+ arrow::internal::GetCpuThreadPool ()->KeepAlive (instance);
2667+ io::internal::GetIOThreadPool ()->KeepAlive (instance);
2668+ return instance;
26292669}
26302670
2631- Status DoFinalizeS3 () {
2632- RegionResolver::ResetDefaultInstance ();
2633- Aws::ShutdownAPI (aws_options);
2634- aws_initialized.store (false );
2635- return Status::OK ();
2671+ AwsInstance& GetAwsInstance () {
2672+ static auto instance = CreateAwsInstance ();
2673+ return *instance;
2674+ }
2675+
2676+ Result<bool > EnsureAwsInstanceInitialized (const S3GlobalOptions& options) {
2677+ return GetAwsInstance ().EnsureInitialized (options);
26362678}
26372679
26382680} // namespace
26392681
26402682Status InitializeS3 (const S3GlobalOptions& options) {
2641- std::lock_guard<std::mutex> lock (aws_init_lock);
2642- return DoInitializeS3 (options);
2643- }
2644-
2645- Status EnsureS3Initialized () {
2646- std::lock_guard<std::mutex> lock (aws_init_lock);
2647- if (!aws_initialized.load ()) {
2648- S3GlobalOptions options{S3LogLevel::Fatal};
2649- return DoInitializeS3 (options);
2683+ ARROW_ASSIGN_OR_RAISE (bool successfully_initialized,
2684+ EnsureAwsInstanceInitialized (options));
2685+ if (!successfully_initialized) {
2686+ return Status::Invalid (
2687+ " S3 was already initialized. It is safe to use but the options passed in this "
2688+ " call have been ignored." );
26502689 }
26512690 return Status::OK ();
26522691}
26532692
2654- Status FinalizeS3 () {
2655- std::lock_guard<std::mutex> lock (aws_init_lock);
2656- return DoFinalizeS3 ();
2693+ Status EnsureS3Initialized () {
2694+ return EnsureAwsInstanceInitialized ({S3LogLevel::Fatal}).status ();
26572695}
26582696
2659- Status EnsureS3Finalized () {
2660- std::lock_guard<std::mutex> lock (aws_init_lock);
2661- if (aws_initialized.load ()) {
2662- return DoFinalizeS3 ();
2663- }
2697+ Status FinalizeS3 () {
2698+ GetAwsInstance ().Finalize ();
26642699 return Status::OK ();
26652700}
26662701
2667- bool IsS3Initialized () { return aws_initialized.load (); }
2702+ Status EnsureS3Finalized () { return FinalizeS3 (); }
2703+
2704+ bool IsS3Initialized () { return GetAwsInstance ().IsInitialized (); }
26682705
26692706// -----------------------------------------------------------------------
26702707// Top-level utility functions
0 commit comments