| // Copyright 2022 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package tls |
| |
| import ( |
| "encoding/pem" |
| "fmt" |
| "runtime" |
| "testing" |
| "time" |
| ) |
| |
| func TestCertCache(t *testing.T) { |
| cc := certCache{} |
| p, _ := pem.Decode([]byte(rsaCertPEM)) |
| if p == nil { |
| t.Fatal("Failed to decode certificate") |
| } |
| |
| certA, err := cc.newCert(p.Bytes) |
| if err != nil { |
| t.Fatalf("newCert failed: %s", err) |
| } |
| certB, err := cc.newCert(p.Bytes) |
| if err != nil { |
| t.Fatalf("newCert failed: %s", err) |
| } |
| if certA.cert != certB.cert { |
| t.Fatal("newCert returned a unique reference for a duplicate certificate") |
| } |
| |
| if entry, ok := cc.Load(string(p.Bytes)); !ok { |
| t.Fatal("cache does not contain expected entry") |
| } else { |
| if refs := entry.(*cacheEntry).refs.Load(); refs != 2 { |
| t.Fatalf("unexpected number of references: got %d, want 2", refs) |
| } |
| } |
| |
| timeoutRefCheck := func(t *testing.T, key string, count int64) { |
| t.Helper() |
| c := time.After(4 * time.Second) |
| for { |
| select { |
| case <-c: |
| t.Fatal("timed out waiting for expected ref count") |
| default: |
| e, ok := cc.Load(key) |
| if !ok && count != 0 { |
| t.Fatal("cache does not contain expected key") |
| } else if count == 0 && !ok { |
| return |
| } |
| |
| if e.(*cacheEntry).refs.Load() == count { |
| return |
| } |
| } |
| } |
| } |
| |
| // Keep certA alive until at least now, so that we can |
| // purposefully nil it and force the finalizer to be |
| // called. |
| runtime.KeepAlive(certA) |
| certA = nil |
| runtime.GC() |
| |
| timeoutRefCheck(t, string(p.Bytes), 1) |
| |
| // Keep certB alive until at least now, so that we can |
| // purposefully nil it and force the finalizer to be |
| // called. |
| runtime.KeepAlive(certB) |
| certB = nil |
| runtime.GC() |
| |
| timeoutRefCheck(t, string(p.Bytes), 0) |
| } |
| |
| func BenchmarkCertCache(b *testing.B) { |
| p, _ := pem.Decode([]byte(rsaCertPEM)) |
| if p == nil { |
| b.Fatal("Failed to decode certificate") |
| } |
| |
| cc := certCache{} |
| b.ReportAllocs() |
| b.ResetTimer() |
| // We expect that calling newCert additional times after |
| // the initial call should not cause additional allocations. |
| for extra := 0; extra < 4; extra++ { |
| b.Run(fmt.Sprint(extra), func(b *testing.B) { |
| actives := make([]*activeCert, extra+1) |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| var err error |
| actives[0], err = cc.newCert(p.Bytes) |
| if err != nil { |
| b.Fatal(err) |
| } |
| for j := 0; j < extra; j++ { |
| actives[j+1], err = cc.newCert(p.Bytes) |
| if err != nil { |
| b.Fatal(err) |
| } |
| } |
| for j := 0; j < extra+1; j++ { |
| actives[j] = nil |
| } |
| runtime.GC() |
| } |
| }) |
| } |
| } |