@@ -110,8 +110,38 @@ public struct S3Manager: S3ManagerProtocol {
110110 private func withS3Client< T> ( _ block: ( SotoS3 . S3 ) async throws -> T ) async throws -> T {
111111 let awsClient = AWSClient ( credentialProvider: credentialProvider, httpClientProvider: . createNew)
112112 let s3Client = SotoS3 . S3 ( client: awsClient, region: Region ( rawValue: self . region) )
113- let result = try await block ( s3Client)
113+
114+ // The following code `try await` the given operation to execute with a
115+ // `SotoS3.S3` client created ad hoc and, once done, shuts down that
116+ // client.
117+ //
118+ // If something goes wrong while running the block, we still need to
119+ // shutdown the client, else the error from the block will be lost and
120+ // the program will fail with something like:
121+ //
122+ // SotoCore/AWSClient.swift:110: Assertion failed:
123+ // AWSClient not shut down before the deinit.
124+ // Please call client.syncShutdown() when no longer needed.
125+ //
126+ // It would be good to use `defer`, but it doesn't yet support asycn.
127+ // See https://forums.swift.org/t/async-rethrows-and-defer/58356.
128+ //
129+ // So, we use a `do catch` where in the catch we first shutdown the
130+ // client and then bubble up the error.
131+ let result : T
132+ do {
133+ result = try await block ( s3Client)
134+ } catch {
135+ try await awsClient. shutdown ( )
136+ throw error
137+ }
138+
139+ // We need to shutdown the client also when `block` run successfully.
140+ //
141+ // This duplication with the call above will disappear once Swift will
142+ // give us a way to call `defer`.
114143 try await awsClient. shutdown ( )
144+
115145 return result
116146 }
117147
0 commit comments