Clear cache on reload (#11673)

This commit is contained in:
Gregory Michael Travis 2024-12-10 13:51:26 -05:00 committed by GitHub
parent d79b4218fc
commit 9e00b9ddcc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 135 additions and 40 deletions

View File

@ -108,6 +108,7 @@
- [Added `Table.input` allowing creation of typed tables from vectors of data,
including auto parsing text columns.][11562]
- [Enhance Managed_Resource to allow implementation of in-memory caches][11577]
- [The reload button clears the HTTP cache.][11673]
[11235]: https://github.com/enso-org/enso/pull/11235
[11255]: https://github.com/enso-org/enso/pull/11255
@ -116,6 +117,7 @@
[11490]: https://github.com/enso-org/enso/pull/11490
[11562]: https://github.com/enso-org/enso/pull/11562
[11577]: https://github.com/enso-org/enso/pull/11577
[11673]: https://github.com/enso-org/enso/pull/11673
#### Enso Language & Runtime

View File

@ -0,0 +1,31 @@
import Standard.Base.Error.Error
import Standard.Base.Meta
import Standard.Base.Nothing.Nothing
import Standard.Base.Runtime.Managed_Resource.Managed_Resource
import Standard.Base.Runtime.Ref.Ref
from Standard.Base.Data.Boolean import Boolean, True, False
## PRIVATE
This is used by ReloadDetector.java to create a `Managed_Resource` that is
garbage collected when the reload button is pressed.
The managed resource contains a Ref containing a 0 (the value is
unimportant). When the reload button is pressed, the ref is removed and
attempting to access it using `with` throws an `Uninitialized_State`. When
the `Uninitialized_State` is detected, it indicates that the reload has been
initiated.
type Reload_Detector
private Value mr:Managed_Resource
new -> Reload_Detector =
mr = Managed_Resource.register (Ref.new 1) (x-> Nothing) True
Reload_Detector.Value mr
has_reload_occurred self =
self.mr.has_been_finalized
## PRIVATE
simulate_reload_test_only reload_detector = reload_detector.mr.finalize
## PRIVATE
create_reload_detector = Reload_Detector.new

View File

@ -1,8 +1,10 @@
## An API for manual resource management.
import project.Any.Any
import project.Errors.Common.Uninitialized_State
import project.Meta
import project.Nothing.Nothing
from project.Data.Boolean import Boolean, False
from project.Data.Boolean import Boolean, True, False
## Resource provides an API for manual management of computation resources.
@ -90,5 +92,15 @@ type Managed_Resource
take : Any
take self = @Builtin_Method "Managed_Resource.take"
## PRIVATE
ADVANCED
Returns true iff the resource has been collected by the engine, false
otherwise. If `with` throws any other error, it is propagated.
has_been_finalized : Boolean
has_been_finalized self -> Boolean = self.with x->
if x.is_error.not then False else
if x.catch.is_a Uninitialized_State then True else x
register_builtin r fn sys:Boolean = @Builtin_Method "Managed_Resource.register_builtin"
with_builtin r fn = @Builtin_Method "Managed_Resource.with_builtin"

View File

@ -11,6 +11,7 @@ import org.enso.interpreter.instrument.{
}
import org.enso.interpreter.instrument.execution.RuntimeContext
import org.enso.interpreter.instrument.job.{EnsureCompiledJob, ExecuteJob}
import org.enso.interpreter.runtime.EnsoContext
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.polyglot.runtime.Runtime.Api.RequestId
@ -42,6 +43,10 @@ class RecomputeContextCmd(
ec: ExecutionContext
): Future[Boolean] = {
Future {
EnsoContext
.get(null)
.getResourceManager()
.scheduleFinalizationOfSystemReferences();
ctx.jobControlPlane.abortJobs(
request.contextId,
"recompute context",

View File

@ -73,6 +73,9 @@ public class LRUCache<M> {
/** Used to get the current free disk space; mockable. */
private final DiskSpaceGetter diskSpaceGetter;
/** Used to clear the cache on reload. */
private final ReloadDetector reloadDetector = new ReloadDetector();
public LRUCache() {
this(LRUCacheSettings.getDefault(), new NowGetter(), new DiskSpaceGetter());
}
@ -89,6 +92,8 @@ public class LRUCache<M> {
*/
public CacheResult<M> getResult(ItemBuilder<M> itemBuilder)
throws IOException, InterruptedException, ResponseTooLargeException {
clearOnReload();
String cacheKey = itemBuilder.makeCacheKey();
try {
@ -221,6 +226,12 @@ public class LRUCache<M> {
removeCacheEntriesByPredicate(e -> true);
}
private void clearOnReload() {
if (reloadDetector.hasReloadOccurred()) {
clear();
}
}
/** Remove all cache entries (and their cache files) that match the predicate. */
private void removeCacheEntriesByPredicate(Predicate<CacheEntry<M>> predicate) {
List<Map.Entry<String, CacheEntry<M>>> toRemove =
@ -352,6 +363,11 @@ public class LRUCache<M> {
return settings;
}
/** Public for testing. */
public void simulateReloadTestOnly() {
reloadDetector.simulateReloadTestOnly();
}
private record CacheEntry<M>(File responseData, M metadata, long size, ZonedDateTime expiry) {}
/**

View File

@ -0,0 +1,39 @@
package org.enso.base.cache;
import org.enso.base.polyglot.EnsoMeta;
import org.graalvm.polyglot.Value;
/**
* Detects that the reload button has been pressed.
*
* <p>.hasReloadOccurred() returns true if the reload button was pressed since the last call to
* .hasReloadOccurred().
*
* <p>This uses a `Managed_Resource` (created in eval'd Enso code) that is cleared on reload.
*/
public class ReloadDetector {
private Value ensoReloadDetector;
public ReloadDetector() {
resetEnsoReloadDetector();
}
public boolean hasReloadOccurred() {
var reloadHasOccurred = ensoReloadDetector.invokeMember("has_reload_occurred").asBoolean();
if (reloadHasOccurred) {
resetEnsoReloadDetector();
}
return reloadHasOccurred;
}
private void resetEnsoReloadDetector() {
ensoReloadDetector =
EnsoMeta.callStaticModuleMethod(
"Standard.Base.Network.Reload_Detector", "create_reload_detector");
}
void simulateReloadTestOnly() {
EnsoMeta.callStaticModuleMethod(
"Standard.Base.Network.Reload_Detector", "simulate_reload_test_only", ensoReloadDetector);
}
}

View File

@ -19,12 +19,10 @@ import org.enso.base.cache.ResponseTooLargeException;
import org.enso.base.net.URISchematic;
import org.enso.base.net.URIWithSecrets;
import org.graalvm.collections.Pair;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
/** Makes HTTP requests with secrets in either header or query string. */
public final class EnsoSecretHelper extends SecretValueResolver {
private static Value cache;
private static EnsoHTTPResponseCache cache;
/** Gets a JDBC connection resolving EnsoKeyValuePair into the properties. */
public static Connection getJDBCConnection(
@ -179,43 +177,10 @@ public final class EnsoSecretHelper extends SecretValueResolver {
}
public static EnsoHTTPResponseCache getOrCreateCache() {
if (getCache() instanceof EnsoHTTPResponseCache httpCache) {
return httpCache;
} else {
var module =
Context.getCurrent()
.eval(
"enso",
"""
import Standard.Base.Runtime.Managed_Resource.Managed_Resource
import Standard.Base.Data.Boolean.Boolean
type Cache
private Value ref:Managed_Resource
new obj -> Cache =
on_finalize _ = 0
ref = Managed_Resource.register obj on_finalize Boolean.True
Cache.Value ref
get self = self.ref.with (r->r)
""");
var cacheNew = module.invokeMember("eval_expression", "Cache.new");
var httpCache = new EnsoHTTPResponseCache();
cache = cacheNew.execute(httpCache);
return httpCache;
}
}
public static EnsoHTTPResponseCache getCache() {
var c = cache instanceof Value v ? v.invokeMember("get") : null;
if (c != null
&& c.isHostObject()
&& c.asHostObject() instanceof EnsoHTTPResponseCache httpCache) {
return httpCache;
} else {
return null;
if (cache == null) {
cache = new EnsoHTTPResponseCache();
}
return cache;
}
private static final Comparator<Pair<String, String>> headerNameComparator =

View File

@ -95,6 +95,7 @@ add_specs suite_builder = suite_builder.group "Managed_Resource" group_builder->
# finalizes the resource
mr.finalize
mr.has_been_finalized . should_be_true
builder.append "Finalized:"+mr.to_text
# operation on finalized resource

View File

@ -172,6 +172,9 @@ add_specs suite_builder =
lru_cache = LRUCache.new
with_lru_cache lru_cache action
fake_reload =
EnsoSecretHelper.getOrCreateCache.getLRUCache.simulateReloadTestOnly
url0 = base_url_with_slash+'test_download?max-age=16&length=10'
url1 = base_url_with_slash+'test_download?max-age=16&length=20'
url_post = base_url_with_slash + "post"
@ -541,6 +544,27 @@ add_specs suite_builder =
Test_Environment.unsafe_with_environment_override "ENSO_LIB_HTTP_CACHE_MAX_TOTAL_CACHE_LIMIT" "101%" <|
LRUCache.new . getSettings . getTotalCacheLimit . should_equal (TotalCacheLimit.Percentage.new 0.2)
group_builder.specify "Cache should be cleared when a reload is detected" <|
HTTP.fetch base_url_with_slash+'test_download?length=10'
HTTP.fetch base_url_with_slash+'test_download?length=11'
HTTP.fetch base_url_with_slash+'test_download?length=12'
get_num_response_cache_entries . should_equal 3
fake_reload
get_num_response_cache_entries . should_equal 3 # Cleaning is not triggered until the next request
HTTP.fetch base_url_with_slash+'test_download?length=10'
get_num_response_cache_entries . should_equal 1
HTTP.fetch base_url_with_slash+'test_download?length=14'
HTTP.fetch base_url_with_slash+'test_download?length=15'
get_num_response_cache_entries . should_equal 3
fake_reload
get_num_response_cache_entries . should_equal 3 # Cleaning is not triggered until the next request
HTTP.fetch base_url_with_slash+'test_download?length=16'
get_num_response_cache_entries . should_equal 1
group_builder.specify "Reissues the request if the cache file disappears" pending=pending_has_url <| Test.with_retries <|
with_default_cache <|
url = base_url_with_slash+'test_download?max-age=16&length=10'