@@ -43,8 +43,11 @@ type instancePCNode struct {
4343 value * PlanCacheValue
4444 lastUsed atomic.Time
4545 next atomic.Pointer [instancePCNode ]
46+ deleted atomic.Bool
4647}
4748
49+ var deletedInstancePCNode = & instancePCNode {}
50+
4851// instancePlanCache is a lock-free implementation of InstancePlanCache interface.
4952// [key1] --> [headNode1] --> [node1] --> [node2] --> [node3]
5053// [key2] --> [headNode2] --> [node4] --> [node5]
@@ -62,32 +65,45 @@ type instancePlanCache struct {
6265}
6366
6467func (pc * instancePlanCache ) getHead (key string , create bool ) * instancePCNode {
65- headNode , ok := pc .heads .Load (key )
66- if ok { // cache hit
67- return headNode .(* instancePCNode )
68- }
69- if ! create { // cache miss
70- return nil
71- }
72- newHeadNode := pc .createNode (nil )
73- actual , _ := pc .heads .LoadOrStore (key , newHeadNode )
74- if headNode , ok := actual .(* instancePCNode ); ok { // for safety
75- return headNode
68+ for {
69+ headNode , ok := pc .heads .Load (key )
70+ if ok { // cache hit
71+ head := headNode .(* instancePCNode )
72+ if ! head .deleted .Load () {
73+ return head
74+ }
75+ if ! create {
76+ return nil
77+ }
78+ pc .heads .CompareAndDelete (key , headNode )
79+ continue
80+ }
81+ if ! create { // cache miss
82+ return nil
83+ }
84+ newHeadNode := pc .createNode (nil )
85+ actual , loaded := pc .heads .LoadOrStore (key , newHeadNode )
86+ if ! loaded {
87+ return newHeadNode
88+ }
89+ if headNode , ok := actual .(* instancePCNode ); ok && ! headNode .deleted .Load () { // for safety
90+ return headNode
91+ }
92+ pc .heads .CompareAndDelete (key , actual )
7693 }
77- return nil
7894}
7995
8096// Get gets the cached value according to key and paramTypes.
8197func (pc * instancePlanCache ) Get (key string , paramTypes any ) (value any , ok bool ) {
8298 headNode := pc .getHead (key , false )
83- if headNode == nil { // cache miss
99+ if headNode == nil || headNode . deleted . Load () { // cache miss
84100 return nil , false
85101 }
86102 return pc .getPlanFromList (headNode , paramTypes )
87103}
88104
89105func (pc * instancePlanCache ) getPlanFromList (headNode * instancePCNode , paramTypes any ) (any , bool ) {
90- for node := headNode .next .Load (); node != nil ; node = node .next .Load () {
106+ for node := headNode .next .Load (); node != nil && node != deletedInstancePCNode ; node = node .next .Load () {
91107 if checkTypesCompatibility4PC (node .value .ParamTypes , paramTypes ) { // v.Plan is read-only, no need to lock
92108 if ! pc .inEvict .Load () {
93109 node .lastUsed .Store (time .Now ()) // atomically update the lastUsed field
@@ -109,17 +125,20 @@ func (pc *instancePlanCache) Put(key string, value, paramTypes any) (succ bool)
109125 return // do nothing if it exceeds the hard limit
110126 }
111127 headNode := pc .getHead (key , true )
112- if headNode == nil {
128+ if headNode == nil || headNode . deleted . Load () {
113129 return false // for safety
114130 }
115131 if _ , ok := pc .getPlanFromList (headNode , paramTypes ); ok {
116132 return // some other thread has inserted the same plan before
117133 }
118- if pc .inEvict .Load () {
134+ if pc .inEvict .Load () || headNode . deleted . Load () {
119135 return // do nothing if eviction is in progress
120136 }
121137
122138 firstNode := headNode .next .Load ()
139+ if firstNode == deletedInstancePCNode {
140+ return
141+ }
123142 currNode := pc .createNode (value )
124143 currNode .next .Store (firstNode )
125144 if headNode .next .CompareAndSwap (firstNode , currNode ) { // if failed, some other thread has updated this node,
@@ -130,6 +149,28 @@ func (pc *instancePlanCache) Put(key string, value, paramTypes any) (succ bool)
130149 return
131150}
132151
152+ // Delete removes all cached values under the exact cache key.
153+ func (pc * instancePlanCache ) Delete (key string ) (numDeleted int ) {
154+ pc .evictMutex .Lock () // serialize against Evict and key deletion for safety
155+ defer pc .evictMutex .Unlock ()
156+ pc .inEvict .Store (true )
157+ defer pc .inEvict .Store (false )
158+
159+ headNode := pc .getHead (key , false )
160+ if headNode == nil {
161+ return 0
162+ }
163+ headNode .deleted .Store (true )
164+ firstNode := headNode .next .Swap (deletedInstancePCNode )
165+ pc .heads .Delete (key )
166+ for node := firstNode ; node != nil && node != deletedInstancePCNode ; node = node .next .Load () {
167+ pc .totCost .Sub (node .value .MemoryUsage ())
168+ pc .totPlan .Sub (1 )
169+ numDeleted ++
170+ }
171+ return
172+ }
173+
133174// All returns all cached values.
134175// All returned values are read-only, don't modify them.
135176func (pc * instancePlanCache ) All () (values []any ) {
@@ -226,7 +267,7 @@ func (pc *instancePlanCache) calcEvictionThreshold(lastUsedTimes []time.Time) (t
226267func (pc * instancePlanCache ) foreach (callback func (prev , this * instancePCNode ) (thisRemoved bool )) {
227268 _ , headNodes := pc .headNodes ()
228269 for _ , headNode := range headNodes {
229- for prev , this := headNode , headNode .next .Load (); this != nil ; {
270+ for prev , this := headNode , headNode .next .Load (); this != nil && this != deletedInstancePCNode ; {
230271 thisRemoved := callback (prev , this )
231272 if ! thisRemoved { // this node is removed, no need to update the prev node in this case
232273 prev , this = this , this .next .Load ()
0 commit comments