Skip to content

Commit 3e3ccdd

Browse files
authored
Thread-safe image cache handling (#215)
1. Specify an ID in AddImagePart to avoid duplicate key issues when handling multiple images. (#215) 2. Add thread-safe image cache handling
1 parent 2c1afd5 commit 3e3ccdd

File tree

1 file changed

+52
-7
lines changed

1 file changed

+52
-7
lines changed

src/Html2OpenXml/IO/ImagePrefetcher.cs

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ sealed class ImagePrefetcher<T> : IImageLoader
5252
private readonly T hostingPart;
5353
private readonly IWebRequest resourceLoader;
5454
private readonly HtmlImageInfoCollection prefetchedImages;
55+
private readonly object lockObject = new object();
5556

5657

5758
/// <summary>
@@ -76,8 +77,12 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
7677
/// </summary>
7778
public async Task<HtmlImageInfo?> Download(string imageUri, CancellationToken cancellationToken)
7879
{
79-
if (prefetchedImages.Contains(imageUri))
80-
return prefetchedImages[imageUri];
80+
// Check if image is already cached using thread-safe operation
81+
lock (lockObject)
82+
{
83+
if (prefetchedImages.Contains(imageUri))
84+
return prefetchedImages[imageUri];
85+
}
8186

8287
HtmlImageInfo? iinfo;
8388
if (DataUri.IsWellFormed(imageUri)) // data inline, encoded in base64
@@ -89,8 +94,18 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
8994
iinfo = await DownloadRemoteImage(imageUri, cancellationToken).ConfigureAwait(false);
9095
}
9196

97+
// Add to cache using thread-safe operation
9298
if (iinfo != null)
93-
prefetchedImages.Add(iinfo);
99+
{
100+
lock (lockObject)
101+
{
102+
// Double-check pattern to prevent duplicate adds during concurrent access
103+
if (!prefetchedImages.Contains(imageUri))
104+
{
105+
prefetchedImages.Add(iinfo);
106+
}
107+
}
108+
}
94109

95110
return iinfo;
96111
}
@@ -121,7 +136,16 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
121136
return null;
122137
}
123138

124-
var ipart = hostingPart.AddImagePart(type);
139+
// Generate a unique GUID-based relationship ID for the image part
140+
string relationshipId = "img_" + Guid.NewGuid().ToString("N");
141+
142+
// Synchronize access to AddImagePart to prevent concurrent modifications to the OpenXml document
143+
ImagePart ipart;
144+
lock (lockObject)
145+
{
146+
ipart = hostingPart.AddImagePart(type, relationshipId);
147+
}
148+
125149
Size originalSize;
126150
using (var outputStream = ipart.GetStream(FileMode.Create))
127151
{
@@ -131,7 +155,13 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
131155
originalSize = GetImageSize(outputStream);
132156
}
133157

134-
return new HtmlImageInfo(src, hostingPart.GetIdOfPart(ipart)) {
158+
string partId;
159+
lock (lockObject)
160+
{
161+
partId = hostingPart.GetIdOfPart(ipart);
162+
}
163+
164+
return new HtmlImageInfo(src, partId) {
135165
TypeInfo = type,
136166
Size = originalSize
137167
};
@@ -147,7 +177,16 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
147177
{
148178
Size originalSize;
149179
knownContentType.TryGetValue(dataUri!.Mime, out PartTypeInfo type);
150-
var ipart = hostingPart.AddImagePart(type);
180+
// Generate a unique GUID-based relationship ID for the image part
181+
string relationshipId = "img_" + Guid.NewGuid().ToString("N");
182+
183+
// Synchronize access to AddImagePart to prevent concurrent modifications to the OpenXml document
184+
ImagePart ipart;
185+
lock (lockObject)
186+
{
187+
ipart = hostingPart.AddImagePart(type, relationshipId);
188+
}
189+
151190
using (var outputStream = ipart.GetStream(FileMode.Create))
152191
{
153192
outputStream.Write(dataUri.Data, 0, dataUri.Data.Length);
@@ -156,7 +195,13 @@ public ImagePrefetcher(T hostingPart, IWebRequest resourceLoader)
156195
originalSize = GetImageSize(outputStream);
157196
}
158197

159-
return new HtmlImageInfo(src, hostingPart.GetIdOfPart(ipart)) {
198+
string partId;
199+
lock (lockObject)
200+
{
201+
partId = hostingPart.GetIdOfPart(ipart);
202+
}
203+
204+
return new HtmlImageInfo(src, partId) {
160205
TypeInfo = type,
161206
Size = originalSize
162207
};

0 commit comments

Comments
 (0)