Skip to content

Commit

Permalink
[Fastlane.Swift] Swift fastlane does not run on Apple Silicon fastlan…
Browse files Browse the repository at this point in the history
…e#18502

* [Fastlane.Swift] Fix thread data race using a thread safe storage
  • Loading branch information
kikeenrique committed Oct 23, 2021
1 parent 026d9c4 commit f64aea8
Showing 1 changed file with 39 additions and 6 deletions.
45 changes: 39 additions & 6 deletions fastlane/swift/Runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,47 @@ func desc(_: String) {
// no-op, this is handled in fastlane/lane_list.rb
}

class AtomicDictionary<Key: Hashable, Value> {
private let lockQueue = DispatchQueue(label: "tools.fastlane.serial.queue")
private var storage: [Key: Value]

init() {
self.storage = [:]
}

func get(_ key: Key) -> Value? {
lockQueue.sync {
storage[key]
}
}

func set(_ key: Key, value: Value) {
lockQueue.sync {
storage[key] = value
}
}

func removeValue(forKey key: Key) {
lockQueue.sync {
_ = storage.removeValue(forKey: key)
}
}

var allValues: [Key: Value] {
lockQueue.sync {
storage
}
}
}

class Runner {
private var thread: Thread!
private var socketClient: SocketClient!
private let dispatchGroup = DispatchGroup()
private var returnValue: String? // lol, so safe
private var currentlyExecutingCommand: RubyCommandable?
private var shouldLeaveDispatchGroupDuringDisconnect = false
private var executeNext: [String: Bool] = [:]
private var executeNext: AtomicDictionary<String, Bool> = AtomicDictionary()

func executeCommand(_ command: RubyCommandable) -> String {
dispatchGroup.enter()
Expand All @@ -38,7 +71,7 @@ class Runner {

let secondsToWait = DispatchTimeInterval.seconds(SocketClient.defaultCommandTimeoutSeconds)
// swiftformat:disable:next redundantSelf
let timeoutResult = Self.waitWithPolling(self.executeNext[command.id], toEventually: { $0 == true }, timeout: SocketClient.defaultCommandTimeoutSeconds)
let timeoutResult = Self.waitWithPolling(self.executeNext.get(command.id), toEventually: { $0 == true }, timeout: SocketClient.defaultCommandTimeoutSeconds)
executeNext.removeValue(forKey: command.id)
let failureMessage = "command didn't execute in: \(SocketClient.defaultCommandTimeoutSeconds) seconds"
let success = testDispatchTimeoutResult(timeoutResult, failureMessage: failureMessage, timeToWait: secondsToWait)
Expand Down Expand Up @@ -160,10 +193,10 @@ extension Runner: SocketClientDelegateProtocol {
if let command = currentlyExecutingCommand as? RubyCommand {
if let closureArgumentValue = closureArgumentValue, !closureArgumentValue.isEmpty {
command.performCallback(callbackArg: closureArgumentValue, socket: socketClient) {
self.executeNext[command.id] = true
self.executeNext.set(command.id, value: true)
}
} else {
executeNext[command.id] = true
executeNext.set(command.id, value: true)
}
}
dispatchGroup.leave()
Expand All @@ -172,14 +205,14 @@ extension Runner: SocketClientDelegateProtocol {
verbose(message: "server acknowledged a cancel request")
dispatchGroup.leave()
if let command = currentlyExecutingCommand as? RubyCommand {
executeNext[command.id] = true
executeNext.set(command.id, value: true)
}
completion(socketClient)
case .alreadyClosedSockets, .connectionFailure, .malformedRequest, .malformedResponse, .serverError:
log(message: "error encountered while executing command:\n\(serverResponse)")
dispatchGroup.leave()
if let command = currentlyExecutingCommand as? RubyCommand {
executeNext[command.id] = true
executeNext.set(command.id, value: true)
}
completion(socketClient)
case let .commandTimeout(timeout):
Expand Down

0 comments on commit f64aea8

Please sign in to comment.