44package cache
55
66import (
7- // Note: AX-6 — no core equivalent for SHA-256 hashing.
8- "crypto/sha256"
9- // Note: AX-6 — no core equivalent for URL-safe base64 encoding.
10- "encoding/base64"
11- // Note: AX-6 — no core equivalent for hex encoding.
12- "encoding/hex"
137 // Note: AX-6 — structural: coreio.Medium surfaces fs.ErrNotExist/fs.DirEntry, and Lstat symlink checks use fs.ModeSymlink.
148 "io/fs"
159 // Note: AX-6 — intrinsic: coreio.Medium has no no-follow Lstat primitive or dynamic cwd lookup.
1610 "os"
1711 "slices"
18- // Note: AX-6 — core.RWMutex is not available in the pinned core module.
19- "sync"
2012 // Note: AX-6 — no core equivalent for durations or wall-clock timestamps.
2113 "time"
2214
@@ -51,8 +43,9 @@ type Cache struct {
5143 baseDir string
5244 cacheTTL time.Duration
5345 invalidation map [string ][]InvalidateFunc
54- mu sync.RWMutex
55- entryMu sync.RWMutex
46+ runtime * core.Core
47+ // Backing-store operations intentionally have no mutex; callers must
48+ // synchronize concurrent writes to the same key.
5649}
5750
5851// Entry is the serialized cache record written to the backing Medium.
@@ -90,7 +83,7 @@ func marshalPrettyJSON(value any) (string, error) {
9083 if ! result .OK {
9184 return "" , result .Value .(error )
9285 }
93- return indentJSON (result . Value .( []byte )), nil
86+ return indentJSON ([]byte ( core . JSONMarshalString ( value ) )), nil
9487}
9588
9689func indentJSON (data []byte ) string {
@@ -243,6 +236,7 @@ func New(medium coreio.Medium, baseDir string, cacheTTL time.Duration) (*Cache,
243236 baseDir : baseDir ,
244237 cacheTTL : cacheTTL ,
245238 invalidation : make (map [string ][]InvalidateFunc ),
239+ runtime : core .New (),
246240 }, nil
247241}
248242
@@ -295,9 +289,6 @@ func (cache *Cache) Get(key string, dest any) (bool, error) {
295289 return false , err
296290 }
297291
298- cache .entryMu .RLock ()
299- defer cache .entryMu .RUnlock ()
300-
301292 path , err := cache .Path (key )
302293 if err != nil {
303294 return false , err
@@ -355,9 +346,6 @@ func (cache *Cache) set(key string, data any, ttl time.Duration, useDefaultTTL b
355346 return err
356347 }
357348
358- cache .entryMu .Lock ()
359- defer cache .entryMu .Unlock ()
360-
361349 path , _ , err := cache .entryPaths (key )
362350 if err != nil {
363351 return err
@@ -423,8 +411,6 @@ func (cache *Cache) removeEntryFiles(key string) (bool, error) {
423411 if err := cache .ensureReady ("cache.removeEntryFiles" ); err != nil {
424412 return false , err
425413 }
426- cache .entryMu .Lock ()
427- defer cache .entryMu .Unlock ()
428414
429415 jsonPath , binaryPath , err := cache .entryPaths (key )
430416 if err != nil {
@@ -477,8 +463,6 @@ func (cache *Cache) setBinary(key string, data []byte, contentType string, ttl t
477463 if err := cache .ensureReady ("cache.setBinary" ); err != nil {
478464 return err
479465 }
480- cache .entryMu .Lock ()
481- defer cache .entryMu .Unlock ()
482466
483467 jsonPath , binaryPath , err := cache .entryPaths (key )
484468 if err != nil {
@@ -540,8 +524,6 @@ func (cache *Cache) GetBinary(key string) ([]byte, bool, error) {
540524 if err := cache .ensureReady ("cache.GetBinary" ); err != nil {
541525 return nil , false , err
542526 }
543- cache .entryMu .RLock ()
544- defer cache .entryMu .RUnlock ()
545527
546528 metaPath , binaryPath , err := cache .entryPaths (key )
547529 if err != nil {
@@ -586,9 +568,6 @@ func (cache *Cache) DeleteMany(keys ...string) error {
586568 return err
587569 }
588570
589- cache .entryMu .Lock ()
590- defer cache .entryMu .Unlock ()
591-
592571 type entryFileSet struct {
593572 jsonPath string
594573 binaryPath string
@@ -667,9 +646,6 @@ func (cache *Cache) keysByPattern(pattern string) ([]string, error) {
667646 return nil , err
668647 }
669648
670- cache .entryMu .RLock ()
671- defer cache .entryMu .RUnlock ()
672-
673649 allKeys , err := cache .listJSONKeys ()
674650 if err != nil {
675651 return nil , err
@@ -813,8 +789,12 @@ func (cache *Cache) OnInvalidate(trigger string, fn InvalidateFunc) {
813789 if fn == nil {
814790 return
815791 }
816- cache .mu .Lock ()
817- defer cache .mu .Unlock ()
792+ lock := cache .runtime .Lock ("cache" )
793+ lock .Mutex .Lock ()
794+ defer lock .Mutex .Unlock ()
795+ if cache .invalidation == nil {
796+ cache .invalidation = make (map [string ][]InvalidateFunc )
797+ }
818798 cache .invalidation [trigger ] = append (cache .invalidation [trigger ], fn )
819799}
820800
@@ -826,9 +806,10 @@ func (cache *Cache) Invalidate(trigger string) (int, error) {
826806 return 0 , err
827807 }
828808
829- cache .mu .RLock ()
809+ lock := cache .runtime .Lock ("cache" )
810+ lock .Mutex .RLock ()
830811 callbacks := append ([]InvalidateFunc (nil ), cache .invalidation [trigger ]... )
831- cache . mu .RUnlock ()
812+ lock . Mutex .RUnlock ()
832813 total := 0
833814 for _ , callback := range callbacks {
834815 for _ , pattern := range callback (trigger ) {
@@ -1016,9 +997,7 @@ type ScopedCache struct {
1016997}
1017998
1018999func scopePrefix (origin string ) string {
1019- sum := sha256 .Sum256 ([]byte (origin ))
1020- hash := hex .EncodeToString (sum [:])
1021- return "scope_" + hash
1000+ return "scope_" + core .SHA256Hex ([]byte (origin ))
10221001}
10231002
10241003func (scopedCache * ScopedCache ) fullKey (key string ) string {
@@ -1180,7 +1159,7 @@ type CacheStorage struct {
11801159 medium coreio.Medium
11811160 baseDir string
11821161 caches map [string ]* HTTPCache
1183- mu sync. RWMutex
1162+ runtime * core. Core
11841163}
11851164
11861165// NewCacheStorage creates a namespace container for HTTPCache instances.
@@ -1209,6 +1188,7 @@ func NewCacheStorage(medium coreio.Medium, baseDir string) (*CacheStorage, error
12091188 medium : medium ,
12101189 baseDir : baseDir ,
12111190 caches : make (map [string ]* HTTPCache ),
1191+ runtime : core .New (),
12121192 }, nil
12131193}
12141194
@@ -1224,8 +1204,9 @@ func (storage *CacheStorage) Open(name string) (*HTTPCache, error) {
12241204 return nil , err
12251205 }
12261206
1227- storage .mu .Lock ()
1228- defer storage .mu .Unlock ()
1207+ lock := storage .runtime .Lock ("cache-storage" )
1208+ lock .Mutex .Lock ()
1209+ defer lock .Mutex .Unlock ()
12291210 if httpCache , ok := storage .caches [name ]; ok {
12301211 return httpCache , nil
12311212 }
@@ -1256,8 +1237,9 @@ func (storage *CacheStorage) Delete(name string) error {
12561237 return err
12571238 }
12581239
1259- storage .mu .Lock ()
1260- defer storage .mu .Unlock ()
1240+ lock := storage .runtime .Lock ("cache-storage" )
1241+ lock .Mutex .Lock ()
1242+ defer lock .Mutex .Unlock ()
12611243 if err := storage .medium .DeleteAll (core .JoinPath (storage .baseDir , name )); err != nil && ! core .Is (err , fs .ErrNotExist ) {
12621244 return core .E ("cache.CacheStorage.Delete" , "failed to delete cache directory" , err )
12631245 }
@@ -1295,12 +1277,13 @@ func (storage *CacheStorage) Keys() ([]string, error) {
12951277 return nil , err
12961278 }
12971279
1298- storage .mu .RLock ()
1280+ lock := storage .runtime .Lock ("cache-storage" )
1281+ lock .Mutex .RLock ()
12991282 names := make (map [string ]struct {}, len (storage .caches ))
13001283 for name := range storage .caches {
13011284 names [name ] = struct {}{}
13021285 }
1303- storage . mu .RUnlock ()
1286+ lock . Mutex .RUnlock ()
13041287
13051288 entries , err := storage .medium .List (storage .baseDir )
13061289 if err != nil {
@@ -1331,9 +1314,14 @@ func (storage *CacheStorage) Close() error {
13311314 if storage == nil {
13321315 return nil
13331316 }
1334- storage .mu .Lock ()
1317+ if storage .runtime == nil {
1318+ storage .caches = make (map [string ]* HTTPCache )
1319+ return nil
1320+ }
1321+ lock := storage .runtime .Lock ("cache-storage" )
1322+ lock .Mutex .Lock ()
1323+ defer lock .Mutex .Unlock ()
13351324 storage .caches = make (map [string ]* HTTPCache )
1336- storage .mu .Unlock ()
13371325 return nil
13381326}
13391327
@@ -1358,11 +1346,15 @@ func (storage *CacheStorage) ensureReady(op string) error {
13581346 if storage .baseDir == "" {
13591347 return core .E (op , "cache storage base directory is empty; construct via cache.NewCacheStorage" , nil )
13601348 }
1361- storage .mu .Lock ()
1349+ if storage .runtime == nil {
1350+ return core .E (op , "cache storage runtime is nil; construct via cache.NewCacheStorage" , nil )
1351+ }
1352+ lock := storage .runtime .Lock ("cache-storage" )
1353+ lock .Mutex .Lock ()
1354+ defer lock .Mutex .Unlock ()
13621355 if storage .caches == nil {
13631356 storage .caches = make (map [string ]* HTTPCache )
13641357 }
1365- storage .mu .Unlock ()
13661358 return nil
13671359}
13681360
@@ -1421,11 +1413,99 @@ func (httpCache *HTTPCache) requestKey(req CachedRequest) (string, error) {
14211413}
14221414
14231415func legacyRequestKey (req CachedRequest ) string {
1424- return base64 .RawURLEncoding .EncodeToString ([]byte (req .Method + "\x00 " + req .URL ))
1416+ return rawBase64URLEncode ([]byte (req .Method + "\x00 " + req .URL ))
1417+ }
1418+
1419+ func rawBase64URLEncode (data []byte ) string {
1420+ if len (data ) == 0 {
1421+ return ""
1422+ }
1423+
1424+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
1425+ builder := core .NewBuilder ()
1426+
1427+ i := 0
1428+ for ; i + 3 <= len (data ); i += 3 {
1429+ n := uint (data [i ])<< 16 | uint (data [i + 1 ])<< 8 | uint (data [i + 2 ])
1430+ builder .WriteByte (alphabet [(n >> 18 )& 0x3f ])
1431+ builder .WriteByte (alphabet [(n >> 12 )& 0x3f ])
1432+ builder .WriteByte (alphabet [(n >> 6 )& 0x3f ])
1433+ builder .WriteByte (alphabet [n & 0x3f ])
1434+ }
1435+
1436+ switch len (data ) - i {
1437+ case 1 :
1438+ n := uint (data [i ]) << 16
1439+ builder .WriteByte (alphabet [(n >> 18 )& 0x3f ])
1440+ builder .WriteByte (alphabet [(n >> 12 )& 0x3f ])
1441+ case 2 :
1442+ n := uint (data [i ])<< 16 | uint (data [i + 1 ])<< 8
1443+ builder .WriteByte (alphabet [(n >> 18 )& 0x3f ])
1444+ builder .WriteByte (alphabet [(n >> 12 )& 0x3f ])
1445+ builder .WriteByte (alphabet [(n >> 6 )& 0x3f ])
1446+ }
1447+
1448+ return builder .String ()
1449+ }
1450+
1451+ func rawBase64URLDecode (encoded string ) ([]byte , error ) {
1452+ if core .Contains (encoded , "=" ) {
1453+ return nil , core .E ("cache.rawBase64URLDecode" , "raw URL base64 must not contain padding" , nil )
1454+ }
1455+ if len (encoded )% 4 == 1 {
1456+ return nil , core .E ("cache.rawBase64URLDecode" , "invalid raw URL base64 length" , nil )
1457+ }
1458+
1459+ out := make ([]byte , 0 , len (encoded )* 3 / 4 )
1460+ for i := 0 ; i < len (encoded ); {
1461+ remaining := len (encoded ) - i
1462+ chunkLen := 4
1463+ if remaining < chunkLen {
1464+ chunkLen = remaining
1465+ }
1466+
1467+ var values [4 ]byte
1468+ for j := 0 ; j < chunkLen ; j ++ {
1469+ value := rawBase64URLDecodeValue (encoded [i + j ])
1470+ if value < 0 {
1471+ return nil , core .E ("cache.rawBase64URLDecode" , "invalid raw URL base64 character" , nil )
1472+ }
1473+ values [j ] = byte (value )
1474+ }
1475+
1476+ out = append (out , values [0 ]<< 2 | values [1 ]>> 4 )
1477+ if chunkLen >= 3 {
1478+ out = append (out , values [1 ]<< 4 | values [2 ]>> 2 )
1479+ }
1480+ if chunkLen == 4 {
1481+ out = append (out , values [2 ]<< 6 | values [3 ])
1482+ }
1483+
1484+ i += chunkLen
1485+ }
1486+
1487+ return out , nil
1488+ }
1489+
1490+ func rawBase64URLDecodeValue (c byte ) int {
1491+ switch {
1492+ case c >= 'A' && c <= 'Z' :
1493+ return int (c - 'A' )
1494+ case c >= 'a' && c <= 'z' :
1495+ return int (c - 'a' ) + 26
1496+ case c >= '0' && c <= '9' :
1497+ return int (c - '0' ) + 52
1498+ case c == '-' :
1499+ return 62
1500+ case c == '_' :
1501+ return 63
1502+ default :
1503+ return - 1
1504+ }
14251505}
14261506
14271507func decodeRequestKey (encoded string ) (CachedRequest , error ) {
1428- raw , err := base64 . RawURLEncoding . DecodeString (encoded )
1508+ raw , err := rawBase64URLDecode (encoded )
14291509 if err != nil {
14301510 return CachedRequest {}, core .E ("cache.decodeRequestKey" , "invalid cached request key" , err )
14311511 }
@@ -1658,8 +1738,7 @@ func requestStorageKey(req CachedRequest) (string, error) {
16581738 return "" , core .E ("cache.HTTPCache.requestStorageKey" , "invalid cached request" , err )
16591739 }
16601740
1661- sum := sha256 .Sum256 ([]byte (req .Method + "\x00 " + req .URL ))
1662- return hex .EncodeToString (sum [:]), nil
1741+ return core .SHA256Hex ([]byte (req .Method + "\x00 " + req .URL )), nil
16631742}
16641743
16651744func validateCachedRequest (req CachedRequest ) error {
@@ -1976,6 +2055,9 @@ func (c *Cache) ensureConfigured(op string) error {
19762055 if c .baseDir == "" {
19772056 return core .E (op , "cache base directory is empty; construct with cache.New" , nil )
19782057 }
2058+ if c .runtime == nil {
2059+ return core .E (op , "cache runtime is nil; construct with cache.New" , nil )
2060+ }
19792061
19802062 return nil
19812063}
0 commit comments