feat: DH-20578: Groovy remote file sourcing#7451
Conversation
No docs changes detected for 9bcc8b2 |
a48276f to
fc106d6
Compare
777b0b4 to
4d1b51c
Compare
| implementation project(':server-jetty-11') | ||
|
|
||
| implementation project(':extensions-flight-sql') | ||
| implementation project(':plugin-remotefilesource') |
There was a problem hiding this comment.
Not sure if these should have been added here since I don't see any other plugins.
| implementation project(':server-jetty') | ||
|
|
||
| implementation project(':extensions-flight-sql') | ||
| implementation project(':plugin-remotefilesource') |
There was a problem hiding this comment.
Not sure if these should have been added here since I don't see any other plugins.
f0ced57 to
b70f606
Compare
| /** | ||
| * Utility methods for working with protobuf messages in the client-side JavaScript environment. | ||
| */ | ||
| public class JsProtobufUtils { |
There was a problem hiding this comment.
...how sure are we that we actually need any/all of this? doesnt the generated message types give us this for free?
There was a problem hiding this comment.
I thought we needed it in order to support the Any wrapper?
| @JsType(namespace = "dh.remotefilesource", name = "RemoteFileSourceService") | ||
| public class JsRemoteFileSourceService extends HasEventHandling { | ||
| /** Event name for generic messages from the server */ | ||
| @JsProperty(namespace = "dh.remotefilesource.RemoteFileSourceService") |
There was a problem hiding this comment.
this annotation shouldnt be necessary, should get it from the type's namespace+name, ditto below
There was a problem hiding this comment.
I got warnings for the @link #EVENT_MESSAGE in the header comment. Claude suggested this fix which worked. Is there a better way?
There was a problem hiding this comment.
By adding hte JsType annotation at the class top level, all public static constants are added to the JS automatically. See JsPartitionedTable for some precedent.
There was a problem hiding this comment.
Where are you seeing this warning? I'm not seeing any warning in the header or anywhere else in the file related to this when I remove this annotation (the @JsProperty annotation)
There was a problem hiding this comment.
@mofojed
If I remove the annotation and build the JS API types via ./gradlew :web-client-api:types:assemble
I get:
[warning] Failed to resolve link to "dh.remotefilesource.EVENT_REQUEST_SOURCE" in comment for dh.remotefilesource.RemoteFileSourceService.
There was a problem hiding this comment.
Although, there are a number of other warnings as well. I just didn't want to keep adding to them:
[warning] Failed to resolve link to "dh.convertArrayToString" in comment for dh.PreviewOptions.
[warning] Failed to resolve link to "dh.array" in comment for dh.PreviewOptions.
[warning] Failed to resolve link to "dh.transportFactory" in comment for dh.ConnectOptions.useWebsockets.
[warning] Failed to resolve link to "dh.useWebsockets" in comment for dh.ConnectOptions.transportFactory.
[warning] Failed to resolve link to "Table.uncoalesced" in comment for dh.Column.isPartitionColumn.
[warning] Failed to resolve link to "Promise" in comment for dh.IdeConnection.mergeTables.
[warning] Failed to resolve link to "io.deephaven.web.shared.fu.JsRunnable" in comment for dh.IdeConnection.onLogMessage.
[warning] Failed to resolve link to "io.deephaven.web.shared.fu.JsRunnable" in comment for dh.IdeConnection.onLogMessage.__type.
[warning] Failed to resolve link to "dh.includeConstituents" in comment for dh.TreeTable.
[warning] Failed to resolve link to "TreeRowImpl.hasChildren" in comment for dh.TreeTable.
[warning] Failed to resolve link to "java.lang.UnsupportedOperationException" in comment for dh.TreeTable.applyCustomColumns.
[warning] Failed to resolve link to "io.deephaven.engine.table.PartitionedTable.merge" in comment for dh.PartitionedTable.getMergedTable.
[warning] Failed to resolve link to "DataOptions.SubscriptionOptions" in comment for dh.Table.subscribe.
[warning] Failed to resolve link to "io.deephaven.web.client.api.subscription.AbstractTableSubscription.close" in comment for dh.Table.createSubscription.
[warning] Failed to resolve link to "Table.uncoalesced" in comment for dh.Table.size.
[warning] Failed to resolve link to "Promise" in comment for dh.IdeSession.getTreeTable.
[warning] Failed to resolve link to "Promise" in comment for dh.IdeSession.mergeTables.
[warning] Failed to resolve link to "Promise" in comment for dh.IdeSession.emptyTable.
[warning] Failed to resolve link to "Promise" in comment for dh.IdeSession.timeTable.
[warning] Failed to resolve link to "JsTable#createSnapshot(Object)" in comment for dh.TableViewportSubscription.snapshot.
[warning] Failed to resolve link to "dh.remotefilesource.EVENT_REQUEST_SOURCE" in comment for dh.remotefilesource.RemoteFileSourceService.
| "RemoteFileSourcePluginFetchRequest must contain a valid result_id"); | ||
| } | ||
|
|
||
| final String pluginName = request.getPluginName(); |
There was a problem hiding this comment.
it appears that DeephavenRemoteFileSourcePlugin is always the pluginName set by the client, so we end up with exactly one PluginMarker. Do we anticipate more of these, or specific clients who can control this? Nothing seems able to clear old instances, do we want a mechanism (liveness?) for that?
There was a problem hiding this comment.
I anticipate that the Python version of the plugin may make use of this at some point.
Clearing instances is probably a good idea. Any suggestions on best way to do that?
mofojed
left a comment
There was a problem hiding this comment.
Functionality seems good overall, nice.
| throw new IllegalArgumentException("messageStream must not be null"); | ||
| } | ||
|
|
||
| executionContext = new RemoteFileSourceExecutionContext(messageStream, resourcePaths); |
There was a problem hiding this comment.
Should we throw if the executionContext is already set?
There was a problem hiding this comment.
I don't think so. executionContext is static and only 1 MessageStream instance can claim it at a time. This probably doesn't impact Core+, but in Community it could matter if you had multiple connections to the worker.
There was a problem hiding this comment.
Added a better block comment to the method.
| @JsType(namespace = "dh.remotefilesource", name = "RemoteFileSourceService") | ||
| public class JsRemoteFileSourceService extends HasEventHandling { | ||
| /** Event name for generic messages from the server */ | ||
| @JsProperty(namespace = "dh.remotefilesource.RemoteFileSourceService") |
There was a problem hiding this comment.
By adding hte JsType annotation at the class top level, all public static constants are added to the JS automatically. See JsPartitionedTable for some precedent.
| DomGlobal.setTimeout(ignore -> fireEvent(EVENT_REQUEST_SOURCE, | ||
| new ResourceRequestEvent(message.getRequestId(), request)), 0); |
There was a problem hiding this comment.
Comment why this is adding a timeout here instead of just calling it right away.
There was a problem hiding this comment.
I'm not actually 100% sure why this is needed except that I saw precedence in JsWidget for dispatching new events in response to message stream events. I assume it's desirable to ensure this happens on the next tick, but I can't speak to a specific problem it may cause if we don't do so.
There was a problem hiding this comment.
Looking at the history in JsWidget, looks like it was added with this PR: #6715
In that case, I think the issue was the promise for fetching the widget would resolve after we had already fired off the events, so client didn't have time to add a listener to actually receive the event. So just put emitting the event on the next tick after resolving.
I guess in theory that might be possible here as well... though maybe we should be checking if there's no listeners and throwing in that case, or force a listener to be passed in when retrieving the plugin. When there's a request from the server, it's blocked until there's a response, yeah? So we must have a listener to respond here (or we should default to a no-op response, but then why bother fetching the plugin...), and it doesn't make sense to have multiple listeners registered (since multiple responses for one request is invalid), so really we want exactly one listener... may as well enforce it with creation of the service?
In CoreClient#getRemoteFileSourceService(), we're returning a new instance of the service plugin whenever that method is called - what happens if there's multiple instances? We really only want one instance, right?
There was a problem hiding this comment.
I made 2 primary changes to address this:
CoreClient#getRemoteFileSourceService()now creates a single instance of JsRemoteFileSourceService- JsRemoteFileSourceService enforces that only 1
EVENT_REQUEST_SOURCEhandler can be registered. It also throws if a request for source is received and there are no registered event handlers
There was a problem hiding this comment.
Also removed the setTimeout
mofojed
left a comment
There was a problem hiding this comment.
Changes look good, had some questions about fetching an instance of the service and how we handle multiple instances.
mofojed
left a comment
There was a problem hiding this comment.
Marking as needing more changes, as I think this has a caching issue right now? Maybe add a comment on the PR about what the issue is, and the plan to fix it. Let me know if you need any help figuring it out.
|
There seems to be an issue with GroovyClassLoader caching previously run scripts such that changes after initial run are not picked up. Next step is to confirm that it is the GroovyClassLoader that is doing the caching. |
| // opportunity to be remotely fetched. If remote sources aren't involved, no need to clear the cache. | ||
| if (previousEvalHadRemoteSources || hasRemoteSources) { | ||
| log.debug("Remote file sourcing enabled or was previously enabled. Clearing class cache."); | ||
| refreshClassLoader(); |
There was a problem hiding this comment.
How much of a performance hit is this? Could we improve this by having the plugin notify when changes have been made and only clearing the cache then?
There was a problem hiding this comment.
@mofojed I'd need to think through how to implement that, but wouldn't it be safe to assume that the majority of cases where a user re-runs a script would imply changes have been made to a source script? Like even a small change to 1 line requires the full cache eviction, so not sure we'd prevent clearing the cache very often.
There was a problem hiding this comment.
You're running a file from a script; you can make a ton of changes to that/re-run and not change any of your imported modules for quite a while. Only need to evict the cache after you make changes to those imported modules AND re-run the script.
There was a problem hiding this comment.
gotcha. I wonder then if the execution context can track an isDirty flag that is true if
- sources are added / removed
- sources in the context are changed
There was a problem hiding this comment.
@mofojed I've made the caching improvement. Should only evict cache if remote source list changes or if a dependency changes. Won't evict if only the entry point file changes.
| GroovyClassLoader classLoader = new GroovyClassLoader(STATIC_LOADER, config); | ||
|
|
||
| // Update the session's ExecutionContext with fresh QueryCompiler | ||
| executionContext = createExecutionContext( |
There was a problem hiding this comment.
Instead of building a whole new ExecutionContext, could we just add a setQueryCompiler method on the ExecutionContext and update that directly? Is there precedent for replacing the ExecutionContext somewhere?
There was a problem hiding this comment.
I don't see any precedent for replacing the execution context, but I also don't see precedent for any setXxx methods on ExecutionContext. There is a builder pattern and a handful of withXxx methods used to create copies with 1 property modification. I ended up adding a withQueryCompiler method to at least encapsulate more of the code.
There was a problem hiding this comment.
Okay, I like the with way better, but would like confirmation from @cpwright or @rcaudy that this won't cause other issues... I imagine in most places we should be calling .getContext() instead of retaining a reference to the context, but wondering if there's some place they may be cached locally where this would cause unexpected results? Like an updating table or something?
| // Server → Client: Requests sent from server to client via MessageStream | ||
| message RemoteFileSourceServerRequest { | ||
| // Unique identifier for this request, used to correlate the response | ||
| string request_id = 1; | ||
|
|
||
| oneof request { | ||
| // Request source data/resource from the client | ||
| RemoteFileSourceMetaRequest meta_request = 2; | ||
|
|
||
| // Acknowledgment that execution context was set | ||
| SetExecutionContextResponse set_execution_context_response = 3; | ||
| } | ||
| } | ||
|
|
||
| // Client → Server: Requests/responses sent from client to server via MessageStream | ||
| message RemoteFileSourceClientRequest { | ||
| // The request_id from the ServerRequest this is responding to (if applicable) | ||
| string request_id = 1; | ||
|
|
||
| oneof request { | ||
| // Response to a resource request | ||
| RemoteFileSourceMetaResponse meta_response = 2; | ||
|
|
||
| // Set the execution context for script execution | ||
| SetExecutionContextRequest set_execution_context = 3; | ||
| } | ||
| } |
There was a problem hiding this comment.
This is confusing - The outer message is Request but it is both requests and responses.
I'd probably name them RemoteFileSourceServerMessage and RemoteFileSourceClientMessage instead.
There was a problem hiding this comment.
I've renamed these. Note that there is a gwt wrapper generation step that Colin has to do. I did a find / replace change and things seem to work, but there's a chance a regeneration might produce additional changes.
…e and RemoteFileSourceServerRequest -> RemoteFileSourceServerMessage (#DH-20578)
feat: DH-20578: regen gwt bindings
|
Please run |
mofojed
left a comment
There was a problem hiding this comment.
Most of my comments have been addressed, but still would like review on this comment: #7451 (comment)
Specifically, we call ExecutionContext#open() in many, many places. That's a public API; how can we be sure that we can replace the execution context, and any consumer of the execution context is up to date? I don't think we can guarantee that, so this change would almost certainly break something. E.g. EmbeddedServer, opens the context permanently; then we're just blowing it away. Those are very difficult to debug problems.
I see ExecutionContext still has a getQueryCompiler() method there so theoretically someone could still cache that, but that's not a) a resource that you open/close, and b) smaller footprint... so I think that would be safer? Would really prefer someone more knowledgeable to provide review there.
This definitely could break Mutating ExecContext seems far more likely to be bad news than changing the execcontext that the script session uses. That's the only "open" that really matters here, and that will close when the current block of code is finished executing. |
There was a problem hiding this comment.
Pull request overview
Implements “Groovy remote file sourcing” (DH-20578) by adding a server-side plugin + classloader integration and the corresponding client/proto bindings so Groovy scripts can import .groovy sources supplied by a remote client over a message stream.
Changes:
- Adds
plugin-remotefilesource(Flight command resolver + ObjectType plugin + message stream provider) and integrates it into server runtime. - Introduces
RemoteFileSourceClassLoader/ provider interface and updates Groovy session execution to refresh compilation cache when remote sources are dirty/removed. - Adds new
remotefilesource.protoplus generated bindings for JS (client-backplane + raw-js-openapi), Python, and Go; adds a JS client API service for remote file source interactions.
Reviewed changes
Copilot reviewed 38 out of 38 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceservermessage/RequestCase.java | New JS interop enum wrapper for server-message oneof case. |
| web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/remotefilesourceclientmessage/RequestCase.java | New JS interop enum wrapper for client-message oneof case. |
| web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextResponse.java | Generated JS interop type for SetExecutionContextResponse. |
| web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/SetExecutionContextRequest.java | Generated JS interop type for SetExecutionContextRequest. |
| web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceServerMessage.java | Generated JS interop type for server→client message stream proto. |
| web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourcePluginFetchRequest.java | Generated JS interop type for Flight “fetch plugin” command proto. |
| web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaResponse.java | Generated JS interop type for meta response (content/found/error). |
| web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceMetaRequest.java | Generated JS interop type for meta request (resource name). |
| web/client-backplane/src/main/java/io/deephaven/javascript/proto/dhinternal/io/deephaven_core/proto/remotefilesource_pb/RemoteFileSourceClientMessage.java | Generated JS interop type for client→server message stream proto. |
| web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/ResourceContentUnion.java | TS/JS union type for resource content (String or Uint8Array). |
| web/client-api/src/main/java/io/deephaven/web/client/api/remotefilesource/JsRemoteFileSourceService.java | JS client service to fetch plugin instance and handle message stream requests/responses. |
| web/client-api/src/main/java/io/deephaven/web/client/api/JsProtobufUtils.java | Adds client-side helper to wrap messages into google.protobuf.Any bytes. |
| web/client-api/src/main/java/io/deephaven/web/client/api/CoreClient.java | Exposes a cached getRemoteFileSourceService() accessor. |
| settings.gradle | Adds :plugin-remotefilesource project inclusion. |
| server/jetty-app/build.gradle | Adds remote file source plugin to runtime classpath. |
| server/jetty-app-11/build.gradle | Adds remote file source plugin to runtime classpath (Jetty 11). |
| server/build.gradle | Adds remote file source plugin to server runtime dependencies. |
| py/client/deephaven_core/proto/remotefilesource_pb2_grpc.py | Generated Python gRPC stubs for new proto. |
| py/client/deephaven_core/proto/remotefilesource_pb2.pyi | Generated mypy-protobuf typing stubs for new proto. |
| py/client/deephaven_core/proto/remotefilesource_pb2.py | Generated Python protobuf message definitions for new proto. |
| proto/raw-js-openapi/webpack.config.js | Adds remotefilesource to JS shim alias generation list. |
| proto/raw-js-openapi/src/shim/remotefilesource_pb.js | Adds shim export for JS raw-openapi bundle. |
| proto/raw-js-openapi/src/index.js | Exports remotefilesource_pb in raw-js-openapi index. |
| proto/proto-backplane-grpc/src/main/proto/deephaven_core/proto/remotefilesource.proto | Defines the RemoteFileSource message stream + fetch command protos. |
| proto/proto-backplane-grpc/Dockerfile | Includes new proto in codegen and proto-doc generation. |
| plugin/src/main/java/io/deephaven/plugin/type/PluginMarker.java | Introduces a generic marker object keyed by plugin name for export/type matching. |
| plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceTicketResolverFactoryService.java | Registers ticket/command resolver via service loader. |
| plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourcePlugin.java | ObjectType plugin creating a message stream for the remote file source. |
| plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceMessageStream.java | Implements bidirectional message handling and registers as a remote resource provider. |
| plugin/remotefilesource/src/main/java/io.deephaven.remotefilesource/RemoteFileSourceCommandResolver.java | Resolves Flight command into exported PluginMarker and returns FlightInfo endpoint. |
| plugin/remotefilesource/gradle.properties | Declares project type for the new plugin module. |
| plugin/remotefilesource/build.gradle | Build config + deps for the new plugin module. |
| go/internal/proto/remotefilesource/remotefilesource.pb.go | Generated Go protobuf bindings for new proto. |
| engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java | Integrates remote classloader + refreshes Groovy compilation cache when remote sources are dirty/removed. |
| engine/table/src/main/java/io/deephaven/engine/util/AbstractScriptSession.java | Makes executionContext mutable and factors ExecutionContext construction for refresh scenarios. |
| engine/context/src/main/java/io/deephaven/engine/context/ExecutionContext.java | Adds withQueryCompiler(...) for swapping compilers while keeping context stable. |
| engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceProvider.java | Defines provider interface for remote resource sourcing. |
| engine/base/src/main/java/io/deephaven/engine/util/RemoteFileSourceClassLoader.java | Implements resource lookup via providers using a custom remotefile:// URL + URLConnection. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| byte[] content = response.getContent().toByteArray(); | ||
|
|
||
| log.info().append("Received resource response for requestId: ").append(requestId) | ||
| .append(", found: ").append(response.getFound()) | ||
| .append(", content length: ").append(content.length).endl(); | ||
|
|
||
| if (!response.getError().isEmpty()) { | ||
| log.warn().append("Error in response: ").append(response.getError()).endl(); | ||
| } | ||
|
|
| for (String contextResourcePath : executionContext.getResourcePaths()) { | ||
| if (resourceName.equals(contextResourcePath)) { |
| content = provider.requestResource(resourceName) | ||
| .orTimeout(RESOURCE_TIMEOUT_SECONDS, TimeUnit.SECONDS) | ||
| .get(); |
| if (content == null || content.length == 0) { | ||
| throw new IOException("No content for resource: " + resourceName); |
|
|
||
| // Create a lazy promise that will be resolved when we get the response | ||
| LazyPromise<Boolean> promise = new LazyPromise<>(); | ||
| pendingSetExecutionContextRequests.put(requestId, promise); |
| import java.nio.ByteBuffer; | ||
|
|
| import io.deephaven.server.session.TicketRouter; | ||
| import io.deephaven.server.session.WantsTicketRouter; | ||
|
|
||
| import io.grpc.StatusRuntimeException; |
|
|
||
| String requestId = UUID.randomUUID().toString(); | ||
| CompletableFuture<byte[]> future = new CompletableFuture<>(); | ||
| pendingRequests.put(requestId, future); |
DH-20578: Adds Groovy support for remote file source features in VS Code extension. The supporting plugin work has not fully landed yet mostly waiting on tests, but I don't think that should block this PR: **Related Pending PRs** Changes for these are deployed to `bmingles-remote-file-source2` BHS - deephaven/deephaven-core#7451 - deephaven-ent/iris#3878 - TBD: PR to integrate Core changes into gplus ## Testing - Test on a grizzly or gplus server without the plugin installed. Verify that we can still run python and groovy scripts without remote file sourcing. This is just a regression test. - `bmingles-remote-file-source2` has Groovy and Python remote plugins installed, so full feature set can be tested there ### Setup - Start `bmingles-remote-file-source2` vm if not already running - Clone this test repo `git@github.com:bmingles/deephaven-controller-script-test` - Checkout the `local` git branch (the `main` branch is configured as a controller source on `bmingles-remote-file-source2`). ### Local override tests - Connect to `bmingles-remote-file-source2` server in VS Code - Open and run `src/main/groovy/docs-sample.groovy` against the BHS - Should see STDOUT entries in OUTPUT -> Deephaven panel with `"Server"` prefix e.g. ``` 12:16:14.829 STDOUT Server: Notebook Level Var 12:16:14.830 STDOUT Server: Notebook Level Var 12:16:14.830 STDOUT Server: Top Level Var 12:16:14.830 STDOUT Server: Top Level Var ... ``` - Cells in the `testTable` should also include the `"Server"` prefix - Add `src/main/groovy/test` folder as a Groovy remote file source - Run the script again. Should see `"Local"` prefix e.g. ``` 12:21:12.336 STDOUT Local: Notebook Level Var 12:21:12.336 STDOUT Local: Notebook Level Var 12:21:12.337 STDOUT Local: Top Level Var 12:21:12.337 STDOUT Local: Top Level Var ... ``` - Cells in the `testTable` should also include the `"Local"` prefix - Remove the `src/main/groovy/test` as a remote file source - Run the script again. STDOUT and table cells should all go back to the `"Server"` prefixes ### Tree Selection Behavior There was a bug fixed in this PR that impacts how partial unmarking works in the Remote file source tree view. - Navigate to Deephaven activity bar tab (left sidebar) - Expand Groovy workspace tree until you get to `src/main/groovy/nested/ws1` - Add `ws1` folder as remote source with the `+` button - Should see ws1 and all of its children turn green - Hover over `ws1/sub1` subfolder and click the `-` sign to remove it - Result should be that `ws1/sub2` is now the marked folder. `ws1/sub1` and `ws1/file1.groovy` should both be unmarked
|
There's a separate PR to this branch, bmingles#6 that adds more tests and fixes a merge bug. One of those test is failing, this PR has been blocked on not having the tests (and having them pass). The failing use case in the test was that if a Groovy class was already present on the classpath, then the script session with the remote file source feature couldn't replace the file - the server kept using the implementation present on the classpath. The bug didn't seem to occur when running the server normally, suggesting it was a test artifact of some kind. There appear to be two bugs in the PR itself that were preventing the test from passing, and they seem to be legit bugs that ought to prevent normal usage from working correctly as well. First, a quick call tree describing how AbstractScriptSession runs script code
Two bugs in the above, one obvious, one less so:
Draft patch that resolves these two issues, and makes the test pass: diff --git a/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java b/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java
index 89f5efe59b..fbac74a81a 100644
--- a/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java
+++ b/engine/table/src/main/java/io/deephaven/engine/util/GroovyDeephavenSession.java
@@ -359,12 +359,12 @@ public class GroovyDeephavenSession extends AbstractScriptSession<GroovySnapshot
// Create fresh classloader - reusing the existing configurations
GroovyClassLoader scriptClassLoader = new GroovyClassLoader(STATIC_LOADER, scriptConfig);
- // Update the execution context with a fresh QueryCompiler
- final QueryCompiler freshCompiler = QueryCompilerImpl.create(classCacheDirectory, scriptClassLoader);
- executionContext = executionContext.withQueryCompiler(freshCompiler);
-
// Permanently replace groovyShell with fresh shell
groovyShell = new DeephavenGroovyShell(scriptClassLoader, groovyShell.getContext(), consoleConfig);
+
+ // Update the execution context with a fresh QueryCompiler and classloader
+ final QueryCompiler freshCompiler = QueryCompilerImpl.create(classCacheDirectory, scriptClassLoader);
+ executionContext = executionContext.withQueryCompiler(freshCompiler).withClassLoader(groovyShell.getClassLoader());
}
@Override
@@ -399,8 +399,8 @@ public class GroovyDeephavenSession extends AbstractScriptSession<GroovySnapshot
// Execute the script
try (final SafeCloseable ignored = groovyShell.setScriptPrefix(currentScriptName)) {
updateClassloader(lastCommand);
- try {
- ExecutionContext.getContext().getUpdateGraph().exclusiveLock()
+ try (SafeCloseable ignored2 = executionContext.open()){
+ executionContext.getUpdateGraph().exclusiveLock()
.doLockedInterruptibly(() -> groovyShell.evaluate(lastCommand));
} catch (InterruptedException e) {
throw new CancellationException(e.getMessage() != null ? e.getMessage() : "Query interrupted",More broadly though, this intersects with #8065, an attempt to fix a case where we were deleting formula class files after they had been compiled, but before they were loaded and used. This was aimed at being a bandaid fix for a customer issue, but it seems likely to find other ways to come up. Recapping the original requirement there:
But this requirement of being able to delete classes pretty directly conflicts with our desire to keep them around - we generally should draw the line somewhere between "must keep classes around for the duration of the process or other things will break" and "don't bother writing classes to disk at all". I'm inclined to say it should be either one of the extremes, or support a third option that we haven't considered previously: "each round of classes that is written to disk goes into a new directory", enabling groovy to be rewritten at will, but formulas to stick around as needed. In theory, #8065 brings us quite close to the transient extreme, as "every formula can/should be deleted as soon as it has been compiled and loaded", which could let us decouple formula compilation output from groovy output (as formulas need to see groovy, but not each other, and groovy doesn't need to see anything). I think that could make sense as a simpler fourth option, if we think that change is rigorous enough to support lazy usage of formulas. |
DH-20578: This PR implements a RemoteFileSource plugin in Deephaven Core that can request file sources from a remote client. Key points of interest
Related PRs
Testing
Community
:web-client-api:gwtCompile server-jetty-app:run -Panonymous -Pgroovy)Workspace/Groovy/groovy-remote-docs-demofolder underREMOTE IMPORT SOURCESand click the + button to add test folder.Core+
Changes are deployed to
bmingles-remote-file-sourcevm. Can also configure VS Code to connect to https://bmingles-remote-file-source.int.illumon.com:8123/ and run same code