Aspose.PDF for .net 21.1 dictionary object usage not thread safe

Need to know which version, if any, of Aspose.PDF for .net fixes this dictionary insert/enumeration problem. Current version in use is 21.1.

From MSDN –

https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?view=netframework-4.7.2

A Dictionary can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization***.***

We are seeing multiple threads attempting to access a dictionary that is changing without being locked.
DETAILED ANALYSIS:

Below we are going to look at the thread that we are doing the INSERT on (59) and one of the threads that is enumerating the collection (54)

… Thread 59 doing the insert –

OS Thread Id: 0x30a8 (59)

    Child SP               IP Call Site

000000704b8f4270 00007ffdc5375412 System.Collections.Generic.Dictionary`2[[System.Char, mscorlib],[System.__Canon, mscorlib]].Insert(Char, System.__Canon, Boolean)

000000704b8f4300 00007ffd675821f9 Aspose.PDF!#=zwxu$Joq4PdeH0sJHH2Wnk0ezseQktYX8KXb73Jqtq99O.#=z2OLFCSU=(System.String, Int32)

000000704b8f4350 00007ffd675820b1 Aspose.PDF!#=zwxu$Joq4PdeH0sJHH2Wnk0ezseQktYX8KXb73Jqtq99O.#=ziLZIp4OG_Oew()

000000704b8fc9b0 00007ffd6755e714 Aspose.PDF!#=zwxu$Joq4PdeH0sJHH2Wnk0ezseQktYX8KXb73Jqtq99O.#=zXGfqX33YZxT1(System.String)

000000704b8fc9f0 00007ffd6755e42f Aspose.PDF!#=zvTBSW6Zaed0wowLrUj9HKiIUv6L63zJcN$szuyMrk2EKynxu9LbSUN8=.#=zUsfTq9o=(Int32)

000000704b8fca50 00007ffd67408373 Aspose.PDF!#=zSc2zND9faB2l1cyMlh71ruRML901ZtiuqT7KhGYa09DXAMG7084zlDw=.#=zzeTNADYJoxwC(Int32)

000000704b8fca80 00007ffd6755cedd Aspose.PDF!#=z8hvi3dDxmmrMvE7_zssGlw8Ckf35KmScfKivyb7Ddz8FBBVN$iP9t0E=.#=zNY9GmTKc4VS6LTxV7w==(#=zJLi5mMC1gubWZTlWh7nZZW6ZDbcWVw0MJQ==, Boolean,#=z$qK8V0DhfSurhk$CxIrcBj4VrfdEpm_9BMW2jx9nfbXUXy0yCObRoOc=)

000000704b8fcbb0 00007ffd673fc1dd Aspose.PDF!#=zQ1QRfnYGY_Cf6TD93PUWH1z$fZvnIK$597un$cRE3n59PT19FQ==.#=z_rKbx6k=(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr,#=zJLi5mMC1gubWZTlWh7nZZW6ZDbcWVw0MJQ==, Int32)

000000704b8fce60 00007ffd673fbf08 Aspose.PDF!#=z7AKGPP7xgh2ZlUPTQOLb0k2dBAb8DgjJK9RXm1213nPZCwHH4Q==.#=zFfNUsEsypu4I(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr)

000000704b8fcec0 00007ffd673be445 Aspose.PDF!#=zlE3GXhWJATyuGmk$Mx26uZVtHIpC9EnFYWpN3w2f6pBH.#=zYiY_PcVWcfjC(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr)

000000704b8fcf00 00007ffd673be3d4 Aspose.PDF!#=zlE3GXhWJATyuGmk$Mx26uZVtHIpC9EnFYWpN3w2f6pBH.#=zfJovujw=(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr)

000000704b8fcf40 00007ffd673bcc66 Aspose.PDF!#=zBpwQv4kqNuLWVhg1HEhtnz7_CAO$G4ntLYb2oNMy3jJW.#=zVl$YUPxoZ8Zb(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr, System.Collections.Generic.List`1)

000000704b8fcfb0 00007ffd67286199 Aspose.PDF!#=zBpwQv4kqNuLWVhg1HEhtnz7_CAO$G4ntLYb2oNMy3jJW.#=zInOmkls=(#=z2Dl9O39kx1DDITklJ8uoxODUL73b,#=zOrqnFYPnIgXaCzlc9qItrbJNiWsxpfTmZQ==,#=zW1YAYkz5bcj2nQoDh_ydVwJTyulyuYqLkeKSDhE=,#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr ByRef)

000000704b8fd020 00007ffd6727fc8f Aspose.PDF!#=zb1I0Zf4u2TAstjzbagZUm5TRW5td.#=zqG2kzy0=(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr ByRef)

000000704b8fd1a0 00007ffd6727e3f5 Aspose.PDF!#=zb1I0Zf4u2TAstjzbagZUm5TRW5td.#=zqG2kzy0=()

000000704b8fd1d0 00007ffd6727e28a Aspose.Pdf.Devices.ImageDevice.#=zqG2kzy0=(Aspose.Pdf.Page)

000000704b8fd270 00007ffd6727e0df Aspose.Pdf.Devices.JpegDevice.Process(Aspose.Pdf.Page, System.IO.Stream)

000000704b8fd2d0 00007ffd6723016a CDOL.WebViper.imagestreamer.getBitMapFromPDF(System.String)

… Disassembly of the method highlighted above

0:059> !U /d 00007ffd675821f9

Normal JIT generated code

Aspose.PDF!#=zwxu$Joq4PdeH0sJHH2Wnk0ezseQktYX8KXb73Jqtq99O.#=z2OLFCSU=(System.String, Int32)

Begin 00007ffd67582170, size b9

00007ffd`67582170 57 push rdi

00007ffd`67582171 56 push rsi

00007ffd`67582172 55 push rbp

← SNIPPED FOR BREVITY β†’

00007ffd675821aa e8912cdf5d call mscorlib_ni!System.Collections.Generic.Dictionary2[System.Char,System.__Canon].TryGetValue(Char, System.__Canon ByRef)$##6003927 (00007ffd`c5374e40)

← SNIPPED FOR BREVITY β†’

00007ffd675821ba 48b9906a61c4fd7f0000 mov rcx,offset mscorlib_ni!System.Environment.get_UserInteractive()$##6000E68 <PERF> (mscorlib_ni+0x16a90) (00007ffdc4616a90)

← SNIPPED FOR BREVITY β†’

00007ffd675821d4 e8671b5e5d call mscorlib_ni!System.Collections.Generic.Dictionary2[System.__Canon,System.Char]…ctor(Int32, System.Collections.Generic.IEqualityComparer1<System.__Canon>)$##6003907 (00007ffdc4b63d40)

← SNIPPED FOR BREVITY β†’

00007ffd`675821f1 4533c9 xor r9d,r9d

00007ffd675821f4 e88731df5d call mscorlib_ni!System.Collections.Generic.Dictionary2[System.Char,System.__Canon].Insert(Char, System.__Canon, Boolean)$##6003922 (00007ffd`c5375380) ß Executing instruction when the dump was taken

00007ffd675821f9 eb07 jmp 00007ffd67582202

00007ffd`675821fb 48896c2420 mov qword ptr [rsp+20h],rbp

… Thread 54 doing the enumeration (FindEntry)

NOTE: System.Collections.Generic.Dictionary`2.TryGetValue makes the call to .FindEntry, I’m pointing out here where the Aspose code initiates the call to .TryGetValue which then calls .FindEntry.

0:054> !clrstack

OS Thread Id: 0x3bd4 (54)

    Child SP               IP Call Site

000000705562cfa0 00007ffdc53756a0 System.Collections.Generic.Dictionary`2[[System.Char, mscorlib],[System.__Canon, mscorlib]].FindEntry(Char)

000000705562cff0 00007ffdc5374e57 System.Collections.Generic.Dictionary`2[[System.Char, mscorlib],[System.__Canon, mscorlib]].TryGetValue(Char, System.__Canon ByRef)

000000705562d030 00007ffd6755e739 Aspose.PDF!#=zwxu$Joq4PdeH0sJHH2Wnk0ezseQktYX8KXb73Jqtq99O.#=zXGfqX33YZxT1(System.String)

000000705562d070 00007ffd6755e42f Aspose.PDF!#=zvTBSW6Zaed0wowLrUj9HKiIUv6L63zJcN$szuyMrk2EKynxu9LbSUN8=.#=zUsfTq9o=(Int32)

000000705562d0d0 00007ffd67408373 Aspose.PDF!#=zSc2zND9faB2l1cyMlh71ruRML901ZtiuqT7KhGYa09DXAMG7084zlDw=.#=zzeTNADYJoxwC(Int32)

000000705562d100 00007ffd6755cedd Aspose.PDF!#=z8hvi3dDxmmrMvE7_zssGlw8Ckf35KmScfKivyb7Ddz8FBBVN$iP9t0E=.#=zNY9GmTKc4VS6LTxV7w==(#=zJLi5mMC1gubWZTlWh7nZZW6ZDbcWVw0MJQ==, Boolean,#=z$qK8V0DhfSurhk$CxIrcBj4VrfdEpm_9BMW2jx9nfbXUXy0yCObRoOc=)

000000705562d230 00007ffd673fc1dd Aspose.PDF!#=zQ1QRfnYGY_Cf6TD93PUWH1z$fZvnIK$597un$cRE3n59PT19FQ==.#=z_rKbx6k=(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr,#=zJLi5mMC1gubWZTlWh7nZZW6ZDbcWVw0MJQ==, Int32)

000000705562d4e0 00007ffd6754dcdb Aspose.PDF!#=zzfxltZYVr9ScJAHf_I$rVpbzORdEFLzTSXfupXKiZzZ$H5iYyQ==.#=zFfNUsEsypu4I(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr)

000000705562d520 00007ffd673be445 Aspose.PDF!#=zlE3GXhWJATyuGmk$Mx26uZVtHIpC9EnFYWpN3w2f6pBH.#=zYiY_PcVWcfjC(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr)

000000705562d560 00007ffd673be3d4 Aspose.PDF!#=zlE3GXhWJATyuGmk$Mx26uZVtHIpC9EnFYWpN3w2f6pBH.#=zfJovujw=(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr)

000000705562d5a0 00007ffd673bcc66 Aspose.PDF!#=zBpwQv4kqNuLWVhg1HEhtnz7_CAO$G4ntLYb2oNMy3jJW.#=zVl$YUPxoZ8Zb(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr, System.Collections.Generic.List`1)

000000705562d610 00007ffd67584d66 Aspose.PDF!#=zBpwQv4kqNuLWVhg1HEhtnz7_CAO$G4ntLYb2oNMy3jJW.#=zInOmkls=(#=z2Dl9O39kx1DDITklJ8uoxODUL73b,#=zaFrGza58Ln7_lV1IeXXChjx4nFvpdz$Jyw==,#=zO0kt11UIIc$hStxXYHEspvN6HAGX1Rf22ZIJFFs=,#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr)

000000705562d650 00007ffd673c12e9 Aspose.PDF!#=zN0rxLZ0nC$n7nts4RfcoB33eURIJ14RmuCiGqt4f1kRVUjBGQA==.#=zFfNUsEsypu4I(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr)

000000705562d7a0 00007ffd673be445 Aspose.PDF!#=zlE3GXhWJATyuGmk$Mx26uZVtHIpC9EnFYWpN3w2f6pBH.#=zYiY_PcVWcfjC(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr)

000000705562d7e0 00007ffd673be3d4 Aspose.PDF!#=zlE3GXhWJATyuGmk$Mx26uZVtHIpC9EnFYWpN3w2f6pBH.#=zfJovujw=(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr)

000000705562d820 00007ffd673bcc66 Aspose.PDF!#=zBpwQv4kqNuLWVhg1HEhtnz7_CAO$G4ntLYb2oNMy3jJW.#=zVl$YUPxoZ8Zb(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr, System.Collections.Generic.List`1)

000000705562d890 00007ffd67286199 Aspose.PDF!#=zBpwQv4kqNuLWVhg1HEhtnz7_CAO$G4ntLYb2oNMy3jJW.#=zInOmkls=(#=z2Dl9O39kx1DDITklJ8uoxODUL73b,#=zOrqnFYPnIgXaCzlc9qItrbJNiWsxpfTmZQ==,#=zW1YAYkz5bcj2nQoDh_ydVwJTyulyuYqLkeKSDhE=,#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr ByRef)

000000705562d900 00007ffd6727fc8f Aspose.PDF!#=zb1I0Zf4u2TAstjzbagZUm5TRW5td.#=zqG2kzy0=(#=zLNXFkb4EpseBcwMvmTIfntQEOyWJzMiOqMTQeb5ZSewr ByRef)

000000705562da80 00007ffd6727e3f5 Aspose.PDF!#=zb1I0Zf4u2TAstjzbagZUm5TRW5td.#=zqG2kzy0=()

000000705562dab0 00007ffd6727e28a Aspose.Pdf.Devices.ImageDevice.#=zqG2kzy0=(Aspose.Pdf.Page)

000000705562db50 00007ffd6727e0df Aspose.Pdf.Devices.JpegDevice.Process(Aspose.Pdf.Page, System.IO.Stream)

000000705562dbb0 00007ffd6723016a CDOL.WebViper.imagestreamer.getBitMapFromPDF(System.String)

… Disassembly of the method highlighted above

0:054> !U /d 00007ffd6755e739

Normal JIT generated code

Aspose.PDF!#=zwxu$Joq4PdeH0sJHH2Wnk0ezseQktYX8KXb73Jqtq99O.#=zXGfqX33YZxT1(System.String)

Begin 00007ffd6755e6d0, size aa

00007ffd`6755e6d0 56 push rsi

00007ffd`6755e6d1 4883ec30 sub rsp,30h

← SNIPPED FOR BREVITY β†’

00007ffd6755e70d 7505 jne 00007ffd6755e714

00007ffd6755e70f e8f42cffff call 00007ffd67551408 (Aspose.PDF!#=zwxu$Joq4PdeH0sJHH2Wnk0ezseQktYX8KXb73Jqtq99O.#=ziLZIp4OG_Oew(), mdToken: 000000000601d197)

← SNIPPED FOR BREVITY β†’

00007ffd`6755e732 3909 cmp dword ptr [rcx],ecx

00007ffd6755e734 e80767e15d call mscorlib_ni!System.Collections.Generic.Dictionary2[System.Char,System.__Canon].TryGetValue(Char, System.__Canon ByRef)$##6003927 (00007ffd`c5374e40) ß Executing instruction when the dump was taken

00007ffd`6755e739 84c0 test al,al

00007ffd6755e73b 742f je 00007ffd6755e76c

← SNIPPED FOR BREVITY β†’

00007ffd`6755e74d 488bd6 mov rdx,rsi

… And we can see here that multiple threads (54 & 60) are accessing the same Generic Dictionary.

OS Thread Id: 0x3bd4 (54)

RSP/REG Object Name

rax 0000006cff0909a8 System.Collections.Generic.Dictionary2+Entry[[System.Char, mscorlib],[System.Collections.Generic.Dictionary2[[System.String, mscorlib],[System.Char, mscorlib]], mscorlib]][]

rsi 0000006f3ef19728 System.Collections.Generic.Dictionary2[[System.Char, mscorlib],[System.Collections.Generic.Dictionary2[[System.String, mscorlib],[System.Char, mscorlib]], mscorlib]]

r12 0000006efeec7e38 System.Collections.Generic.List`1[[#=zmcmFK44ua_78K0uhsdt3Osr_tNz4ActsoKJUJ$Jfn9dc$p33GA==, Aspose.PDF]]

r15 0000006efeec7e10 Aspose.PDF!#=zyv7qNJaFLOxmHUayweA$tZGc$xOSvtz1ctDZdB$kqg1cxEnojw==

000000705562CFD8 0000006f3ef19728 System.Collections.Generic.Dictionary2[[System.Char, mscorlib],[System.Collections.Generic.Dictionary2[[System.String, mscorlib],[System.Char, mscorlib]], mscorlib]]

000000705562D018 0000006f3eee5810 System.String E

OS Thread Id: 0x3510 (60)

RSP/REG Object Name

rax 0000006cff0909a8 System.Collections.Generic.Dictionary2+Entry[[System.Char, mscorlib],[System.Collections.Generic.Dictionary2[[System.String, mscorlib],[System.Char, mscorlib]], mscorlib]][]

rsi 0000006f3ef19728 System.Collections.Generic.Dictionary2[[System.Char, mscorlib],[System.Collections.Generic.Dictionary2[[System.String, mscorlib],[System.Char, mscorlib]], mscorlib]]

r12 0000006cff1a7fc8 System.Collections.Generic.List`1[[#=zmcmFK44ua_78K0uhsdt3Osr_tNz4ActsoKJUJ$Jfn9dc$p33GA==, Aspose.PDF]]

r14 0000006f3eef4c50 System.String one

r15 0000006cff1a7fa0 Aspose.PDF!#=zyv7qNJaFLOxmHUayweA$tZGc$xOSvtz1ctDZdB$kqg1cxEnojw==

000000706158D080 0000006f3f0276e0 System.Collections.Generic.SortedSet1+Node[[System.Collections.Generic.KeyValuePair2[[#=zmcpXga1wu_kM3xOwpmnxrO5TdzoL, Aspose.PDF],[System.Int32, mscorlib]], mscorlib]]

000000706158D098 0000006f3ef19728 System.Collections.Generic.Dictionary2[[System.Char, mscorlib],[System.Collections.Generic.Dictionary2[[System.String, mscorlib],[System.Char, mscorlib]], mscorlib]]

000000706158D0D8 0000006f3eef4c50 System.String one

… and looking at the ASPOSE obfuscated code …

– we can see that there is no locking around when the insert is being done ….

// #=zwxu$Joq4PdeH0sJHH2Wnk0ezseQktYX8KXb73Jqtq99O
private static void #=z2OLFCSU=(string #=ztJ8TZV4=, int #=zgFDl_yU=)
{
char expr_02 = #=ztJ8TZV4=[0];
char key;
if (true)
{
key = expr_02;
}
Dictionary<string, char> dictionary;
if (!#=zwxu$Joq4PdeH0sJHH2Wnk0ezseQktYX8KXb73Jqtq99O.#=zmnLjy_I=.TryGetValue(key, out dictionary))
{
Dictionary<string, char> expr_1F = new Dictionary<string, char>();
if (6 != 0)
{
dictionary = expr_1F;
}
#=zwxu$Joq4PdeH0sJHH2Wnk0ezseQktYX8KXb73Jqtq99O.#=zmnLjy_I=[key] = dictionary; ß FINDENTRY & INSERT HERE. Occurring during assignment of dictionary to .#=zmnLjy_I=
}
dictionary[#=ztJ8TZV4=] = (char)#=zgFDl_yU=;
}

Looking at the assignment of #=zmnLjy_I= to the #=zwxu$Joq4PdeH0sJHH2Wnk0ezseQktYX8KXb73Jqtq99O object (the generic dictionary) –

// System.Collections.Generic.Dictionary<TKey, TValue>
[__DynamicallyInvokable]
public TValue this[TKey key]
{
[__DynamicallyInvokable]
get
{
int num = this.FindEntry(key);
if (num >= 0)
{
return this.entries[num].value;
}
ThrowHelper.ThrowKeyNotFoundException();
return default(TValue);
}
[__DynamicallyInvokable]
set
{
this.Insert(key, value, false);
}
}

@yetanothersteve

Please try to use the latest version of the API i.e. Aspose.PDF for .NET 22.7 as always recommended. However, if issue still persists, please share a sample code snippet and sample source files for our reference so that we can try to replicate the issue in our environment and address it accordingly.

1 Like