tkobayas commented on code in PR #4032:
URL: 
https://github.com/apache/incubator-kie-tools/pull/4032#discussion_r4180044595


##########
packages/drools-lsp/drools-lsp-server/src/main/java/org/drools/lsp/server/ProjectEngine.java:
##########
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.drools.lsp.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The project's own Drools engine, driven through the bridge class.
+ *
+ * <p>The project loader sees the project's classpath and the platform classes
+ * only. The server's loader is deliberately not its parent: the server jar
+ * carries a DRL parser and an ANTLR runtime of its own, and the project's
+ * versions must win inside the engine. The bridge class is defined from this
+ * server's own jar into a child of the project loader, so its references to
+ * kie-api resolve against the project.
+ */
+final class ProjectEngine implements Engine {
+
+    static final String BRIDGE_CLASS = "org.drools.lsp.bridge.EngineBridge";
+    static final String COMPILER_CLASS = 
"org.drools.compiler.kie.builder.impl.KieServicesImpl";
+    static final String MVEL_CLASS = "org.drools.mvel.MVELConstraint";
+    private static final String BRIDGE_PACKAGE_PREFIX = 
"org.drools.lsp.bridge.";
+    private static final Logger logger = 
Logger.getLogger(ProjectEngine.class.getName());
+
+    private final Set<Path> entries;
+    private final URLClassLoader projectLoader;
+    private final Class<?> bridge;
+    private final Method build;
+    private volatile boolean closed;
+
+    private ProjectEngine(Set<Path> entries, URLClassLoader projectLoader, 
Class<?> bridge, Method build) {
+        this.entries = entries;
+        this.projectLoader = projectLoader;
+        this.bridge = bridge;
+        this.build = build;
+    }
+
+    static ProjectEngine over(Collection<Path> classpathEntries) {
+        Set<Path> entries = new LinkedHashSet<>(classpathEntries);
+        List<URL> urls = new ArrayList<>(entries.size());
+        for (Path entry : entries) {
+            try {
+                urls.add(entry.toUri().toURL());
+            } catch (MalformedURLException e) {
+                logger.fine(() -> "Skipping classpath entry " + entry + ": " + 
e.getMessage());
+            }
+        }
+        URLClassLoader projectLoader = new URLClassLoader("drools-lsp-project",
+                urls.toArray(new URL[0]), 
ClassLoader.getPlatformClassLoader());
+        Class<?> bridge = null;
+        Method build = null;
+        if (hasCompiler(projectLoader)) {
+            try {
+                bridge = Class.forName(BRIDGE_CLASS, true, new 
BridgeClassLoader(projectLoader));
+                build = bridge.getMethod("build", Map.class);
+            } catch (ReflectiveOperationException | LinkageError e) {
+                logger.log(Level.WARNING, "A Drools engine is on the project 
classpath but the compile bridge did not link", e);
+            }
+        }
+        ProjectEngine engine = new ProjectEngine(entries, projectLoader, 
bridge, build);
+        if (build == null) {
+            // Permanently unavailable: release the loader's open jar handles 
now
+            // rather than holding them until the next classpath change 
replaces it.
+            engine.close();
+        }
+        return engine;
+    }
+
+    Set<Path> entries() {
+        return entries;
+    }
+
+    @Override
+    public boolean available() {
+        return build != null && !closed;
+    }
+
+    @Override
+    public List<Map<String, Object>> build(Map<String, String> drlByPath, 
Duration timeout) throws Exception {
+        if (!available()) {
+            throw new IllegalStateException("No Drools engine available");
+        }
+        AtomicReference<Object> outcome = new AtomicReference<>();
+        Thread worker = new Thread(() -> {
+            
Thread.currentThread().setContextClassLoader(bridge.getClassLoader());
+            try {
+                outcome.set(build.invoke(null, drlByPath));
+            } catch (Throwable t) {
+                outcome.set(t);
+            }
+        }, "drools-lsp-engine-build");
+        worker.setDaemon(true);
+        worker.start();
+        worker.join(Math.max(1, timeout.toMillis()));
+        if (worker.isAlive()) {
+            worker.interrupt();
+            throw new TimeoutException("The Drools engine did not finish the 
build within " + timeout.toSeconds()
+                    + " seconds; raise drools.lsp.compile.timeoutSeconds if 
the group needs longer");

Review Comment:
   The copilot's point may not be a real risk. Per Claude: 
   
   The buildExecutor is a single-thread executor, and build() runs inside its 
submitted task. When the timeout fires and TimeoutException is thrown, the task 
exits (caught by the catch (Throwable t) in 
   submit()), and only then does the executor pick up the next queued item. 
setEngine()'s previous::close is also submitted to the same executor, so it 
cannot race with a build in flight. The one-at-a-time 
   guarantee is maintained by the executor's queue, not by the join() call.
   
   The remaining concern — an interrupted daemon thread that ignores the 
interrupt — is real but self-limiting: projectLoader.close() invalidates the 
loader's open JAR handles, so the orphaned thread will hit 
   an IOException on its next class load and stop. The result is discarded 
since build() already returned with TimeoutException.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to