Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clear cache on reload #11673

Merged
merged 68 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
5266a4b
Using Ref.new allow_gc=True to implement in-memory caches
JaroslavTulach Nov 18, 2024
d803b28
Hide operations with References behind @TruffleBoundary
JaroslavTulach Nov 18, 2024
38a20b4
Work with v and not this.value
JaroslavTulach Nov 18, 2024
0f437fc
Merging with latest develop
JaroslavTulach Nov 20, 2024
404e3cc
Fixing typo
JaroslavTulach Nov 20, 2024
5629e20
Using Ref.new to cache reference to EnsoHTTPResponseCache
JaroslavTulach Nov 20, 2024
b1d4b37
Removing (unused) support for Name.Special
JaroslavTulach Nov 20, 2024
0e42e9d
Fixing moved import
JaroslavTulach Nov 20, 2024
0b4e1f8
Providing access to bodyNode in the builtin methods
JaroslavTulach Nov 20, 2024
f5f1614
Enabling allow_gc in the caches
JaroslavTulach Nov 20, 2024
dad2bb6
Note in changelog
JaroslavTulach Nov 20, 2024
ada146e
Merge branch 'wip/jtulach/ReferenceManager11485' into wip/gmt/11485-c…
GregoryTravis Nov 25, 2024
e26a868
wip
GregoryTravis Nov 25, 2024
da895d8
Merge branch 'develop' into wip/gmt/11485-clear-cache
GregoryTravis Nov 26, 2024
96ea1f2
Merge branch 'develop' into wip/gmt/11485-clear-cache
GregoryTravis Nov 26, 2024
e003b8a
revert ESH changes
GregoryTravis Nov 26, 2024
5d97a2a
1 test
GregoryTravis Nov 26, 2024
6a1e89f
docs
GregoryTravis Nov 26, 2024
c493ec0
Clear references on reload
GregoryTravis Nov 26, 2024
131c52e
doc
GregoryTravis Nov 27, 2024
4f750b6
Merge branch 'develop' into wip/gmt/11485-clear-cache
GregoryTravis Nov 27, 2024
5b563d4
Can no longer invoke Managed_Resource.with when Managed_Resource.fina…
JaroslavTulach Nov 27, 2024
dc2decd
remove reference release builtin and simulate reload
GregoryTravis Nov 27, 2024
1fb2517
Merge branch 'wip/gmt/11485-clear-cache' of github.com:enso-org/enso …
GregoryTravis Nov 27, 2024
ef15d90
Can no longer invoke Managed_Resource.with when Managed_Resource.fina…
JaroslavTulach Nov 27, 2024
0738190
Merge branch 'wip/gmt/11485-clear-cache' of github.com:enso-org/enso …
GregoryTravis Nov 27, 2024
5f176e7
fmt
GregoryTravis Nov 27, 2024
85e8b6d
Can no longer invoke Managed_Resource.with when Managed_Resource.fina…
JaroslavTulach Nov 27, 2024
2e9d0c3
Merge branch 'wip/jtulach/ReferenceManager11485' into wip/gmt/11485-c…
GregoryTravis Nov 27, 2024
46ef5fb
revert
GregoryTravis Nov 27, 2024
e56b867
Backing out the Ref changes
JaroslavTulach Nov 28, 2024
3ca29aa
Merge tag '2024.5.1-nightly.2024.11.27' into wip/jtulach/ReferenceMan…
JaroslavTulach Nov 28, 2024
da2d438
Ref.new takes only one argument (again)
JaroslavTulach Nov 28, 2024
ae83008
Commenting out releaseAll call for now
JaroslavTulach Nov 28, 2024
276a885
Allow system controlled Managed_Resource
JaroslavTulach Nov 28, 2024
205811c
Merging with most recent develop
JaroslavTulach Nov 28, 2024
40aedfc
on_missing behavior for managed resources that get access after being…
JaroslavTulach Nov 28, 2024
2e91135
Updating micro-distribution with the new Managed_Resource builtins
JaroslavTulach Nov 28, 2024
786f156
Moving the GC related parts of RefTest to ManagedResourceTest
JaroslavTulach Nov 28, 2024
934ec5f
Better note in changelog
JaroslavTulach Nov 28, 2024
a3c07e0
Making public so the Fetch_Spec passes OK
JaroslavTulach Nov 28, 2024
a7890d2
Using Managed_Resource inside of EnsoSecretHelper
JaroslavTulach Nov 28, 2024
7c3c33c
merge mr-only
GregoryTravis Nov 29, 2024
20b14b5
update to mr-only
GregoryTravis Nov 29, 2024
375fb31
Merge branch 'develop' into wip/gmt/11485-clear-cache
GregoryTravis Nov 29, 2024
c9ee885
schedule finalization
GregoryTravis Nov 29, 2024
00ccaca
green, clear returns Nothing
GregoryTravis Dec 1, 2024
2899524
merge
GregoryTravis Dec 3, 2024
eeba1dc
wip
GregoryTravis Dec 3, 2024
afa0dfc
remove Jaroslav POC
GregoryTravis Dec 3, 2024
4f22fe5
move ReloadDetector inline enso to a module
GregoryTravis Dec 3, 2024
0aa2490
wip
GregoryTravis Dec 3, 2024
2b01606
changelog
GregoryTravis Dec 3, 2024
a041045
fmt
GregoryTravis Dec 3, 2024
9b34a56
Merge branch 'develop' into wip/gmt/11485-clear-cache
GregoryTravis Dec 4, 2024
1f13014
Merge branch 'develop' into wip/gmt/11485-clear-cache
GregoryTravis Dec 4, 2024
8ca2e23
Merge branch 'develop' into wip/gmt/11485-clear-cache
GregoryTravis Dec 5, 2024
eebe998
wip
GregoryTravis Dec 5, 2024
f168299
fake with finalize
GregoryTravis Dec 5, 2024
95208ac
wip
GregoryTravis Dec 5, 2024
9796234
wip
GregoryTravis Dec 5, 2024
07f413d
wip
GregoryTravis Dec 5, 2024
e6ad441
wip
GregoryTravis Dec 5, 2024
c2c1ada
Merge branch 'develop' into wip/gmt/11485-clear-cache
GregoryTravis Dec 9, 2024
828ac2d
review
GregoryTravis Dec 9, 2024
be50dcd
fmt
GregoryTravis Dec 10, 2024
d036b8b
Merge branch 'develop' into wip/gmt/11485-clear-cache
GregoryTravis Dec 10, 2024
5aa4003
repeat url in fake reload test
GregoryTravis Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,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 @@ -95,6 +96,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,32 @@
import Standard.Base.Data.Boolean.Boolean
import Standard.Base.Nothing.Nothing
import Standard.Base.Runtime.Managed_Resource.Managed_Resource
import Standard.Base.Runtime.Ref.Ref

## PRIVATE
This is used by ReloadDetector.java to create a `Managed_Resource` that is
finalized when the reload button is pressed.

The `on_finalize` function and the `clear` method both write `Nothing` to the
ref. This is a signal that a reload has happenend. `on_finalize` is called by
the engine when a reload happens. `clear` is only for testing, to simulate a
reload.

The `0` value stored in the ref is not used; it just has to be something
other than Nothing.
type Reload_Detector
private Value mr:Managed_Resource

new -> Reload_Detector =
ref = Ref.new 0
on_finalize ref = ref.put Nothing
mr = Managed_Resource.register ref on_finalize Boolean.True
Reload_Detector.Value mr

get self = self.mr.with .get
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this fail if the Managed_Resource is cleaned, because with will apply .get with Uninitialized_State? It feels like we should have a catch somewhere

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


clear self =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • if Promote broken values instead of ignoring them #11777 gets implemented
  • then the clear method would change its behavior
  • if the ref in ret.put Nothing would be a DataflowError then
  • the result of whole self.m.with would be a DataflowError
  • and the method would terminate immediately returning the DataflowError

self.mr.with (ref-> ref.put Nothing)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the most recent changes in the implementation this code shouldn't set the ref to Nothing, but to a DataflowError - however I admit, I have no way to do it! I don't seem to be able to send Error into ref.put and I even managed to crash the engine while doing that #11774

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am using a special value for the test-only implementation.

Nothing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that's why I was asking to have a Panic and not a dataflow error. If by any chance (through correct or incorrect code) the mr.with block raises an error, this is a place where we will discard and lose it.

Suggested change
self.mr.with (ref-> ref.put Nothing)
Nothing
self.mr.with (ref-> ref.put Nothing) . if_not_error Nothing

This would have been much simpler if this was a Panic in the first place...

(cc: @JaroslavTulach)

Copy link
Member

@JaroslavTulach JaroslavTulach Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case we however have a valid code. clear of already GCed reference is a no-op. Which is exactly whe the following:

  • ignore potential error
  • return Nothing

sequence does. Maybe it was @GregoryTravis's intention?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What can we learn from this? Let's claim: Use of Error encourages robust code, as it is idempotent to double cleanup like the one performed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a mistake -- I didn't realize the resource was GC'd as well as having the finalizer run. I don't think it's enough to check for Nothing -- what if something changes, and the Nothing is stored before the release of the reference, then it will no longer be nothing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated it to use catch to detect the finalization / collection.


create_reload_detector = Reload_Detector.new
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,7 @@ 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 @@ -344,6 +355,11 @@ public LRUCacheSettings getSettings() {
return settings;
}

/** Public for testing. */
public void simulateReloadTestOnly() {
reloadDetector.simulateReloadTestOnly();
}
Comment on lines +358 to +361
Copy link
Member

@radeusgd radeusgd Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced about this mock.

After all - tests relying on this mock use completely different logic for checking if the cache should be cleared than what will be running in the IDE. The Reload_Detector is a new component and it is not tested anywhere at all (apart from manual testing).

I think it would be best to add a test to the JVM tests, similar to something like RuntimeExecutionEnvironmentTest that checks the refresh logic is correctly connected through the Language Server. Perhaps we don't need to test it all the way down to HTTP. It would probably suffice to have a test that instantiates the Java ReloadDetector and checks that it indeed detects cache invalidation correctly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think perhaps we may want to proceed with this PR, as it's needed for release, with just manual testing. But I think it would be good to have some test that ensures that this works as expected.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a test to the JVM tests, similar to something like RuntimeExecutionEnvironmentTest that checks the refresh logic is correctly connected through the Language Server.

Logic of Managed_Resource.register ref on_finalize Boolean.True is unit tested at

ensoCtx.getResourceManager().scheduleFinalizationOfSystemReferences();

If we have a unit test of Managed_Resource and another unit test for ReloadDetector, then I believe we have 100% coverage!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point was that we are not testing the integration between ReloadDetector and Managed_Resource logics if we rely on a mock when testing ReloadDetector.

The problem is clear in the get bug which if I understand correctly you agree with me is a bug - we are not catching the possible Uninitialized_State there, possibly returning dataflow error instead of boolean. This is not covered by any existing test as far as I can tell. Given that we seem to have a bug and no test seems to be catching the bug - I'd say we don't have good enough tests for this facility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that a test like this is necessary -- precisely because I have already had the tests pass but the feature fail to work. Thus: #11792.


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 weak reference (created in eval'd Enso code) that is set to null on reload.
*/
public class ReloadDetector {
// Weak reference that is set to null on reload.
private Value trigger;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment still up-to-date? This contains a Reload_Detector now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


public ReloadDetector() {
resetTrigger();
}

public boolean hasReloadOccurred() {
var reloadHasOccurred = trigger.invokeMember("get").isNull();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was correct, but it is not anymore. In the IDE the value returned from get should be an Enso Error now after the most recent changes.

E.g. one should check for isException() and not (only) for isNull().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

if (reloadHasOccurred) {
resetTrigger();
}
return reloadHasOccurred;
}

private void resetTrigger() {
trigger =
EnsoMeta.callStaticModuleMethod(
"Standard.Base.Network.Reload_Detector", "create_reload_detector");
}

void simulateReloadTestOnly() {
trigger.invokeMember("clear");
}
}
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
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 @@
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 @@ -435,7 +438,7 @@
. add_query_argument "arg2" "plain value"

HTTP.fetch url1
get_num_response_cache_entries . should_equal 1

Check failure on line 441 in test/Table_Tests/src/IO/Fetch_Spec.enso

View workflow job for this annotation

GitHub Actions / Standard Library Tests (GraalVM CE) (macos, amd64)

Response caching: Should work with secrets in the URI

0 did not equal 1 (at /Users/runner/work/enso/enso/test/Table_Tests/src/IO/Fetch_Spec.enso:441:21-67).
HTTP.fetch uri2
get_num_response_cache_entries . should_equal 2

Expand Down Expand Up @@ -517,6 +520,27 @@
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?max-age=16&length=10'
HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=11'
HTTP.fetch base_url_with_slash+'test_download?max-age=16&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?max-age=16&length=13'
get_num_response_cache_entries . should_equal 1
HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=14'
HTTP.fetch base_url_with_slash+'test_download?max-age=16&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?max-age=16&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
Loading