From 9b835664828a6c688f01441bef0be78925c917b1 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Fri, 7 Oct 2022 21:12:20 -0400 Subject: [PATCH 1/2] Fix TuskerTests not compiling --- Tusker.xcodeproj/project.pbxproj | 29 ++-- Tusker/MultiThreadDictionary.swift | 1 + TuskerUITests/API Mocks/APIMocks.swift | 134 +++++++++--------- .../API Mocks/DelegatingResponse.swift | 40 +++--- TuskerUITests/API Mocks/JSONResponse.swift | 18 +-- TuskerUITests/ComposeTests.swift | 2 +- TuskerUITests/MyProfileTests.swift | 2 +- TuskerUITests/OnboardingTests.swift | 4 +- TuskerUITests/TuskerUITests.swift | 68 ++++----- 9 files changed, 152 insertions(+), 146 deletions(-) diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 0b863d60..28e9df9d 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -33,6 +33,7 @@ D6114E1727F8BB210080E273 /* VersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1627F8BB210080E273 /* VersionTests.swift */; }; D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */; }; D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */; }; + D61ABEFC28F105DE00B29151 /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D61ABEFB28F105DE00B29151 /* Pachyderm */; }; D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; }; D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; }; D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; }; @@ -208,8 +209,6 @@ D6A6C11525B62E9700298D0F /* CacheExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C11425B62E9700298D0F /* CacheExpiry.swift */; }; D6A6C11B25B63CEE00298D0F /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C11A25B63CEE00298D0F /* MemoryCache.swift */; }; D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */; }; - D6ACE1AC240C3BAD004EA8E2 /* Ambassador.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65F613023AE99E000F3CFD3 /* Ambassador.framework */; }; - D6ACE1AD240C3BAD004EA8E2 /* Embassy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65F612D23AE990C00F3CFD3 /* Embassy.framework */; }; D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; }; D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */; }; D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */; }; @@ -673,8 +672,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D6ACE1AC240C3BAD004EA8E2 /* Ambassador.framework in Frameworks */, - D6ACE1AD240C3BAD004EA8E2 /* Embassy.framework in Frameworks */, + D61ABEFC28F105DE00B29151 /* Pachyderm in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1513,6 +1511,7 @@ ); name = TuskerUITests; packageProductDependencies = ( + D61ABEFB28F105DE00B29151 /* Pachyderm */, ); productName = TuskerUITests; productReference = D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */; @@ -2218,14 +2217,15 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = HGYVAQA9FW; + DEVELOPMENT_TEAM = V4WK9KR9U2; INFOPLIST_FILE = TuskerTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.TuskerTests; + PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.TuskerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2239,14 +2239,15 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = HGYVAQA9FW; + DEVELOPMENT_TEAM = V4WK9KR9U2; INFOPLIST_FILE = TuskerTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.TuskerTests; + PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.TuskerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2259,14 +2260,14 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = HGYVAQA9FW; + DEVELOPMENT_TEAM = V4WK9KR9U2; INFOPLIST_FILE = TuskerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.TuskerUITests; + PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.TuskerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2279,14 +2280,14 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = HGYVAQA9FW; + DEVELOPMENT_TEAM = V4WK9KR9U2; INFOPLIST_FILE = TuskerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.TuskerUITests; + PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.TuskerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2439,6 +2440,10 @@ package = D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */; productName = SwiftSoup; }; + D61ABEFB28F105DE00B29151 /* Pachyderm */ = { + isa = XCSwiftPackageProductDependency; + productName = Pachyderm; + }; D6552366289870790048A653 /* ScreenCorners */ = { isa = XCSwiftPackageProductDependency; package = D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */; diff --git a/Tusker/MultiThreadDictionary.swift b/Tusker/MultiThreadDictionary.swift index d808bd75..1a0d4661 100644 --- a/Tusker/MultiThreadDictionary.swift +++ b/Tusker/MultiThreadDictionary.swift @@ -75,6 +75,7 @@ fileprivate class UnfairLock: Lock { self.lock.initialize(to: os_unfair_lock()) } deinit { + self.lock.deinitialize(count: 1) self.lock.deallocate() } func withLock(_ body: (inout State) throws -> R) rethrows -> R where R: Sendable { diff --git a/TuskerUITests/API Mocks/APIMocks.swift b/TuskerUITests/API Mocks/APIMocks.swift index 7a577385..72f21098 100644 --- a/TuskerUITests/API Mocks/APIMocks.swift +++ b/TuskerUITests/API Mocks/APIMocks.swift @@ -7,70 +7,70 @@ // import Foundation -import Ambassador - -fileprivate let notFound = ["error": "Record not found"] - -extension Router { - func allRoutes() { - instanceRoutes() - accountRoutes() - timelineRoutes() - notificationRoutes() - } - - func instanceRoutes() { - self["/api/v1/instance"] = JSONResponse(handler: { (_) in - return [ - "description": "An instance description", - "max_toot_chars": 500, - "thumbnail": "http://localhost:8080/thumbnail.png", - "title": "Localhost", - "uri": "http://localhost:8080", - "version": "2.7.2", - "urls": [:] - ] - }) - } - - func accountRoutes() { - let selfAccount: [String: Any] = [ - "id": "1", - "username": "admin", - "acct": "admin", - "display_name": "Admin Account", - "locked": false, - "created_at": "2019-12-31T11:13:42.0Z", - "followers_count": 0, - "following_count": 0, - "statuses_count": 0, - "note": "My profile description.", - "url": "http://localhost:8080/users/admin", - "avatar": "http://localhost:8080/avatar/admin.jpg", - "avatar_static": "http://localhost:8080/avatar/admin.jpg", - "header": "http://localhost:8080/header/admin.jpg", - "header_static": "http://localhost:8080/header/admin.jpg", - "emojis": [] - ] - self["/api/v1/accounts/verify_credentials"] = JSONResponse(result: selfAccount) - self["/api/v1/accounts/\\d+/statuses"] = JSONResponse(result: []) - self["/api/v1/accounts/(\\d+)"] = DelegatingResponse { (ctx) in - if ctx.captures[0] == "1" { - return JSONResponse(result: selfAccount) - } else { - return JSONResponse(statusCode: 404, statusMessage: "Not Found", result: notFound) - } - } - } - - func timelineRoutes() { - let emptyTimeline: [Any] = [] - self["/api/v1/timelines/home"] = JSONResponse(result: emptyTimeline) - self["/api/v1/timelines/public"] = JSONResponse(result: emptyTimeline) - } - - func notificationRoutes() { - let emptyTimeline: [Any] = [] - self["/api/v1/notifications"] = JSONResponse(result: emptyTimeline) - } -} +//import Ambassador +// +//fileprivate let notFound = ["error": "Record not found"] +// +//extension Router { +// func allRoutes() { +// instanceRoutes() +// accountRoutes() +// timelineRoutes() +// notificationRoutes() +// } +// +// func instanceRoutes() { +// self["/api/v1/instance"] = JSONResponse(handler: { (_) in +// return [ +// "description": "An instance description", +// "max_toot_chars": 500, +// "thumbnail": "http://localhost:8080/thumbnail.png", +// "title": "Localhost", +// "uri": "http://localhost:8080", +// "version": "2.7.2", +// "urls": [:] +// ] +// }) +// } +// +// func accountRoutes() { +// let selfAccount: [String: Any] = [ +// "id": "1", +// "username": "admin", +// "acct": "admin", +// "display_name": "Admin Account", +// "locked": false, +// "created_at": "2019-12-31T11:13:42.0Z", +// "followers_count": 0, +// "following_count": 0, +// "statuses_count": 0, +// "note": "My profile description.", +// "url": "http://localhost:8080/users/admin", +// "avatar": "http://localhost:8080/avatar/admin.jpg", +// "avatar_static": "http://localhost:8080/avatar/admin.jpg", +// "header": "http://localhost:8080/header/admin.jpg", +// "header_static": "http://localhost:8080/header/admin.jpg", +// "emojis": [] +// ] +// self["/api/v1/accounts/verify_credentials"] = JSONResponse(result: selfAccount) +// self["/api/v1/accounts/\\d+/statuses"] = JSONResponse(result: []) +// self["/api/v1/accounts/(\\d+)"] = DelegatingResponse { (ctx) in +// if ctx.captures[0] == "1" { +// return JSONResponse(result: selfAccount) +// } else { +// return JSONResponse(statusCode: 404, statusMessage: "Not Found", result: notFound) +// } +// } +// } +// +// func timelineRoutes() { +// let emptyTimeline: [Any] = [] +// self["/api/v1/timelines/home"] = JSONResponse(result: emptyTimeline) +// self["/api/v1/timelines/public"] = JSONResponse(result: emptyTimeline) +// } +// +// func notificationRoutes() { +// let emptyTimeline: [Any] = [] +// self["/api/v1/notifications"] = JSONResponse(result: emptyTimeline) +// } +//} diff --git a/TuskerUITests/API Mocks/DelegatingResponse.swift b/TuskerUITests/API Mocks/DelegatingResponse.swift index 4664ce8a..3badecf3 100644 --- a/TuskerUITests/API Mocks/DelegatingResponse.swift +++ b/TuskerUITests/API Mocks/DelegatingResponse.swift @@ -7,23 +7,23 @@ // import Foundation -import Ambassador - -struct DelegatingResponse: WebApp { - let handler: (_ ctx: Context) -> WebApp - - func app(_ environ: [String : Any], startResponse: @escaping ((String, [(String, String)]) -> Void), sendBody: @escaping ((Data) -> Void)) { - let ctx = Context(environ: environ) - handler(ctx).app(environ, startResponse: startResponse, sendBody: sendBody) - } -} - -extension DelegatingResponse { - struct Context { - let environ: [String: Any] - - var captures: [String] { - environ["ambassador.router_captures"] as? [String] ?? [] - } - } -} +//import Ambassador +// +//struct DelegatingResponse: WebApp { +// let handler: (_ ctx: Context) -> WebApp +// +// func app(_ environ: [String : Any], startResponse: @escaping ((String, [(String, String)]) -> Void), sendBody: @escaping ((Data) -> Void)) { +// let ctx = Context(environ: environ) +// handler(ctx).app(environ, startResponse: startResponse, sendBody: sendBody) +// } +//} +// +//extension DelegatingResponse { +// struct Context { +// let environ: [String: Any] +// +// var captures: [String] { +// environ["ambassador.router_captures"] as? [String] ?? [] +// } +// } +//} diff --git a/TuskerUITests/API Mocks/JSONResponse.swift b/TuskerUITests/API Mocks/JSONResponse.swift index d7dc2d00..fbf1e4e6 100644 --- a/TuskerUITests/API Mocks/JSONResponse.swift +++ b/TuskerUITests/API Mocks/JSONResponse.swift @@ -7,12 +7,12 @@ // import Foundation -import Ambassador - -extension JSONResponse { - init(statusCode: Int = 200, statusMessage: String = "OK", result: Any) { - self.init(statusCode: statusCode, statusMessage: statusMessage, handler: { (_) in - return result - }) - } -} +//import Ambassador +// +//extension JSONResponse { +// init(statusCode: Int = 200, statusMessage: String = "OK", result: Any) { +// self.init(statusCode: statusCode, statusMessage: statusMessage, handler: { (_) in +// return result +// }) +// } +//} diff --git a/TuskerUITests/ComposeTests.swift b/TuskerUITests/ComposeTests.swift index 704d08e8..ef96222f 100644 --- a/TuskerUITests/ComposeTests.swift +++ b/TuskerUITests/ComposeTests.swift @@ -14,7 +14,7 @@ class ComposeTests: TuskerUITests { override func setUp() { super.setUp() - router.allRoutes() +// router.allRoutes() app.launchEnvironment["UI_TESTING_LOGIN"] = "true" app.launch() diff --git a/TuskerUITests/MyProfileTests.swift b/TuskerUITests/MyProfileTests.swift index 2412177c..ceb91a25 100644 --- a/TuskerUITests/MyProfileTests.swift +++ b/TuskerUITests/MyProfileTests.swift @@ -13,7 +13,7 @@ class MyProfileTests: TuskerUITests { override func setUp() { super.setUp() - router.allRoutes() +// router.allRoutes() app.launchEnvironment["UI_TESTING_LOGIN"] = "true" app.launch() diff --git a/TuskerUITests/OnboardingTests.swift b/TuskerUITests/OnboardingTests.swift index d3f14104..98d4200d 100644 --- a/TuskerUITests/OnboardingTests.swift +++ b/TuskerUITests/OnboardingTests.swift @@ -7,14 +7,14 @@ // import XCTest -import Ambassador +//import Ambassador class OnboardingTests: TuskerUITests { override func setUp() { super.setUp() - router.instanceRoutes() +// router.instanceRoutes() app.launch() } diff --git a/TuskerUITests/TuskerUITests.swift b/TuskerUITests/TuskerUITests.swift index b353f68e..2a2f7486 100644 --- a/TuskerUITests/TuskerUITests.swift +++ b/TuskerUITests/TuskerUITests.swift @@ -7,40 +7,40 @@ // import XCTest -import Embassy -import Ambassador +//import Embassy +//import Ambassador class TuskerUITests: XCTestCase { - var eventLoop: EventLoop! - var router: Router! - var server: HTTPServer! - var eventLoopThreadCondition: NSCondition! - var eventLoopThread: Thread! +// var eventLoop: EventLoop! +// var router: Router! +// var server: HTTPServer! +// var eventLoopThreadCondition: NSCondition! +// var eventLoopThread: Thread! var app: XCUIApplication! - private func setupWebServer() { - eventLoop = try! SelectorEventLoop(selector: try! KqueueSelector()) - router = Router() - server = DefaultHTTPServer(eventLoop: eventLoop, port: 8080, app: router.app) - router["/hello"] = JSONResponse(handler: { (_) in - return ["Hello", "World"] - }) - try! server.start() - - eventLoopThreadCondition = NSCondition() - eventLoopThread = Thread(block: { - self.eventLoop.runForever() - self.eventLoopThreadCondition.lock() - self.eventLoopThreadCondition.signal() - self.eventLoopThreadCondition.unlock() - }) - eventLoopThread.start() - } +// private func setupWebServer() { +// eventLoop = try! SelectorEventLoop(selector: try! KqueueSelector()) +// router = Router() +// server = DefaultHTTPServer(eventLoop: eventLoop, port: 8080, app: router.app) +// router["/hello"] = JSONResponse(handler: { (_) in +// return ["Hello", "World"] +// }) +// try! server.start() +// +// eventLoopThreadCondition = NSCondition() +// eventLoopThread = Thread(block: { +// self.eventLoop.runForever() +// self.eventLoopThreadCondition.lock() +// self.eventLoopThreadCondition.signal() +// self.eventLoopThreadCondition.unlock() +// }) +// eventLoopThread.start() +// } override func setUp() { - setupWebServer() +// setupWebServer() continueAfterFailure = false app = XCUIApplication() @@ -48,14 +48,14 @@ class TuskerUITests: XCTestCase { } override func tearDown() { - server.stopAndWait() - eventLoopThreadCondition.lock() - eventLoop.stop() - while eventLoop.running { - if !eventLoopThreadCondition.wait(until: Date(timeIntervalSinceNow: 10)) { - fatalError("Join eventLoopThread timeout") - } - } +// server.stopAndWait() +// eventLoopThreadCondition.lock() +// eventLoop.stop() +// while eventLoop.running { +// if !eventLoopThreadCondition.wait(until: Date(timeIntervalSinceNow: 10)) { +// fatalError("Join eventLoopThread timeout") +// } +// } } } From b30f149dc93195a5d5744d031c30180ba768f00f Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 8 Oct 2022 10:57:59 -0400 Subject: [PATCH 2/2] Use mutex on iOS 15 instead of os_unfair_lock See #178 --- Tusker/MultiThreadDictionary.swift | 45 +++++++++++++++++++++--------- TuskerTests/TuskerTests.swift | 40 ++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 13 deletions(-) diff --git a/Tusker/MultiThreadDictionary.swift b/Tusker/MultiThreadDictionary.swift index 1a0d4661..8b5c316b 100644 --- a/Tusker/MultiThreadDictionary.swift +++ b/Tusker/MultiThreadDictionary.swift @@ -19,7 +19,7 @@ class MultiThreadDictionary { if #available(iOS 16.0, *) { self.lock = OSAllocatedUnfairLock(initialState: [:]) } else { - self.lock = UnfairLock(initialState: [:]) + self.lock = MutexLock(initialState: [:]) } } @@ -65,22 +65,41 @@ fileprivate protocol Lock { extension OSAllocatedUnfairLock: Lock { } -// from http://www.russbishop.net/the-law -fileprivate class UnfairLock: Lock { - private var lock: UnsafeMutablePointer +// something is wrong with the UnfairLock impl and it results in segv_accerrs +fileprivate class MutexLock: Lock { private var state: State + private var lock = NSLock() + init(initialState: State) { self.state = initialState - self.lock = .allocate(capacity: 1) - self.lock.initialize(to: os_unfair_lock()) } - deinit { - self.lock.deinitialize(count: 1) - self.lock.deallocate() - } - func withLock(_ body: (inout State) throws -> R) rethrows -> R where R: Sendable { - os_unfair_lock_lock(lock) - defer { os_unfair_lock_unlock(lock) } + + func withLock(_ body: @Sendable (inout State) throws -> R) rethrows -> R where R : Sendable { + if !lock.lock(before: Date(timeIntervalSinceNow: 1)) { + // if we can't acquire the lock after 1 second, something has gone catastrophically wrong + fatalError() + } + defer { lock.unlock() } return try body(&state) } } + +//// from http://www.russbishop.net/the-law +//fileprivate class UnfairLock: Lock { +// private var lock: UnsafeMutablePointer +// private var state: State +// init(initialState: State) { +// self.state = initialState +// self.lock = .allocate(capacity: 1) +// self.lock.initialize(to: os_unfair_lock()) +// } +// deinit { +// self.lock.deinitialize(count: 1) +// self.lock.deallocate() +// } +// func withLock(_ body: (inout State) throws -> R) rethrows -> R where R: Sendable { +// os_unfair_lock_lock(lock) +// defer { os_unfair_lock_unlock(lock) } +// return try body(&state) +// } +//} diff --git a/TuskerTests/TuskerTests.swift b/TuskerTests/TuskerTests.swift index 1d8f9a78..2ba572fa 100644 --- a/TuskerTests/TuskerTests.swift +++ b/TuskerTests/TuskerTests.swift @@ -30,5 +30,45 @@ class TuskerTests: XCTestCase { // Put the code you want to measure the time of here. } } + + + func testFuckingLock() { + let lock = MutexLock<[Int: Bool]>(initialState: [:]) + for i in 0..<100 { + Thread.detachNewThread { + for j in 0..<50_000 { + lock.withLock { + $0[i * 50_000 + j] = true + } + } + } + } + while true { + if lock.withLock({ $0.count }) == 5_000_000 { + break + } + } + lock.withLock({ _ in + print("WHAT THE FUUUUUUUUUUUUCK") + }) + } } + +fileprivate class MutexLock { + private var state: State + private var lock = NSLock() + + init(initialState: State) { + self.state = initialState + } + + func withLock(_ body: @Sendable (inout State) throws -> R) rethrows -> R where R : Sendable { + if !lock.lock(before: Date(timeIntervalSinceNow: 1)) { + // if we can't acquire the lock after 1 second, something has gone catastrophically wrong + fatalError() + } + defer { lock.unlock() } + return try body(&state) + } +}