Skip to content

Commit

Permalink
Clear cache on reload (#11673)
Browse files Browse the repository at this point in the history
  • Loading branch information
GregoryTravis authored Dec 10, 2024
1 parent d79b421 commit 9e00b9d
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 40 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -42,6 +43,10 @@ class RecomputeContextCmd(
ec: ExecutionContext
): Future[Boolean] = {
Future {
EnsoContext
.get(null)
.getResourceManager()
.scheduleFinalizationOfSystemReferences();
ctx.jobControlPlane.abortJobs(
request.contextId,
"recompute context",
Expand Down
16 changes: 16 additions & 0 deletions std-bits/base/src/main/java/org/enso/base/cache/LRUCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand All @@ -89,6 +92,8 @@ public LRUCache(LRUCacheSettings settings, NowGetter nowGetter, DiskSpaceGetter
*/
public CacheResult<M> getResult(ItemBuilder<M> itemBuilder)
throws IOException, InterruptedException, ResponseTooLargeException {
clearOnReload();

String cacheKey = itemBuilder.makeCacheKey();

try {
Expand Down Expand Up @@ -221,6 +226,12 @@ public void clear() {
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 =
Expand Down Expand Up @@ -352,6 +363,11 @@ public LRUCacheSettings getSettings() {
return settings;
}

/** Public for testing. */
public void simulateReloadTestOnly() {
reloadDetector.simulateReloadTestOnly();
}

private record CacheEntry<M>(File responseData, M metadata, long size, ZonedDateTime expiry) {}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@
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(
Expand Down Expand Up @@ -179,43 +177,10 @@ public EnsoHttpResponse reconstructResponseFromCachedStream(
}

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 =
Expand Down
1 change: 1 addition & 0 deletions test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions test/Table_Tests/src/IO/Fetch_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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'
Expand Down

0 comments on commit 9e00b9d

Please sign in to comment.