diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 3ac625fabd..c9a93c1e39 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -84,10 +84,8 @@ extension ChatAPIResult { // Spec: spec/api.md#decodeAPIResult public func decodeAPIResult(_ d: Data) -> APIResult { - print("decodeAPIResult \(String(describing: R.self)): \(String(data: d, encoding: .utf8) ?? "")") do { -// return try withStackSizeLimit { try jsonDecoder.decode(APIResult.self, from: d) } - return try jsonDecoder.decode(APIResult.self, from: d) + return try withLargeStack { try jsonDecoder.decode(APIResult.self, from: d) } } catch {} if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary { if let (_, jErr) = getOWSF(j, "error") { @@ -106,31 +104,50 @@ public func decodeAPIResult(_ d: Data) -> APIResult { // Default stack size for the main thread is 1mb, for secondary threads - 512 kb. // This function can be used to test what size is used (or to increase available stack size). // Stack size must be a multiple of system page size (16kb). -//private let stackSizeLimit: Int = 256 * 1024 -// -//private func withStackSizeLimit(_ f: @escaping () throws -> T) throws -> T { -// let semaphore = DispatchSemaphore(value: 0) -// var result: Result? -// let thread = Thread { -// do { -// result = .success(try f()) -// } catch { -// result = .failure(error) -// } -// semaphore.signal() -// } -// -// thread.stackSize = stackSizeLimit -// thread.qualityOfService = Thread.current.qualityOfService -// thread.start() -// -// semaphore.wait() -// -// switch result! { -// case let .success(r): return r -// case let .failure(e): throw e -// } -//} +private let stackSizeLimit: Int = 16 * 1024 * 1024 + +private final class LargeStackRunner: NSObject { + static let shared = LargeStackRunner() + + private let serialize = NSLock() // serialize submitters + private let jobReady = DispatchSemaphore(value: 0) + private let jobDone = DispatchSemaphore(value: 0) + private var job: (() -> Void)? + private var thread: Thread? = nil + + private override init() { + super.init() + let t = Thread(target: self, selector: #selector(loop), object: nil) + t.stackSize = stackSizeLimit + t.name = "chat.simplex.decoding" + t.qualityOfService = .default + t.start() + thread = t + } + + @objc private func loop() { + while true { + jobReady.wait() + job?() + jobDone.signal() + } + } + + func run(_ f: @escaping () throws -> T) throws -> T { + serialize.lock() + defer { serialize.unlock() } + var result: Result! + job = { result = Result(catching: f) } + jobReady.signal() + jobDone.wait() + job = nil + return try result.get() + } +} + +func withLargeStack(_ work: @escaping () throws -> T) throws -> T { + try LargeStackRunner.shared.run(work) +} public func parseApiChats(_ jResp: NSDictionary) -> (user: UserRef, chats: [ChatData])? { if let jApiChats = jResp["apiChats"] as? NSDictionary,