Skip to content
This repository was archived by the owner on Jul 9, 2023. It is now read-only.

Commit a0613aa

Browse files
committed
restore certificate create task cache for burst requests
1 parent 4966b6f commit a0613aa

File tree

2 files changed

+44
-47
lines changed

2 files changed

+44
-47
lines changed

src/Titanium.Web.Proxy/Network/CachedCertificate.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ internal sealed class CachedCertificate
1111
{
1212
internal X509Certificate2 Certificate { get; set; }
1313

14-
/// <summary>
15-
/// Certificate creation task.
16-
/// </summary>
17-
internal Task<X509Certificate2> CreationTask { get; set; }
18-
1914
/// <summary>
2015
/// Last time this certificate was used.
2116
/// Useful in determining its cache lifetime.

src/Titanium.Web.Proxy/Network/CertificateManager.cs

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,21 @@ public sealed class CertificateManager : IDisposable
4444
/// <summary>
4545
/// Cache dictionary
4646
/// </summary>
47-
private readonly ConcurrentDictionary<string, CachedCertificate> cachedCertificates;
47+
private readonly ConcurrentDictionary<string, CachedCertificate> cachedCertificates
48+
= new ConcurrentDictionary<string, CachedCertificate>();
4849

49-
private readonly CancellationTokenSource clearCertificatesTokenSource;
50+
/// <summary>
51+
/// A list of pending certificate creation tasks.
52+
/// Usefull to prevent multiple threads working on same certificate generation
53+
/// when burst certificate generation requests happen for same certificate.
54+
/// </summary>
55+
private readonly ConcurrentDictionary<string, Task<X509Certificate2>> pendingCertificateCreationTasks
56+
= new ConcurrentDictionary<string, Task<X509Certificate2>>();
57+
58+
private readonly CancellationTokenSource clearCertificatesTokenSource
59+
= new CancellationTokenSource();
5060

51-
private readonly object rootCertCreationLock;
61+
private readonly object rootCertCreationLock = new object();
5262

5363
private ICertificateMaker certEngine;
5464

@@ -60,7 +70,7 @@ public sealed class CertificateManager : IDisposable
6070

6171
private string rootCertificateName;
6272

63-
private ICertificateCache certificateCache;
73+
private ICertificateCache certificateCache = new DefaultCertificateDiskCache();
6474

6575
/// <summary>
6676
/// Initializes a new instance of the <see cref="CertificateManager"/> class.
@@ -99,14 +109,6 @@ internal CertificateManager(string rootCertificateName, string rootCertificateIs
99109
}
100110

101111
CertificateEngine = CertificateEngine.BouncyCastle;
102-
103-
cachedCertificates = new ConcurrentDictionary<string, CachedCertificate>();
104-
105-
clearCertificatesTokenSource = new CancellationTokenSource();
106-
107-
certificateCache = new DefaultCertificateDiskCache();
108-
109-
rootCertCreationLock = new object();
110112
}
111113

112114
/// <summary>
@@ -225,8 +227,9 @@ public X509Certificate2 RootCertificate
225227
public bool SaveFakeCertificates { get; set; } = false;
226228

227229
/// <summary>
228-
/// The service to save fake certificates.
229-
/// The default storage saves certificates in folder "crts" (will be created in proxy dll directory).
230+
/// The fake certificate cache storage.
231+
/// The default cache storage implementation saves certificates in folder "crts" (will be created in proxy dll directory).
232+
/// Implement ICertificateCache interface and assign concrete class here to customize.
230233
/// </summary>
231234
public ICertificateCache CertificateStorage
232235
{
@@ -436,41 +439,40 @@ internal X509Certificate2 CreateCertificate(string certificateName, bool isRootC
436439
internal async Task<X509Certificate2> CreateCertificateAsync(string certificateName)
437440
{
438441
// check in cache first
439-
var item = cachedCertificates.GetOrAdd(certificateName, _ =>
442+
if (cachedCertificates.TryGetValue(certificateName, out var cached))
440443
{
441-
var cached = new CachedCertificate();
442-
cached.CreationTask = Task.Run(() =>
443-
{
444-
var certificate = CreateCertificate(certificateName, false);
445-
446-
// see http://www.albahari.com/threading/part4.aspx for the explanation
447-
// why Thread.MemoryBarrier is used here and below
448-
cached.Certificate = certificate;
449-
Thread.MemoryBarrier();
450-
cached.CreationTask = null;
451-
Thread.MemoryBarrier();
452-
return certificate;
453-
});
454-
455-
return cached;
456-
});
457-
458-
item.LastAccess = DateTime.Now;
444+
cached.LastAccess = DateTime.Now;
445+
return cached.Certificate;
446+
}
459447

460-
if (item.Certificate != null)
448+
// handle burst requests with same certificate name
449+
// by checking for existing task for same certificate name
450+
if (pendingCertificateCreationTasks.TryGetValue(certificateName, out var task))
461451
{
462-
return item.Certificate;
452+
return await task;
463453
}
464454

465-
// handle burst requests with same certificate name
466-
// by checking for existing task
467-
Thread.MemoryBarrier();
468-
var task = item.CreationTask;
455+
// run certificate creation task & add it to pending tasks
456+
task = Task.Run(() =>
457+
{
458+
var result = CreateCertificate(certificateName, false);
459+
if (result != null)
460+
{
461+
cachedCertificates.TryAdd(certificateName, new CachedCertificate
462+
{
463+
Certificate = result
464+
});
465+
}
469466

470-
Thread.MemoryBarrier();
467+
return result;
468+
});
469+
pendingCertificateCreationTasks.TryAdd(certificateName, task);
471470

472-
// return result
473-
return item.Certificate ?? await task;
471+
// cleanup pending tasks & return result
472+
var certificate = await task;
473+
pendingCertificateCreationTasks.TryRemove(certificateName, out task);
474+
475+
return certificate;
474476
}
475477

476478
/// <summary>

0 commit comments

Comments
 (0)