diff --git a/packages/expo-modules-core/CHANGELOG.md b/packages/expo-modules-core/CHANGELOG.md index f5ab4852201a7..423f2b493d76f 100644 --- a/packages/expo-modules-core/CHANGELOG.md +++ b/packages/expo-modules-core/CHANGELOG.md @@ -15,6 +15,7 @@ - [Android] Fix `getContext().getNativeModule(UIManagerModule.class)` in Bridgeless. ([#29203](https://github.com/expo/expo/pull/29203) by [@arushikesarwani94](https://github.com/arushikesarwani94)) - [Android] Fixed converting from `null` to `Record` sometimes didn't work as expected. ([#29508](https://github.com/expo/expo/pull/29508) by [@lukmccall](https://github.com/lukmccall)) - [Android] Fixed `No implementation found for com.facebook.jni.HybridData expo.modules.kotlin.jni.JavaScriptModuleObject.initHybrid`. ([#29513](https://github.com/expo/expo/pull/29513) by [@lukmccall](https://github.com/lukmccall)) +- [iOS] Fix data race in `PersistentFileLogSpec.swift`. ([#28924](https://github.com/expo/expo/pull/28924) by [@hakonk](https://github.com/hakonk)) ### 💡 Others diff --git a/packages/expo-modules-core/ios/Tests/PersistentFileLogSpec.swift b/packages/expo-modules-core/ios/Tests/PersistentFileLogSpec.swift index 29d14abb55f40..5e83e88c645c0 100644 --- a/packages/expo-modules-core/ios/Tests/PersistentFileLogSpec.swift +++ b/packages/expo-modules-core/ios/Tests/PersistentFileLogSpec.swift @@ -48,26 +48,57 @@ class PersistentFileLogSpec: ExpoSpec { } static func clearEntriesSync(log: PersistentFileLog) { - var didClear = false + let didClear = Synchronized(false) log.clearEntries { _ in - didClear = true + didClear.value = true } - expect(didClear).toEventually(beTrue(), timeout: .milliseconds(500)) + expect(didClear.value).toEventually(beTrue(), timeout: .milliseconds(500)) } static func filterEntriesSync(log: PersistentFileLog, filter: @escaping PersistentFileLogFilter) { - var didPurge = false + let didPurge = Synchronized(false) log.purgeEntriesNotMatchingFilter(filter: filter) { _ in - didPurge = true + didPurge.value = true } - expect(didPurge).toEventually(beTrue(), timeout: .milliseconds(500)) + expect(didPurge.value).toEventually(beTrue(), timeout: .milliseconds(500)) } static func appendEntrySync(log: PersistentFileLog, entry: String) { - var didAppend = false + let didAppend = Synchronized(false) log.appendEntry(entry: entry) { _ in - didAppend = true + didAppend.value = true } - expect(didAppend).toEventually(beTrue(), timeout: .milliseconds(500)) + expect(didAppend.value).toEventually(beTrue(), timeout: .milliseconds(500)) } } + +/// Allows for synchronization pertaining to the file scope. +private final class Synchronized { + private var _storage: T + private let lock = NSLock() + + /// Thread safe access here. + var value: T { + get { + return lockAround { + _storage + } + } + set { + lockAround { + _storage = newValue + } + } + } + + init(_ storage: T) { + self._storage = storage + } + + private func lockAround(_ closure: () -> U) -> U { + lock.lock() + defer { lock.unlock() } + return closure() + } + +} diff --git a/packages/expo-updates/CHANGELOG.md b/packages/expo-updates/CHANGELOG.md index df049a8cf336c..349577c7e40e2 100644 --- a/packages/expo-updates/CHANGELOG.md +++ b/packages/expo-updates/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- Fix data race in `AppLauncherWithDatabaseMock.swift`. ([#28924](https://github.com/expo/expo/pull/28924) by [@hakonk](https://github.com/hakonk)) + ### 💡 Others - Removed redundant usage of `EventEmitter` instance. ([#28946](https://github.com/expo/expo/pull/28946) by [@tsapeta](https://github.com/tsapeta)) diff --git a/packages/expo-updates/ios/Tests/AppLauncherWithDatabaseSpec.swift b/packages/expo-updates/ios/Tests/AppLauncherWithDatabaseSpec.swift index 9bab981451909..05421fc196482 100644 --- a/packages/expo-updates/ios/Tests/AppLauncherWithDatabaseSpec.swift +++ b/packages/expo-updates/ios/Tests/AppLauncherWithDatabaseSpec.swift @@ -94,16 +94,11 @@ class AppLauncherWithDatabaseSpec : ExpoSpec { directory: testDatabaseDir, completionQueue: DispatchQueue.global(qos: .default) ) - var successValue: Bool? = nil + let successValue = Synchronized(nil) launcher.launchUpdate(withSelectionPolicy: SelectionPolicyFactory.filterAwarePolicy(withRuntimeVersion: "1")) { error, success in - successValue = success + successValue.value = success } - - while successValue == nil { - Thread.sleep(forTimeInterval: 0.1) - } - - expect(successValue) == true + expect(successValue.value).toEventually(beTrue(), timeout: .milliseconds(10_000)) db.databaseQueue.sync { let sameUpdate = try! db.update(withId: testUpdate.updateId, config: config) @@ -114,3 +109,34 @@ class AppLauncherWithDatabaseSpec : ExpoSpec { } } } + +/// Allows for synchronization pertaining to the file scope. +private final class Synchronized { + private var _storage: T + private let lock = NSLock() + + /// Thread safe access here. + var value: T { + get { + return lockAround { + _storage + } + } + set { + lockAround { + _storage = newValue + } + } + } + + init(_ storage: T) { + self._storage = storage + } + + private func lockAround(_ closure: () -> U) -> U { + lock.lock() + defer { lock.unlock() } + return closure() + } + +}