Skip to content

Commit aa8ae54

Browse files
authored
Merge pull request #13 from petersktang/main
Fix Concurrency Thread-safe exception
2 parents 170fc5b + 9fd7875 commit aa8ae54

File tree

5 files changed

+89
-9
lines changed

5 files changed

+89
-9
lines changed

Sources/SwiftMath/MathBundle/MathFont.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ private class BundleManager {
7676
private var ctFonts = [CTFontSizePair: CTFont]()
7777
private var rawMathTables = [MathFont: NSDictionary]()
7878

79-
private let threadSafeQueue = DispatchQueue(label: "com.smartmath.mathfont.threadsafequeue", attributes: .concurrent)
79+
private let threadSafeQueue = DispatchQueue(label: "com.smartmath.mathfont.threadsafequeue",
80+
qos: .userInitiated,
81+
attributes: .concurrent)
8082

8183
private func registerCGFont(mathFont: MathFont) throws {
8284
guard let frameworkBundleURL = Bundle.module.url(forResource: "mathFonts", withExtension: "bundle"),

Sources/SwiftMath/MathBundle/MathImage.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ extension MathImage {
7575
intrinsicContentSize = intrinsicContentSize(displayList)
7676
displayList.textColor = textColor
7777

78-
let size = intrinsicContentSize
78+
let size = intrinsicContentSize.regularized
7979
layoutImage(size: size, displayList: displayList)
8080

8181
#if os(iOS)
@@ -107,3 +107,8 @@ private extension CGAffineTransform {
107107
return transform
108108
}
109109
}
110+
extension CGSize {
111+
fileprivate var regularized: CGSize {
112+
CGSize(width: ceil(width), height: ceil(height))
113+
}
114+
}

Sources/SwiftMath/MathRender/MTMathAtomFactory.swift

+27-7
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public class MTMathAtomFactory {
6262
"rfloor" : "\u{230B}"
6363
]
6464

65+
private static let delimValueLock = NSLock()
6566
static var _delimValueToName = [String: String]()
6667
public static var delimValueToName: [String: String] {
6768
if _delimValueToName.isEmpty {
@@ -78,7 +79,11 @@ public class MTMathAtomFactory {
7879
}
7980
output[value] = key
8081
}
81-
_delimValueToName = output
82+
delimValueLock.lock()
83+
defer { delimValueLock.unlock() }
84+
if _delimValueToName.isEmpty {
85+
_delimValueToName = output
86+
}
8287
}
8388
return _delimValueToName
8489
}
@@ -98,6 +103,7 @@ public class MTMathAtomFactory {
98103
"widetilde" : "\u{0303}"
99104
]
100105

106+
private static let accentValueLock = NSLock()
101107
static var _accentValueToName: [String: String]? = nil
102108
public static var accentValueToName: [String: String] {
103109
if _accentValueToName == nil {
@@ -115,7 +121,11 @@ public class MTMathAtomFactory {
115121
}
116122
output[value] = key
117123
}
118-
_accentValueToName = output
124+
accentValueLock.lock()
125+
defer { accentValueLock.unlock() }
126+
if _accentValueToName == nil {
127+
_accentValueToName = output
128+
}
119129
}
120130
return _accentValueToName!
121131
}
@@ -390,6 +400,7 @@ public class MTMathAtomFactory {
390400
"scriptscriptstyle" : MTMathStyle(style: .scriptOfScript),
391401
]
392402

403+
private static let textToLatexLock = NSLock()
393404
static var _textToLatexSymbolName: [String: String]? = nil
394405
public static var textToLatexSymbolName: [String: String] {
395406
get {
@@ -413,13 +424,17 @@ public class MTMathAtomFactory {
413424
}
414425
output[atom.nucleus] = key
415426
}
416-
self._textToLatexSymbolName = output
427+
textToLatexLock.lock()
428+
defer { textToLatexLock.unlock() }
429+
if self._textToLatexSymbolName == nil {
430+
self._textToLatexSymbolName = output
431+
}
417432
}
418433
return self._textToLatexSymbolName!
419434
}
420-
set {
421-
self._textToLatexSymbolName = newValue
422-
}
435+
// set {
436+
// self._textToLatexSymbolName = newValue
437+
// }
423438
}
424439

425440
// public static let sharedInstance = MTMathAtomFactory()
@@ -603,8 +618,13 @@ public class MTMathAtomFactory {
603618
e.g. to define a symbol for "lcm" one can call:
604619
`MTMathAtomFactory.add(latexSymbol:"lcm", value:MTMathAtomFactory.operatorWithName("lcm", limits: false))` */
605620
public static func add(latexSymbol name: String, value: MTMathAtom) {
621+
let _ = Self.textToLatexSymbolName
622+
// above force textToLatexSymbolName to instantiate first, _textToLatexSymbolName also initialized.
623+
textToLatexLock.lock()
624+
defer { textToLatexLock.unlock() }
606625
supportedLatexSymbols[name] = value
607-
Self.textToLatexSymbolName[value.nucleus] = name
626+
// below update the underlying dictionary entry.
627+
Self._textToLatexSymbolName?[value.nucleus] = name
608628
}
609629

610630
/** Returns a large opertor for the given name. If limits is true, limits are set up on

Sources/SwiftMath/MathRender/MTTypesetter.swift

+6
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,15 @@ enum InterElementSpaceType : Int {
2121
}
2222

2323
var interElementSpaceArray = [[InterElementSpaceType]]()
24+
private let interElementLock = NSLock()
2425

2526
func getInterElementSpaces() -> [[InterElementSpaceType]] {
2627
if interElementSpaceArray.isEmpty {
28+
29+
interElementLock.lock()
30+
defer { interElementLock.unlock() }
31+
guard interElementSpaceArray.isEmpty else { return interElementSpaceArray }
32+
2733
interElementSpaceArray =
2834
// ordinary operator binary relation open close punct fraction
2935
[ [.none, .thin, .nsMedium, .nsThick, .none, .none, .none, .nsThin], // ordinary
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// ConcurrencyThreadsafeTests.swift
3+
//
4+
//
5+
// Created by Peter Tang on 26/9/2023.
6+
//
7+
8+
import XCTest
9+
@testable import SwiftMath
10+
11+
final class ConcurrencyThreadsafeTests: XCTestCase {
12+
13+
private let executionQueue = DispatchQueue(label: "com.swiftmath.concurrencytests", attributes: .concurrent)
14+
private let executionGroup = DispatchGroup()
15+
16+
let totalCases = 20
17+
var testCount = 0
18+
19+
func testSwiftMathConcurrentScript() throws {
20+
for caseNumber in 0 ..< totalCases {
21+
helperConcurrency(caseNumber, in: executionGroup, on: executionQueue) {
22+
let result1 = getInterElementSpaces()
23+
let result2 = MTMathAtomFactory.delimValueToName
24+
let result3 = MTMathAtomFactory.accentValueToName
25+
let result4 = MTMathAtomFactory.textToLatexSymbolName
26+
XCTAssertNotNil(result1)
27+
XCTAssertNotNil(result2)
28+
XCTAssertNotNil(result3)
29+
XCTAssertNotNil(result4)
30+
}
31+
}
32+
executionGroup.notify(queue: .main) { [weak self] in
33+
// print("All test cases completed: \(self?.testCount ?? 0)")
34+
}
35+
executionGroup.wait()
36+
}
37+
func helperConcurrency(_ count: Int, in group: DispatchGroup, on queue: DispatchQueue, _ testClosure: @escaping () -> (Void)) {
38+
let workitem = DispatchWorkItem {
39+
testClosure()
40+
}
41+
workitem.notify(queue: .main) { [weak self] in
42+
self?.testCount += 1
43+
}
44+
queue.async(group: group, execute: workitem)
45+
}
46+
47+
}

0 commit comments

Comments
 (0)