From 25938db76567982dd9b72f16a9e1c0aa5444e397 Mon Sep 17 00:00:00 2001 From: Peter Tang Date: Sat, 26 Oct 2024 22:20:25 +0800 Subject: [PATCH] Add NSLock to protect lazily loaded tables in multithread concurrent setting --- .../MathRender/MTMathAtomFactory.swift | 39 +++++++++++++++---- .../MTFontMathTableV2Tests.swift | 34 ++++++++-------- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/Sources/SwiftMath/MathRender/MTMathAtomFactory.swift b/Sources/SwiftMath/MathRender/MTMathAtomFactory.swift index 8d00415..bb0a6bf 100644 --- a/Sources/SwiftMath/MathRender/MTMathAtomFactory.swift +++ b/Sources/SwiftMath/MathRender/MTMathAtomFactory.swift @@ -62,6 +62,7 @@ public class MTMathAtomFactory { "rfloor" : "\u{230B}" ] + private static let delimValueLock = NSLock() static var _delimValueToName = [String: String]() public static var delimValueToName: [String: String] { if _delimValueToName.isEmpty { @@ -78,7 +79,12 @@ public class MTMathAtomFactory { } output[value] = key } - _delimValueToName = output + // protect lazily loading table in a multi-thread concurrent environment + delimValueLock.lock() + defer { delimValueLock.unlock() } + if _delimValueToName.isEmpty { + _delimValueToName = output + } } return _delimValueToName } @@ -98,6 +104,7 @@ public class MTMathAtomFactory { "widetilde" : "\u{0303}" ] + private static let accentValueLock = NSLock() static var _accentValueToName: [String: String]? = nil public static var accentValueToName: [String: String] { if _accentValueToName == nil { @@ -115,7 +122,12 @@ public class MTMathAtomFactory { } output[value] = key } - _accentValueToName = output + // protect lazily loading table in a multi-thread concurrent environment + accentValueLock.lock() + defer { accentValueLock.unlock() } + if _accentValueToName == nil { + _accentValueToName = output + } } return _accentValueToName! } @@ -390,6 +402,7 @@ public class MTMathAtomFactory { "scriptscriptstyle" : MTMathStyle(style: .scriptOfScript), ] + private static let textToLatexLock = NSLock() static var _textToLatexSymbolName: [String: String]? = nil public static var textToLatexSymbolName: [String: String] { get { @@ -413,13 +426,20 @@ public class MTMathAtomFactory { } output[atom.nucleus] = key } - self._textToLatexSymbolName = output + // protect lazily loading table in a multi-thread concurrent environment + textToLatexLock.lock() + defer { textToLatexLock.unlock() } + if self._textToLatexSymbolName == nil { + self._textToLatexSymbolName = output + } } return self._textToLatexSymbolName! } - set { - self._textToLatexSymbolName = newValue - } + // make textToLatexSymbolName readonly (allows internal load) + // entries can be lazily added with NSLock protection. + // set { + // self._textToLatexSymbolName = newValue + // } } // public static let sharedInstance = MTMathAtomFactory() @@ -603,8 +623,13 @@ public class MTMathAtomFactory { e.g. to define a symbol for "lcm" one can call: `MTMathAtomFactory.add(latexSymbol:"lcm", value:MTMathAtomFactory.operatorWithName("lcm", limits: false))` */ public static func add(latexSymbol name: String, value: MTMathAtom) { + let _ = Self.textToLatexSymbolName + // above force textToLatexSymbolName to initialise first, _textToLatexSymbolName also initialized. + // protect lazily loading table in a multi-thread concurrent environment + textToLatexLock.lock() + defer { textToLatexLock.unlock() } supportedLatexSymbols[name] = value - Self.textToLatexSymbolName[value.nucleus] = name + Self._textToLatexSymbolName?[value.nucleus] = name } /** Returns a large opertor for the given name. If limits is true, limits are set up on diff --git a/Tests/SwiftMathTests/MTFontMathTableV2Tests.swift b/Tests/SwiftMathTests/MTFontMathTableV2Tests.swift index 23a2bb0..a6dcbf4 100644 --- a/Tests/SwiftMathTests/MTFontMathTableV2Tests.swift +++ b/Tests/SwiftMathTests/MTFontMathTableV2Tests.swift @@ -47,14 +47,14 @@ final class MTFontMathTableV2Tests: XCTestCase { func helperConcurrentMTFontMathTableV2(_ count: Int, mtfont: MTFontV2, in group: DispatchGroup, on queue: DispatchQueue) { let workitem = DispatchWorkItem { let mTable = mtfont.mathTable -// let values = [ -// mTable?.fractionNumeratorDisplayStyleShiftUp, -// mTable?.fractionNumeratorShiftUp, -// mTable?.fractionDenominatorDisplayStyleShiftDown, -// mTable?.fractionDenominatorShiftDown, -// mTable?.fractionNumeratorDisplayStyleGapMin, -// mTable?.fractionNumeratorGapMin, -// ].compactMap{$0} + let values = [ + mTable?.fractionNumeratorDisplayStyleShiftUp, + mTable?.fractionNumeratorShiftUp, + mTable?.fractionDenominatorDisplayStyleShiftDown, + mTable?.fractionDenominatorShiftDown, + mTable?.fractionNumeratorDisplayStyleGapMin, + mTable?.fractionNumeratorGapMin, + ].compactMap{$0} // if count % 50 == 0 { // print(values) // accessed these values on global thread. // } @@ -62,16 +62,16 @@ final class MTFontMathTableV2Tests: XCTestCase { } workitem.notify(queue: .main) { [weak self] in // print("\(Thread.isMainThread ? "main" : "global") completed .....") -// let mTable = mtfont.mathTable + let mTable = mtfont.mathTable if count % 70 == 0 { -// let values = [ -// mTable?.fractionNumeratorDisplayStyleShiftUp, -// mTable?.fractionNumeratorShiftUp, -// mTable?.fractionDenominatorDisplayStyleShiftDown, -// mTable?.fractionDenominatorShiftDown, -// mTable?.fractionNumeratorDisplayStyleGapMin, -// mTable?.fractionNumeratorGapMin, -// ].compactMap{$0} + let values = [ + mTable?.fractionNumeratorDisplayStyleShiftUp, + mTable?.fractionNumeratorShiftUp, + mTable?.fractionDenominatorDisplayStyleShiftDown, + mTable?.fractionDenominatorShiftDown, + mTable?.fractionNumeratorDisplayStyleGapMin, + mTable?.fractionNumeratorGapMin, + ].compactMap{$0} // if count % 50 == 0 { // print(values) // }