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]
