Skip to content

Commit

Permalink
Merge pull request #11 from oa-s/fix_bad_access_crash
Browse files Browse the repository at this point in the history
synchronize access to batch to avoid crash (second in list: AlphaWallet/alpha-wallet-ios#4182)
  • Loading branch information
hboon authored Apr 5, 2022
2 parents 0b4cca2 + 27c0855 commit 45ae1f3
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 37 deletions.
4 changes: 2 additions & 2 deletions web3swift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1422,7 +1422,7 @@
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = web3swift/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "";
MODULEMAP_PRIVATE_FILE = "";
Expand Down Expand Up @@ -1461,7 +1461,7 @@
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = web3swift/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "";
MODULEMAP_PRIVATE_FILE = "";
Expand Down
153 changes: 118 additions & 35 deletions web3swift/Promises/Classes/Promise+Batching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class JSONRPCrequestDispatcher {

private var provider: Web3Provider
private var lockQueue: DispatchQueue
private lazy var batches: [Batch] = []
private lazy var batches: Store<Batch> = .init(queue: lockQueue)

public init(provider: Web3Provider, queue: DispatchQueue, policy: DispatchPolicy) {
self.provider = provider
Expand All @@ -32,10 +32,10 @@ public class JSONRPCrequestDispatcher {
throw Web3Error.inputError("Trying to batch a request when policy is not to batch")
}

let currentBatch = batches.last ?? createBatch()
let currentBatch = batches.last() ?? createBatch()

if currentBatch.requests.count % batchLength == 0 || currentBatch.triggered {
let newBatch = Batch(capacity: Int(batchLength), queue: queue)
if currentBatch.requests.count() % batchLength == 0 || currentBatch.triggered {
let newBatch = Batch(capacity: Int(batchLength), queue: queue, lockQueue: lockQueue)
newBatch.delegate = self

batches.append(newBatch)
Expand All @@ -57,20 +57,17 @@ public class JSONRPCrequestDispatcher {
return provider.sendAsync(request, queue: queue)
case .Batch:
let (promise, seal) = Promise<JSONRPCresponse>.pending()
lockQueue.async { [weak self] in
guard let strongSelf = self else { return }

do {
let batch = try strongSelf.getBatch()
let internalPromise = try batch.add(request, maxWaitTime: strongSelf.MAX_WAIT_TIME)
internalPromise.done(on: strongSelf.queue) { resp in
seal.fulfill(resp)
}.catch(on: strongSelf.queue) { err in
seal.reject(err)
}
} catch {
seal.reject(error)

do {
let batch = try getBatch()
let internalPromise = try batch.add(request, maxWaitTime: MAX_WAIT_TIME)
internalPromise.done(on: queue) { resp in
seal.fulfill(resp)
}.catch(on: queue) { err in
seal.reject(err)
}
} catch {
seal.reject(error)
}

return promise
Expand All @@ -79,11 +76,12 @@ public class JSONRPCrequestDispatcher {

internal final class Batch: NSObject {
var capacity: Int
var promises: [UInt64: (promise: Promise<JSONRPCresponse>, resolver: Resolver<JSONRPCresponse>)] = [:]
var requests: [JSONRPCrequest] = []
lazy var promises: DictionaryStore<UInt64, (promise: Promise<JSONRPCresponse>, resolver: Resolver<JSONRPCresponse>)> = .init(queue: lockQueue)
lazy var requests: Store<JSONRPCrequest> = .init(queue: lockQueue)

private var pendingTrigger: Guarantee<Void>?
private let queue: DispatchQueue
private let lockQueue: DispatchQueue
private (set) var triggered: Bool = false
weak var delegate: BatchDelegate?

Expand Down Expand Up @@ -116,25 +114,24 @@ public class JSONRPCrequestDispatcher {
}
}

if requests.count == capacity {
if requests.count() == capacity {
trigger()
}

return promiseToReturn.promise
}

func trigger() {
guard !triggered else {
return
}
guard !triggered else { return }
triggered = true

delegate?.didTrigger(id: self)
}

init(capacity: Int, queue: DispatchQueue) {
init(capacity: Int, queue: DispatchQueue, lockQueue: DispatchQueue) {
self.capacity = capacity
self.queue = queue
self.lockQueue = lockQueue
}
}

Expand All @@ -143,42 +140,41 @@ public class JSONRPCrequestDispatcher {
extension JSONRPCrequestDispatcher: BatchDelegate {

func didTrigger(id batch: Batch) {
let requestsBatch = JSONRPCrequestBatch(requests: batch.requests)
let requestsBatch = JSONRPCrequestBatch(requests: batch.requests.allValues())

provider.sendAsync(requestsBatch, queue: queue).done(on: queue, { [weak self] batchResponse in
provider.sendAsync(requestsBatch, queue: queue).done(on: queue, { batchResponse in
for response in batchResponse.responses {
if batch.promises[UInt64(response.id)] == nil {
for k in batch.promises.keys {
for k in batch.promises.keys() {
batch.promises[k]?.resolver.reject(Web3Error.nodeError("Unknown request id"))
}
return
}
}

for response in batchResponse.responses {
let promise = batch.promises[UInt64(response.id)]!
promise.resolver.fulfill(response)
batch.promises[UInt64(response.id)]?.resolver.fulfill(response)
}
self?.batches.removeAll(where: { $0 == batch })
}).catch(on: queue, { [weak self] err in
for k in batch.promises.keys {
}).catch(on: queue, { err in
for k in batch.promises.keys() {
batch.promises[k]?.resolver.reject(err)
}
}).finally { [weak self] in
self?.batches.removeAll(where: { $0 == batch })
})
}
}

@discardableResult func createBatch() -> Batch {
switch policy {
case .NoBatching:
let batch = Batch(capacity: 32, queue: queue)
let batch = Batch(capacity: 32, queue: queue, lockQueue: lockQueue)
batch.delegate = self

batches.append(batch)

return batch
case .Batch(let count):
let batch = Batch(capacity: count, queue: queue)
let batch = Batch(capacity: count, queue: queue, lockQueue: lockQueue)
batch.delegate = self

batches.append(batch)
Expand All @@ -191,3 +187,90 @@ extension JSONRPCrequestDispatcher: BatchDelegate {
protocol BatchDelegate: class {
func didTrigger(id batch: JSONRPCrequestDispatcher.Batch)
}

class DictionaryStore<K: Hashable, V> {
private var values: [K: V] = [:]
private let queue: DispatchQueue

public init(queue: DispatchQueue = DispatchQueue(label: "RealmStore.syncQueue", qos: .background)) {
self.queue = queue
}

public subscript(key: K) -> V? {
get {
var element: V?
dispatchPrecondition(condition: .notOnQueue(queue))
queue.sync { [unowned self] in
element = self.values[key]
}
return element
}
set {
dispatchPrecondition(condition: .notOnQueue(queue))
queue.sync { [unowned self] in
self.values[key] = newValue
}
}
}

func keys() -> Dictionary<K, V>.Keys {
var keys: Dictionary<K, V>.Keys?
dispatchPrecondition(condition: .notOnQueue(queue))
queue.sync { [unowned self] in
keys = self.values.keys
}
return keys!
}

}

class Store<T> {
private var values: [T] = []
private let queue: DispatchQueue

public init(queue: DispatchQueue = DispatchQueue(label: "RealmStore.syncQueue", qos: .background)) {
self.queue = queue
}

func append(_ element: T) {
dispatchPrecondition(condition: .notOnQueue(queue))
queue.sync { [unowned self] in
self.values.append(element)
}
}

func last() -> T? {
var element: T?
dispatchPrecondition(condition: .notOnQueue(queue))
queue.sync { [unowned self] in
element = self.values.last
}

return element
}

func removeAll(where closure: (T) -> Bool) {
dispatchPrecondition(condition: .notOnQueue(queue))
queue.sync { [unowned self] in
self.values.removeAll(where: closure)
}
}

func count() -> Int {
var count: Int = 0
dispatchPrecondition(condition: .notOnQueue(queue))
queue.sync { [unowned self] in
count = self.values.count
}
return count
}

func allValues() -> [T] {
var values: [T] = []
dispatchPrecondition(condition: .notOnQueue(queue))
queue.sync { [unowned self] in
values = self.values
}
return values
}
}

0 comments on commit 45ae1f3

Please sign in to comment.