AlinsRan commented on code in PR #14061:
URL: https://github.com/apache/apisix/pull/14061#discussion_r4346869674


##########
apisix/plugins/oas-validator.lua:
##########
@@ -234,11 +281,16 @@ local function get_validator(conf)
         end
         cache_key = cache_key .. "#" .. ngx_md5(tab_concat(parts, "&"))
     end
-    local validator, err = lrucache(cache_key, nil, fetch_and_compile, conf)
-    if not validator then
+
+    -- Key by the declaring resource with the URL key as version, so a changed
+    -- spec_url replaces the resource's slot instead of adding another one.
+    local entry, err = lrucache(conf._meta.parent.resource_key, cache_key,
+                                create_spec_url_entry, conf, cache_key,
+                                spec_url_lrucache_ttl)

Review Comment:
   资源首次复用共享文档时,会获得一个完整的 TTL,而不是文档剩余的有效时间。后续请求命中该资源的缓存槽时,也不会再检查 
`fetched_at`,因此文档过期后仍可能继续使用,且不触发后台刷新。
   
   以 TTL 为 1 秒为例:
   
   - 第 0 秒:A 加载文档,文档应在第 1 秒过期。
   - 第 0.65 秒:B 复用文档,但其缓存槽要到第 1.65 秒才过期。
   - 第 1.2 秒:访问 B,仍命中缓存,不会触发刷新。
   
   复现中,此时当前实现只拉取过一次,base 版本已经重新拉取。校验规则的更新因此可能额外延迟接近一个完整 TTL,默认配置下接近一小时。



##########
apisix/plugins/oas-validator.lua:
##########
@@ -176,6 +184,45 @@ local function fetch_and_compile(conf)
     return validator
 end
 
+local function get_fresh_shared_entry(cache_key, ttl)
+    local entry = shared_spec_url_entries[cache_key]
+    if entry and ngx_now() - entry.fetched_at < ttl then
+        return entry
+    end
+end
+
+local function create_spec_url_entry(conf, cache_key, ttl)
+    local entry = get_fresh_shared_entry(cache_key, ttl)
+    if entry then
+        return entry
+    end
+
+    -- spec_url_lrucache only serialises per resource, so resources sharing a
+    -- cold URL would otherwise each fetch and keep their own compiled copy
+    local lock, err = resty_lock:new("lrucache-lock")
+    if not lock then
+        return nil, "failed to create lock: " .. err
+    end
+
+    local elapsed, lock_err = lock:lock(plugin_name .. "#" .. cache_key)
+    if not elapsed then
+        return nil, "failed to acquire the lock: " .. lock_err
+    end
+
+    entry = get_fresh_shared_entry(cache_key, ttl)
+    if not entry then
+        local validator
+        validator, err = fetch_and_compile(conf)
+        if validator then
+            entry = {validator = validator, fetched_at = ngx_now()}
+            shared_spec_url_entries[cache_key] = entry

Review Comment:
   刷新共享文档时,这里会创建新的 `entry`,但只更新当前资源的缓存槽。其他资源仍持有旧 
`entry`,因此弱引用表无法释放旧的编译对象。如果这些资源不再收到请求,旧对象会一直保留到缓存淘汰。
   
   复现结果:一个路由持续刷新,另外三个路由分别持有历史版本。三轮刷新并执行完整 GC 后,当前实现保留了 **四份编译对象**,而 base 
版本只保留一份。
   
   这意味着,即使所有资源都使用同一个 URL,也可能积累多份编译对象。对于大型文档,这会显著增加 worker 内存占用。



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to