Skip to content

Commit

Permalink
Fix race
Browse files Browse the repository at this point in the history
  • Loading branch information
paulb777 committed Jul 30, 2024
1 parent 8c5e777 commit 6b05933
Show file tree
Hide file tree
Showing 18 changed files with 357 additions and 506 deletions.
3 changes: 3 additions & 0 deletions FirebaseStorage/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unreleased
- [fixed] Fix a potential data race in Storage initialization. (#13369)

# 11.0.0
- [fixed] Updated error handling to support both Swift error enum handling and NSError error
handling. Some of the Swift enums have additional parameters which may be a **breaking** change.
Expand Down
2 changes: 0 additions & 2 deletions FirebaseStorage/Sources/Internal/StorageDeleteTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,9 @@ import Foundation
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
enum StorageDeleteTask {
static func deleteTask(reference: StorageReference,
fetcherService: GTMSessionFetcherService,
queue: DispatchQueue,
completion: ((_: Data?, _: Error?) -> Void)?) {
StorageInternalTask(reference: reference,
fetcherService: fetcherService,
queue: queue,
httpMethod: "DELETE",
fetcherComment: "DeleteTask",
Expand Down
86 changes: 86 additions & 0 deletions FirebaseStorage/Sources/Internal/StorageFetcherService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

#if COCOAPODS
import GTMSessionFetcher
#else
import GTMSessionFetcherCore
#endif

/// Manage Storage's fetcherService
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
actor StorageFetcherService {
private var _fetcherService: GTMSessionFetcherService?

func fetcherService(_ storage: Storage) -> GTMSessionFetcherService {
if let _fetcherService {
return _fetcherService
}
let app = storage.app
var bucketMap = StorageFetcherService.fetcherServiceMap[app.name]
if bucketMap == nil {
bucketMap = [:]
StorageFetcherService.fetcherServiceMap[app.name] = bucketMap
}
var fetcherService = bucketMap?[storage.storageBucket]
if fetcherService == nil {
fetcherService = GTMSessionFetcherService()
fetcherService?.isRetryEnabled = true
fetcherService?.retryBlock = retryWhenOffline
fetcherService?.allowLocalhostRequest = true
fetcherService?.maxRetryInterval = storage.maxOperationRetryInterval
fetcherService?.testBlock = testBlock
let authorizer = StorageTokenAuthorizer(
googleAppID: app.options.googleAppID,
callbackQueue: storage.callbackQueue,
authProvider: storage.auth,
appCheck: storage.appCheck
)
fetcherService?.authorizer = authorizer
bucketMap?[storage.storageBucket] = fetcherService
}
if storage.usesEmulator {
fetcherService?.allowLocalhostRequest = true
fetcherService?.allowedInsecureSchemes = ["http"]
}
_fetcherService = fetcherService
return fetcherService!
}

/// Update the testBlock for unit testing. Save it as a property since this may be called before
/// fetcherService is initialized.
func updateTestBlock(_ block: @escaping GTMSessionFetcherTestBlock) {
testBlock = block
}

private var testBlock: GTMSessionFetcherTestBlock? = nil

/// Map of apps to a dictionary of buckets to GTMSessionFetcherService.
private static var fetcherServiceMap: [String: [String: GTMSessionFetcherService]] = [:]

private var retryWhenOffline: GTMSessionFetcherRetryBlock = {
(suggestedWillRetry: Bool,
error: Error?,
response: @escaping GTMSessionFetcherRetryResponse) in
var shouldRetry = suggestedWillRetry
// GTMSessionFetcher does not consider being offline a retryable error, but we do, so we
// special-case it here.
if !shouldRetry, error != nil {
shouldRetry = (error as? NSError)?.code == URLError.notConnectedToInternet.rawValue
}
response(shouldRetry)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,13 @@

import Foundation

#if COCOAPODS
import GTMSessionFetcher
#else
import GTMSessionFetcherCore
#endif

/// Task which provides the ability to get a download URL for an object in Firebase Storage.
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
enum StorageGetDownloadURLTask {
static func getDownloadURLTask(reference: StorageReference,
fetcherService: GTMSessionFetcherService,
queue: DispatchQueue,
completion: ((_: URL?, _: Error?) -> Void)?) {
StorageInternalTask(reference: reference,
fetcherService: fetcherService,
queue: queue,
httpMethod: "GET",
fetcherComment: "GetDownloadURLTask") { (data: Data?, error: Error?) in
Expand Down
8 changes: 0 additions & 8 deletions FirebaseStorage/Sources/Internal/StorageGetMetadataTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,13 @@

import Foundation

#if COCOAPODS
import GTMSessionFetcher
#else
import GTMSessionFetcherCore
#endif

/// Task which provides the ability to delete an object in Firebase Storage.
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
enum StorageGetMetadataTask {
static func getMetadataTask(reference: StorageReference,
fetcherService: GTMSessionFetcherService,
queue: DispatchQueue,
completion: ((_: StorageMetadata?, _: Error?) -> Void)?) {
StorageInternalTask(reference: reference,
fetcherService: fetcherService,
queue: queue,
httpMethod: "GET",
fetcherComment: "GetMetadataTask") { (data: Data?, error: Error?) in
Expand Down
26 changes: 13 additions & 13 deletions FirebaseStorage/Sources/Internal/StorageInternalTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,35 @@ class StorageInternalTask: StorageTask {

@discardableResult
init(reference: StorageReference,
fetcherService: GTMSessionFetcherService,
queue: DispatchQueue,
request: URLRequest? = nil,
httpMethod: String,
fetcherComment: String,
completion: ((_: Data?, _: Error?) -> Void)?) {
super.init(reference: reference, service: fetcherService, queue: queue)
super.init(reference: reference, queue: queue)

// Prepare a task and begins execution.
dispatchQueue.async { [self] in
self.state = .queueing
var request = request ?? self.baseRequest
request.httpMethod = httpMethod
request.timeoutInterval = self.reference.storage.maxOperationRetryTime
Task {
let fetcherService = await reference.storage.fetcherService
.fetcherService(reference.storage)

let fetcher = self.fetcherService.fetcher(with: request)
fetcher.comment = fetcherComment
self.fetcher = fetcher
var request = request ?? self.baseRequest
request.httpMethod = httpMethod
request.timeoutInterval = self.reference.storage.maxOperationRetryTime

Task {
let callbackQueue = reference.storage.fetcherService != nil ?
reference.storage.fetcherServiceForApp.callbackQueue : DispatchQueue.main
let fetcher = fetcherService.fetcher(with: request)
fetcher.comment = fetcherComment
self.fetcher = fetcher
let callbackQueue = reference.storage.callbackQueue
do {
let data = try await self.fetcher?.beginFetch()
callbackQueue?.async {
callbackQueue.async {
completion?(data, nil)
}
} catch {
callbackQueue?.async {
callbackQueue.async {
completion?(nil, StorageErrorCode.error(withServerError: error as NSError,
ref: self.reference))
}
Expand Down
8 changes: 0 additions & 8 deletions FirebaseStorage/Sources/Internal/StorageListTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,10 @@

import Foundation

#if COCOAPODS
import GTMSessionFetcher
#else
import GTMSessionFetcherCore
#endif

/// A Task that lists the entries under a StorageReference
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
enum StorageListTask {
static func listTask(reference: StorageReference,
fetcherService: GTMSessionFetcherService,
queue: DispatchQueue,
pageSize: Int64?,
previousPageToken: String?,
Expand Down Expand Up @@ -60,7 +53,6 @@ enum StorageListTask {
)

StorageInternalTask(reference: reference,
fetcherService: fetcherService,
queue: queue,
request: request,
httpMethod: "GET",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,10 @@

import Foundation

#if COCOAPODS
import GTMSessionFetcher
#else
import GTMSessionFetcherCore
#endif

/// A Task that lists the entries under a StorageReference
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
enum StorageUpdateMetadataTask {
static func updateMetadataTask(reference: StorageReference,
fetcherService: GTMSessionFetcherService,
queue: DispatchQueue,
metadata: StorageMetadata,
completion: ((_: StorageMetadata?, _: Error?) -> Void)?) {
Expand All @@ -37,7 +30,6 @@ enum StorageUpdateMetadataTask {
}

StorageInternalTask(reference: reference,
fetcherService: fetcherService,
queue: queue,
request: request,
httpMethod: "PATCH",
Expand Down
Loading

0 comments on commit 6b05933

Please sign in to comment.