porting all github actions from bluesky-social/indigo to tangled CI

identity/redisdir: various fixes (#534)

A bunch of cleanups and bug fixes.

The biggest fix is to apply a bug fix from the cache directory resulting
in "nil/nil" responses. These were fixed in
https://github.com/bluesky-social/indigo/pull/479, but not copied to the
redis directory implementation, which has the same coalescing code. This
recently impacted @whyrusleeping's use in discover. This is a
backwards-compatible fix.

Another fix handles cached error messages. An empty string was being
stored for the DID. Because redis cache serializes, and the
de-serialized DID is re-parsed, the empty strings fail to parse,
resulting in an error. I guess we could consider having text parsing of
DIDs not actually parse the string? but that would break auto-validation
when it is expected. The fix was to make that field nullable. this might
make the change not-backwards-compatible with existing cached metadata?
this only impacts cached errors, which have a shorter TTL, so might not
be that big of a deal in practice?

Other smaller cleanups and fixes, which rolled in here to make the code
align better with the in-memory caching directory, so that merge/review
is easier:

- adds invalid handle TTL to redisdir
- adds "WithCacheState" variants of methods, for metrics/perf
measurement
- fix deletion/purging
- special-case handling of resolving invalid handle

One remaining issue is that the cached error types come back as generic
errors (with error kind/type striped). It would be nice if that wasn't
the case, so that code could detect specific types of errors?
Considering doing a manual enum/integer hack to encode the type and
convert the errors to the actual type on read, for a small set of known
identity errors.

authored by Whyrusleeping and committed by GitHub 160af4ab 2e5d3d7e

Changed files
+306 -100
atproto
cmd
hepa
+4 -33
atproto/identity/cache_directory.go
··· 7 7 "time" 8 8 9 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 - "github.com/prometheus/client_golang/prometheus" 11 - "github.com/prometheus/client_golang/prometheus/promauto" 12 10 13 11 "github.com/hashicorp/golang-lru/v2/expirable" 14 12 ) ··· 35 33 Err error 36 34 } 37 35 38 - var handleCacheHits = promauto.NewCounter(prometheus.CounterOpts{ 39 - Name: "atproto_directory_handle_cache_hits", 40 - Help: "Number of cache hits for ATProto handle lookups", 41 - }) 42 - 43 - var handleCacheMisses = promauto.NewCounter(prometheus.CounterOpts{ 44 - Name: "atproto_directory_handle_cache_misses", 45 - Help: "Number of cache misses for ATProto handle lookups", 46 - }) 47 - 48 - var identityCacheHits = promauto.NewCounter(prometheus.CounterOpts{ 49 - Name: "atproto_directory_identity_cache_hits", 50 - Help: "Number of cache hits for ATProto identity lookups", 51 - }) 52 - 53 - var identityCacheMisses = promauto.NewCounter(prometheus.CounterOpts{ 54 - Name: "atproto_directory_identity_cache_misses", 55 - Help: "Number of cache misses for ATProto identity lookups", 56 - }) 57 - 58 - var identityRequestsCoalesced = promauto.NewCounter(prometheus.CounterOpts{ 59 - Name: "atproto_directory_identity_requests_coalesced", 60 - Help: "Number of identity requests coalesced", 61 - }) 62 - 63 - var handleRequestsCoalesced = promauto.NewCounter(prometheus.CounterOpts{ 64 - Name: "atproto_directory_handle_requests_coalesced", 65 - Help: "Number of handle requests coalesced", 66 - }) 67 - 68 36 var _ Directory = (*CacheDirectory)(nil) 69 37 70 38 // Capacity of zero means unlimited size. Similarly, ttl of zero means unlimited duration. 71 - func NewCacheDirectory(inner Directory, capacity int, hitTTL, errTTL time.Duration, invalidHandleTTL time.Duration) CacheDirectory { 39 + func NewCacheDirectory(inner Directory, capacity int, hitTTL, errTTL, invalidHandleTTL time.Duration) CacheDirectory { 72 40 return CacheDirectory{ 73 41 ErrTTL: errTTL, 74 42 InvalidHandleTTL: invalidHandleTTL, ··· 124 92 } 125 93 126 94 func (d *CacheDirectory) ResolveHandle(ctx context.Context, h syntax.Handle) (syntax.DID, error) { 95 + if h.IsInvalidHandle() { 96 + return "", fmt.Errorf("invalid handle") 97 + } 127 98 entry, ok := d.handleCache.Get(h) 128 99 if ok && !d.IsHandleStale(&entry) { 129 100 handleCacheHits.Inc()
+4
atproto/identity/handle.go
··· 168 168 var dnsErr error 169 169 var did syntax.DID 170 170 171 + if handle.IsInvalidHandle() { 172 + return "", fmt.Errorf("invalid handle") 173 + } 174 + 171 175 if !handle.AllowedTLD() { 172 176 return "", ErrHandleReservedTLD 173 177 }
+36
atproto/identity/metrics.go
··· 1 + package identity 2 + 3 + import ( 4 + "github.com/prometheus/client_golang/prometheus" 5 + "github.com/prometheus/client_golang/prometheus/promauto" 6 + ) 7 + 8 + var handleCacheHits = promauto.NewCounter(prometheus.CounterOpts{ 9 + Name: "atproto_directory_handle_cache_hits", 10 + Help: "Number of cache hits for ATProto handle lookups", 11 + }) 12 + 13 + var handleCacheMisses = promauto.NewCounter(prometheus.CounterOpts{ 14 + Name: "atproto_directory_handle_cache_misses", 15 + Help: "Number of cache misses for ATProto handle lookups", 16 + }) 17 + 18 + var identityCacheHits = promauto.NewCounter(prometheus.CounterOpts{ 19 + Name: "atproto_directory_identity_cache_hits", 20 + Help: "Number of cache hits for ATProto identity lookups", 21 + }) 22 + 23 + var identityCacheMisses = promauto.NewCounter(prometheus.CounterOpts{ 24 + Name: "atproto_directory_identity_cache_misses", 25 + Help: "Number of cache misses for ATProto identity lookups", 26 + }) 27 + 28 + var identityRequestsCoalesced = promauto.NewCounter(prometheus.CounterOpts{ 29 + Name: "atproto_directory_identity_requests_coalesced", 30 + Help: "Number of identity requests coalesced", 31 + }) 32 + 33 + var handleRequestsCoalesced = promauto.NewCounter(prometheus.CounterOpts{ 34 + Name: "atproto_directory_handle_requests_coalesced", 35 + Help: "Number of handle requests coalesced", 36 + })
+136
atproto/identity/redisdir/live_test.go
··· 1 + package redisdir 2 + 3 + import ( 4 + "context" 5 + "log/slog" 6 + "net/http" 7 + "strings" 8 + "sync" 9 + "testing" 10 + "time" 11 + 12 + "github.com/bluesky-social/indigo/atproto/identity" 13 + "github.com/bluesky-social/indigo/atproto/syntax" 14 + "golang.org/x/time/rate" 15 + 16 + "github.com/stretchr/testify/assert" 17 + ) 18 + 19 + var redisLocalTestURL string = "redis://localhost:6379/0" 20 + 21 + // NOTE: this hits the open internet! marked as skip below by default 22 + func testDirectoryLive(t *testing.T, d identity.Directory) { 23 + assert := assert.New(t) 24 + ctx := context.Background() 25 + 26 + handle := syntax.Handle("atproto.com") 27 + did := syntax.DID("did:plc:ewvi7nxzyoun6zhxrhs64oiz") 28 + pdsSuffix := "host.bsky.network" 29 + 30 + resp, err := d.LookupHandle(ctx, handle) 31 + assert.NoError(err) 32 + assert.Equal(handle, resp.Handle) 33 + assert.Equal(did, resp.DID) 34 + assert.True(strings.HasSuffix(resp.PDSEndpoint(), pdsSuffix)) 35 + dh, err := resp.DeclaredHandle() 36 + assert.NoError(err) 37 + assert.Equal(handle, dh) 38 + pk, err := resp.PublicKey() 39 + assert.NoError(err) 40 + assert.NotNil(pk) 41 + 42 + resp, err = d.LookupDID(ctx, did) 43 + assert.NoError(err) 44 + assert.Equal(handle, resp.Handle) 45 + assert.Equal(did, resp.DID) 46 + assert.True(strings.HasSuffix(resp.PDSEndpoint(), pdsSuffix)) 47 + 48 + _, err = d.LookupHandle(ctx, syntax.Handle("fake-dummy-no-resolve.atproto.com")) 49 + assert.Error(err) 50 + //assert.ErrorIs(err, identity.ErrHandleNotFound) 51 + 52 + _, err = d.LookupDID(ctx, syntax.DID("did:web:fake-dummy-no-resolve.atproto.com")) 53 + assert.Error(err) 54 + //assert.ErrorIs(err, identity.ErrDIDNotFound) 55 + 56 + _, err = d.LookupDID(ctx, syntax.DID("did:plc:fake-dummy-no-resolve.atproto.com")) 57 + assert.Error(err) 58 + //assert.ErrorIs(err, identity.ErrDIDNotFound) 59 + 60 + _, err = d.LookupHandle(ctx, syntax.HandleInvalid) 61 + assert.Error(err) 62 + } 63 + 64 + func TestRedisDirectory(t *testing.T) { 65 + t.Skip("TODO: skipping live network test") 66 + assert := assert.New(t) 67 + ctx := context.Background() 68 + inner := identity.BaseDirectory{} 69 + d, err := NewRedisDirectory(&inner, redisLocalTestURL, time.Hour*1, time.Hour*1, time.Hour*1, 1000) 70 + if err != nil { 71 + t.Fatal(err) 72 + } 73 + 74 + err = d.Purge(ctx, syntax.Handle("atproto.com").AtIdentifier()) 75 + assert.NoError(err) 76 + err = d.Purge(ctx, syntax.Handle("fake-dummy-no-resolve.atproto.com").AtIdentifier()) 77 + assert.NoError(err) 78 + err = d.Purge(ctx, syntax.DID("did:web:fake-dummy-no-resolve.atproto.com").AtIdentifier()) 79 + assert.NoError(err) 80 + err = d.Purge(ctx, syntax.DID("did:plc:fake-dummy-no-resolve.atproto.com").AtIdentifier()) 81 + assert.NoError(err) 82 + 83 + for i := 0; i < 3; i = i + 1 { 84 + testDirectoryLive(t, d) 85 + } 86 + } 87 + 88 + func TestRedisCoalesce(t *testing.T) { 89 + t.Skip("TODO: skipping live network test") 90 + 91 + assert := assert.New(t) 92 + handle := syntax.Handle("atproto.com") 93 + did := syntax.DID("did:plc:ewvi7nxzyoun6zhxrhs64oiz") 94 + 95 + base := identity.BaseDirectory{ 96 + PLCURL: "https://plc.directory", 97 + HTTPClient: http.Client{ 98 + Timeout: time.Second * 15, 99 + }, 100 + // Limit the number of requests we can make to the PLC to 1 per second 101 + PLCLimiter: rate.NewLimiter(1, 1), 102 + TryAuthoritativeDNS: true, 103 + SkipDNSDomainSuffixes: []string{".bsky.social"}, 104 + } 105 + dir, err := NewRedisDirectory(&base, redisLocalTestURL, time.Hour*1, time.Hour*1, time.Hour*1, 1000) 106 + if err != nil { 107 + t.Fatal(err) 108 + } 109 + // All 60 routines launch at the same time, so they should all miss the cache initially 110 + routines := 60 111 + wg := sync.WaitGroup{} 112 + 113 + // Cancel the context after 2 seconds, if we're coalescing correctly, we should only make 1 request 114 + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) 115 + defer cancel() 116 + for i := 0; i < routines; i++ { 117 + wg.Add(1) 118 + go func() { 119 + defer wg.Done() 120 + ident, err := dir.LookupDID(ctx, did) 121 + if err != nil { 122 + slog.Error("Failed lookup", "error", err) 123 + } 124 + assert.NoError(err) 125 + assert.Equal(handle, ident.Handle) 126 + 127 + ident, err = dir.LookupHandle(ctx, handle) 128 + if err != nil { 129 + slog.Error("Failed lookup", "error", err) 130 + } 131 + assert.NoError(err) 132 + assert.Equal(did, ident.DID) 133 + }() 134 + } 135 + wg.Wait() 136 + }
+125 -66
atproto/identity/redisdir/redis_directory.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "errors" 5 6 "fmt" 6 7 "sync" 7 8 "time" ··· 20 21 // 21 22 // Includes an in-process LRU cache as well (provided by the redis client library), for hot key (identities). 22 23 type RedisDirectory struct { 23 - Inner identity.Directory 24 - ErrTTL time.Duration 25 - HitTTL time.Duration 24 + Inner identity.Directory 25 + ErrTTL time.Duration 26 + HitTTL time.Duration 27 + InvalidHandleTTL time.Duration 26 28 27 29 handleCache *cache.Cache 28 30 identityCache *cache.Cache ··· 32 34 33 35 type handleEntry struct { 34 36 Updated time.Time 35 - DID syntax.DID 36 - Err error 37 + // needs to be pointer type, because unmarshalling empty string would be an error 38 + DID *syntax.DID 39 + Err error 37 40 } 38 41 39 42 type identityEntry struct { ··· 49 52 // `redisURL` contains all the redis connection config options. 50 53 // `hitTTL` and `errTTL` define how long successful and errored identity metadata should be cached (respectively). errTTL is expected to be shorted than hitTTL. 51 54 // `lruSize` is the size of the in-process cache, for each of the handle and identity caches. 10000 is a reasonable default. 52 - func NewRedisDirectory(inner identity.Directory, redisURL string, hitTTL, errTTL time.Duration, lruSize int) (*RedisDirectory, error) { 55 + // 56 + // NOTE: Errors returned may be inconsistent with the base directory, or between calls. This is because cached errors are serialized/deserialized and that may break equality checks. 57 + func NewRedisDirectory(inner identity.Directory, redisURL string, hitTTL, errTTL, invalidHandleTTL time.Duration, lruSize int) (*RedisDirectory, error) { 53 58 opt, err := redis.ParseURL(redisURL) 54 59 if err != nil { 55 60 return nil, err ··· 69 74 LocalCache: cache.NewTinyLFU(lruSize, hitTTL), 70 75 }) 71 76 return &RedisDirectory{ 72 - Inner: inner, 73 - ErrTTL: errTTL, 74 - HitTTL: hitTTL, 75 - handleCache: handleCache, 76 - identityCache: identityCache, 77 + Inner: inner, 78 + ErrTTL: errTTL, 79 + HitTTL: hitTTL, 80 + InvalidHandleTTL: invalidHandleTTL, 81 + handleCache: handleCache, 82 + identityCache: identityCache, 77 83 }, nil 78 84 } 79 85 ··· 88 94 if e.Err != nil && time.Since(e.Updated) > d.ErrTTL { 89 95 return true 90 96 } 97 + if e.Identity != nil && e.Identity.Handle.IsInvalidHandle() && time.Since(e.Updated) > d.InvalidHandleTTL { 98 + return true 99 + } 91 100 return false 92 101 } 93 102 94 - func (d *RedisDirectory) updateHandle(ctx context.Context, h syntax.Handle) (*handleEntry, error) { 103 + func (d *RedisDirectory) updateHandle(ctx context.Context, h syntax.Handle) handleEntry { 95 104 h = h.Normalize() 96 105 ident, err := d.Inner.LookupHandle(ctx, h) 97 106 if err != nil { 98 107 he := handleEntry{ 99 108 Updated: time.Now(), 100 - DID: "", 109 + DID: nil, 101 110 Err: err, 102 111 } 103 112 err = d.handleCache.Set(&cache.Item{ ··· 107 116 TTL: d.ErrTTL, 108 117 }) 109 118 if err != nil { 110 - return nil, err 119 + he.DID = nil 120 + he.Err = fmt.Errorf("identity cache write: %w", err) 121 + return he 111 122 } 112 - return &he, nil 123 + return he 113 124 } 114 125 115 126 entry := identityEntry{ ··· 119 130 } 120 131 he := handleEntry{ 121 132 Updated: time.Now(), 122 - DID: ident.DID, 133 + DID: &ident.DID, 123 134 Err: nil, 124 135 } 125 136 ··· 130 141 TTL: d.HitTTL, 131 142 }) 132 143 if err != nil { 133 - return nil, err 144 + he.DID = nil 145 + he.Err = fmt.Errorf("identity cache write: %w", err) 146 + return he 134 147 } 135 148 err = d.handleCache.Set(&cache.Item{ 136 149 Ctx: ctx, ··· 139 152 TTL: d.HitTTL, 140 153 }) 141 154 if err != nil { 142 - return nil, err 155 + he.DID = nil 156 + he.Err = fmt.Errorf("identity cache write: %w", err) 157 + return he 143 158 } 144 - return &he, nil 159 + return he 145 160 } 146 161 147 162 func (d *RedisDirectory) ResolveHandle(ctx context.Context, h syntax.Handle) (syntax.DID, error) { 163 + if h.IsInvalidHandle() { 164 + return "", errors.New("invalid handle") 165 + } 148 166 var entry handleEntry 149 167 err := d.handleCache.Get(ctx, redisDirPrefix+h.String(), &entry) 150 168 if err != nil && err != cache.ErrCacheMiss { 151 - return "", err 169 + return "", fmt.Errorf("identity cache read: %w", err) 152 170 } 153 - if err != cache.ErrCacheMiss && !d.isHandleStale(&entry) { 171 + if err == nil && !d.isHandleStale(&entry) { // if no error... 154 172 handleCacheHits.Inc() 155 - return entry.DID, entry.Err 173 + if entry.Err != nil { 174 + return "", entry.Err 175 + } else if entry.DID != nil { 176 + return *entry.DID, nil 177 + } else { 178 + return "", errors.New("code flow error in redis identity directory") 179 + } 156 180 } 157 181 handleCacheMisses.Inc() 158 182 ··· 167 191 // The result should now be in the cache 168 192 err := d.handleCache.Get(ctx, redisDirPrefix+h.String(), entry) 169 193 if err != nil && err != cache.ErrCacheMiss { 170 - return "", err 194 + return "", fmt.Errorf("identity cache read: %w", err) 171 195 } 172 - if err != cache.ErrCacheMiss && !d.isHandleStale(&entry) { 173 - return entry.DID, entry.Err 196 + if err == nil && !d.isHandleStale(&entry) { // if no error... 197 + if entry.Err != nil { 198 + return "", entry.Err 199 + } else if entry.DID != nil { 200 + return *entry.DID, nil 201 + } else { 202 + return "", errors.New("code flow error in redis identity directory") 203 + } 174 204 } 175 - return "", fmt.Errorf("identity not found in cache after coalesce returned") 205 + return "", errors.New("identity not found in cache after coalesce returned") 176 206 case <-ctx.Done(): 177 207 return "", ctx.Err() 178 208 } 179 209 } 180 210 181 - var did syntax.DID 182 211 // Update the Handle Entry from PLC and cache the result 183 - newEntry, err := d.updateHandle(ctx, h) 184 - if err == nil && newEntry != nil { 185 - did = newEntry.DID 186 - } 212 + newEntry := d.updateHandle(ctx, h) 213 + 187 214 // Cleanup the coalesce map and close the results channel 188 215 d.handleLookupChans.Delete(h.String()) 189 216 // Callers waiting will now get the result from the cache 190 217 close(res) 191 218 192 - return did, err 219 + if newEntry.Err != nil { 220 + return "", newEntry.Err 221 + } 222 + if newEntry.DID != nil { 223 + return *newEntry.DID, nil 224 + } 225 + return "", errors.New("unexpected control-flow error") 193 226 } 194 227 195 - func (d *RedisDirectory) updateDID(ctx context.Context, did syntax.DID) (*identityEntry, error) { 228 + func (d *RedisDirectory) updateDID(ctx context.Context, did syntax.DID) identityEntry { 196 229 ident, err := d.Inner.LookupDID(ctx, did) 197 230 // persist the identity lookup error, instead of processing it immediately 198 231 entry := identityEntry{ ··· 202 235 } 203 236 var he *handleEntry 204 237 // if *not* an error, then also update the handle cache 205 - if nil == err && !ident.Handle.IsInvalidHandle() { 238 + if err == nil && !ident.Handle.IsInvalidHandle() { 206 239 he = &handleEntry{ 207 240 Updated: time.Now(), 208 - DID: did, 241 + DID: &did, 209 242 Err: nil, 210 243 } 211 244 } ··· 217 250 TTL: d.HitTTL, 218 251 }) 219 252 if err != nil { 220 - return nil, err 253 + entry.Identity = nil 254 + entry.Err = fmt.Errorf("identity cache write: %v", err) 255 + return entry 221 256 } 222 257 if he != nil { 223 258 err = d.handleCache.Set(&cache.Item{ ··· 227 262 TTL: d.HitTTL, 228 263 }) 229 264 if err != nil { 230 - return nil, err 265 + entry.Identity = nil 266 + entry.Err = fmt.Errorf("identity cache write: %v", err) 267 + return entry 231 268 } 232 269 } 233 - return &entry, nil 270 + return entry 234 271 } 235 272 236 273 func (d *RedisDirectory) LookupDID(ctx context.Context, did syntax.DID) (*identity.Identity, error) { 274 + id, _, err := d.LookupDIDWithCacheState(ctx, did) 275 + return id, err 276 + } 277 + 278 + func (d *RedisDirectory) LookupDIDWithCacheState(ctx context.Context, did syntax.DID) (*identity.Identity, bool, error) { 237 279 var entry identityEntry 238 280 err := d.identityCache.Get(ctx, redisDirPrefix+did.String(), &entry) 239 281 if err != nil && err != cache.ErrCacheMiss { 240 - return nil, err 282 + return nil, false, fmt.Errorf("identity cache read: %v", err) 241 283 } 242 - if err != cache.ErrCacheMiss && !d.isIdentityStale(&entry) { 284 + if err == nil && !d.isIdentityStale(&entry) { // if no error... 243 285 identityCacheHits.Inc() 244 - return entry.Identity, entry.Err 286 + return entry.Identity, true, entry.Err 245 287 } 246 288 identityCacheMisses.Inc() 247 289 ··· 256 298 // The result should now be in the cache 257 299 err = d.identityCache.Get(ctx, redisDirPrefix+did.String(), &entry) 258 300 if err != nil && err != cache.ErrCacheMiss { 259 - return nil, err 301 + return nil, false, fmt.Errorf("identity cache read: %v", err) 260 302 } 261 - if err != cache.ErrCacheMiss && !d.isIdentityStale(&entry) { 262 - return entry.Identity, entry.Err 303 + if err == nil && !d.isIdentityStale(&entry) { // if no error... 304 + return entry.Identity, false, entry.Err 263 305 } 264 - return nil, fmt.Errorf("identity not found in cache after coalesce returned") 306 + return nil, false, errors.New("identity not found in cache after coalesce returned") 265 307 case <-ctx.Done(): 266 - return nil, ctx.Err() 308 + return nil, false, ctx.Err() 267 309 } 268 310 } 269 311 270 - var doc *identity.Identity 271 312 // Update the Identity Entry from PLC and cache the result 272 - newEntry, err := d.updateDID(ctx, did) 273 - if err == nil && newEntry != nil { 274 - doc = newEntry.Identity 275 - } 313 + newEntry := d.updateDID(ctx, did) 314 + 276 315 // Cleanup the coalesce map and close the results channel 277 316 d.didLookupChans.Delete(did.String()) 278 317 // Callers waiting will now get the result from the cache 279 318 close(res) 280 319 281 - return doc, err 320 + if newEntry.Err != nil { 321 + return nil, false, newEntry.Err 322 + } 323 + if newEntry.Identity != nil { 324 + return newEntry.Identity, false, nil 325 + } 326 + return nil, false, errors.New("unexpected control-flow error") 282 327 } 283 328 284 329 func (d *RedisDirectory) LookupHandle(ctx context.Context, h syntax.Handle) (*identity.Identity, error) { 330 + ident, _, err := d.LookupHandleWithCacheState(ctx, h) 331 + return ident, err 332 + } 333 + 334 + func (d *RedisDirectory) LookupHandleWithCacheState(ctx context.Context, h syntax.Handle) (*identity.Identity, bool, error) { 335 + h = h.Normalize() 285 336 did, err := d.ResolveHandle(ctx, h) 286 337 if err != nil { 287 - return nil, err 338 + return nil, false, err 288 339 } 289 - ident, err := d.LookupDID(ctx, did) 340 + ident, hit, err := d.LookupDIDWithCacheState(ctx, did) 290 341 if err != nil { 291 - return nil, err 342 + return nil, hit, err 292 343 } 293 344 294 345 declared, err := ident.DeclaredHandle() 295 346 if err != nil { 296 - return nil, err 347 + return nil, hit, err 297 348 } 298 349 if declared != h { 299 - return nil, fmt.Errorf("handle does not match that declared in DID document") 350 + return nil, hit, identity.ErrHandleMismatch 300 351 } 301 - return ident, nil 352 + return ident, hit, nil 302 353 } 303 354 304 355 func (d *RedisDirectory) Lookup(ctx context.Context, a syntax.AtIdentifier) (*identity.Identity, error) { 305 356 handle, err := a.AsHandle() 306 - if nil == err { // if not an error, is a handle 357 + if err == nil { // if not an error, is a handle 307 358 return d.LookupHandle(ctx, handle) 308 359 } 309 360 did, err := a.AsDID() 310 - if nil == err { // if not an error, is a DID 361 + if err == nil { // if not an error, is a DID 311 362 return d.LookupDID(ctx, did) 312 363 } 313 - return nil, fmt.Errorf("at-identifier neither a Handle nor a DID") 364 + return nil, errors.New("at-identifier neither a Handle nor a DID") 314 365 } 315 366 316 367 func (d *RedisDirectory) Purge(ctx context.Context, a syntax.AtIdentifier) error { 317 368 handle, err := a.AsHandle() 318 - if nil == err { // if not an error, is a handle 369 + if err == nil { // if not an error, is a handle 319 370 handle = handle.Normalize() 320 - return d.handleCache.Delete(ctx, handle.String()) 371 + err = d.handleCache.Delete(ctx, redisDirPrefix+handle.String()) 372 + if err == cache.ErrCacheMiss { 373 + return nil 374 + } 375 + return err 321 376 } 322 377 did, err := a.AsDID() 323 - if nil == err { // if not an error, is a DID 324 - return d.identityCache.Delete(ctx, did.String()) 378 + if err == nil { // if not an error, is a DID 379 + err = d.identityCache.Delete(ctx, redisDirPrefix+did.String()) 380 + if err == cache.ErrCacheMiss { 381 + return nil 382 + } 383 + return err 325 384 } 326 - return fmt.Errorf("at-identifier neither a Handle nor a DID") 385 + return errors.New("at-identifier neither a Handle nor a DID") 327 386 }
+1 -1
cmd/hepa/main.go
··· 172 172 } 173 173 var dir identity.Directory 174 174 if cctx.String("redis-url") != "" { 175 - rdir, err := redisdir.NewRedisDirectory(&baseDir, cctx.String("redis-url"), time.Hour*24, time.Minute*2, 10_000) 175 + rdir, err := redisdir.NewRedisDirectory(&baseDir, cctx.String("redis-url"), time.Hour*24, time.Minute*2, time.Minute*5, 10_000) 176 176 if err != nil { 177 177 return nil, err 178 178 }