mirror of
https://github.com/enso-org/enso.git
synced 2024-12-19 23:11:36 +03:00
Clear cache on reload (#11673)
This commit is contained in:
parent
d79b4218fc
commit
9e00b9ddcc
@ -108,6 +108,7 @@
|
|||||||
- [Added `Table.input` allowing creation of typed tables from vectors of data,
|
- [Added `Table.input` allowing creation of typed tables from vectors of data,
|
||||||
including auto parsing text columns.][11562]
|
including auto parsing text columns.][11562]
|
||||||
- [Enhance Managed_Resource to allow implementation of in-memory caches][11577]
|
- [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
|
[11235]: https://github.com/enso-org/enso/pull/11235
|
||||||
[11255]: https://github.com/enso-org/enso/pull/11255
|
[11255]: https://github.com/enso-org/enso/pull/11255
|
||||||
@ -116,6 +117,7 @@
|
|||||||
[11490]: https://github.com/enso-org/enso/pull/11490
|
[11490]: https://github.com/enso-org/enso/pull/11490
|
||||||
[11562]: https://github.com/enso-org/enso/pull/11562
|
[11562]: https://github.com/enso-org/enso/pull/11562
|
||||||
[11577]: https://github.com/enso-org/enso/pull/11577
|
[11577]: https://github.com/enso-org/enso/pull/11577
|
||||||
|
[11673]: https://github.com/enso-org/enso/pull/11673
|
||||||
|
|
||||||
#### Enso Language & Runtime
|
#### Enso Language & Runtime
|
||||||
|
|
||||||
|
@ -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
|
@ -1,8 +1,10 @@
|
|||||||
## An API for manual resource management.
|
## An API for manual resource management.
|
||||||
|
|
||||||
import project.Any.Any
|
import project.Any.Any
|
||||||
|
import project.Errors.Common.Uninitialized_State
|
||||||
|
import project.Meta
|
||||||
import project.Nothing.Nothing
|
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.
|
## Resource provides an API for manual management of computation resources.
|
||||||
|
|
||||||
@ -90,5 +92,15 @@ type Managed_Resource
|
|||||||
take : Any
|
take : Any
|
||||||
take self = @Builtin_Method "Managed_Resource.take"
|
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"
|
register_builtin r fn sys:Boolean = @Builtin_Method "Managed_Resource.register_builtin"
|
||||||
with_builtin r fn = @Builtin_Method "Managed_Resource.with_builtin"
|
with_builtin r fn = @Builtin_Method "Managed_Resource.with_builtin"
|
||||||
|
@ -11,6 +11,7 @@ import org.enso.interpreter.instrument.{
|
|||||||
}
|
}
|
||||||
import org.enso.interpreter.instrument.execution.RuntimeContext
|
import org.enso.interpreter.instrument.execution.RuntimeContext
|
||||||
import org.enso.interpreter.instrument.job.{EnsureCompiledJob, ExecuteJob}
|
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
|
||||||
import org.enso.polyglot.runtime.Runtime.Api.RequestId
|
import org.enso.polyglot.runtime.Runtime.Api.RequestId
|
||||||
|
|
||||||
@ -42,6 +43,10 @@ class RecomputeContextCmd(
|
|||||||
ec: ExecutionContext
|
ec: ExecutionContext
|
||||||
): Future[Boolean] = {
|
): Future[Boolean] = {
|
||||||
Future {
|
Future {
|
||||||
|
EnsoContext
|
||||||
|
.get(null)
|
||||||
|
.getResourceManager()
|
||||||
|
.scheduleFinalizationOfSystemReferences();
|
||||||
ctx.jobControlPlane.abortJobs(
|
ctx.jobControlPlane.abortJobs(
|
||||||
request.contextId,
|
request.contextId,
|
||||||
"recompute context",
|
"recompute context",
|
||||||
|
@ -73,6 +73,9 @@ public class LRUCache<M> {
|
|||||||
/** Used to get the current free disk space; mockable. */
|
/** Used to get the current free disk space; mockable. */
|
||||||
private final DiskSpaceGetter diskSpaceGetter;
|
private final DiskSpaceGetter diskSpaceGetter;
|
||||||
|
|
||||||
|
/** Used to clear the cache on reload. */
|
||||||
|
private final ReloadDetector reloadDetector = new ReloadDetector();
|
||||||
|
|
||||||
public LRUCache() {
|
public LRUCache() {
|
||||||
this(LRUCacheSettings.getDefault(), new NowGetter(), new DiskSpaceGetter());
|
this(LRUCacheSettings.getDefault(), new NowGetter(), new DiskSpaceGetter());
|
||||||
}
|
}
|
||||||
@ -89,6 +92,8 @@ public class LRUCache<M> {
|
|||||||
*/
|
*/
|
||||||
public CacheResult<M> getResult(ItemBuilder<M> itemBuilder)
|
public CacheResult<M> getResult(ItemBuilder<M> itemBuilder)
|
||||||
throws IOException, InterruptedException, ResponseTooLargeException {
|
throws IOException, InterruptedException, ResponseTooLargeException {
|
||||||
|
clearOnReload();
|
||||||
|
|
||||||
String cacheKey = itemBuilder.makeCacheKey();
|
String cacheKey = itemBuilder.makeCacheKey();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -221,6 +226,12 @@ public class LRUCache<M> {
|
|||||||
removeCacheEntriesByPredicate(e -> true);
|
removeCacheEntriesByPredicate(e -> true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void clearOnReload() {
|
||||||
|
if (reloadDetector.hasReloadOccurred()) {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Remove all cache entries (and their cache files) that match the predicate. */
|
/** Remove all cache entries (and their cache files) that match the predicate. */
|
||||||
private void removeCacheEntriesByPredicate(Predicate<CacheEntry<M>> predicate) {
|
private void removeCacheEntriesByPredicate(Predicate<CacheEntry<M>> predicate) {
|
||||||
List<Map.Entry<String, CacheEntry<M>>> toRemove =
|
List<Map.Entry<String, CacheEntry<M>>> toRemove =
|
||||||
@ -352,6 +363,11 @@ public class LRUCache<M> {
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Public for testing. */
|
||||||
|
public void simulateReloadTestOnly() {
|
||||||
|
reloadDetector.simulateReloadTestOnly();
|
||||||
|
}
|
||||||
|
|
||||||
private record CacheEntry<M>(File responseData, M metadata, long size, ZonedDateTime expiry) {}
|
private record CacheEntry<M>(File responseData, M metadata, long size, ZonedDateTime expiry) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
39
std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java
vendored
Normal file
39
std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java
vendored
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -19,12 +19,10 @@ import org.enso.base.cache.ResponseTooLargeException;
|
|||||||
import org.enso.base.net.URISchematic;
|
import org.enso.base.net.URISchematic;
|
||||||
import org.enso.base.net.URIWithSecrets;
|
import org.enso.base.net.URIWithSecrets;
|
||||||
import org.graalvm.collections.Pair;
|
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. */
|
/** Makes HTTP requests with secrets in either header or query string. */
|
||||||
public final class EnsoSecretHelper extends SecretValueResolver {
|
public final class EnsoSecretHelper extends SecretValueResolver {
|
||||||
private static Value cache;
|
private static EnsoHTTPResponseCache cache;
|
||||||
|
|
||||||
/** Gets a JDBC connection resolving EnsoKeyValuePair into the properties. */
|
/** Gets a JDBC connection resolving EnsoKeyValuePair into the properties. */
|
||||||
public static Connection getJDBCConnection(
|
public static Connection getJDBCConnection(
|
||||||
@ -179,43 +177,10 @@ public final class EnsoSecretHelper extends SecretValueResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static EnsoHTTPResponseCache getOrCreateCache() {
|
public static EnsoHTTPResponseCache getOrCreateCache() {
|
||||||
if (getCache() instanceof EnsoHTTPResponseCache httpCache) {
|
if (cache == null) {
|
||||||
return httpCache;
|
cache = new EnsoHTTPResponseCache();
|
||||||
} 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;
|
|
||||||
}
|
}
|
||||||
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Comparator<Pair<String, String>> headerNameComparator =
|
private static final Comparator<Pair<String, String>> headerNameComparator =
|
||||||
|
@ -95,6 +95,7 @@ add_specs suite_builder = suite_builder.group "Managed_Resource" group_builder->
|
|||||||
|
|
||||||
# finalizes the resource
|
# finalizes the resource
|
||||||
mr.finalize
|
mr.finalize
|
||||||
|
mr.has_been_finalized . should_be_true
|
||||||
builder.append "Finalized:"+mr.to_text
|
builder.append "Finalized:"+mr.to_text
|
||||||
|
|
||||||
# operation on finalized resource
|
# operation on finalized resource
|
||||||
|
@ -172,6 +172,9 @@ add_specs suite_builder =
|
|||||||
lru_cache = LRUCache.new
|
lru_cache = LRUCache.new
|
||||||
with_lru_cache lru_cache action
|
with_lru_cache lru_cache action
|
||||||
|
|
||||||
|
fake_reload =
|
||||||
|
EnsoSecretHelper.getOrCreateCache.getLRUCache.simulateReloadTestOnly
|
||||||
|
|
||||||
url0 = base_url_with_slash+'test_download?max-age=16&length=10'
|
url0 = base_url_with_slash+'test_download?max-age=16&length=10'
|
||||||
url1 = base_url_with_slash+'test_download?max-age=16&length=20'
|
url1 = base_url_with_slash+'test_download?max-age=16&length=20'
|
||||||
url_post = base_url_with_slash + "post"
|
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%" <|
|
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)
|
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 <|
|
group_builder.specify "Reissues the request if the cache file disappears" pending=pending_has_url <| Test.with_retries <|
|
||||||
with_default_cache <|
|
with_default_cache <|
|
||||||
url = base_url_with_slash+'test_download?max-age=16&length=10'
|
url = base_url_with_slash+'test_download?max-age=16&length=10'
|
||||||
|
Loading…
Reference in New Issue
Block a user