This is an automated email from the ASF dual-hosted git repository. critas pushed a commit to branch wx_fix_external-service_loader in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit c693f14560019ac0064205fd41bbd76f523f2547 Author: CritasWang <[email protected]> AuthorDate: Thu Feb 12 16:32:21 2026 +0800 Fix external service ClassLoader context handling and dependency scope - Store and restore thread context ClassLoader when starting/stopping external services to ensure proper class loading - Remove `provided` scope from jakarta.ws.rs-api dependency in rest module - Remove unneed external-service-impl assembly descriptor from distribution - Add serviceClassLoader field to ServiceInfo for tracking service-specific ClassLoaders --- distribution/pom.xml | 1 - external-service-impl/rest/pom.xml | 1 - .../ExternalServiceManagementService.java | 35 ++++++++++++++++------ .../iotdb/commons/externalservice/ServiceInfo.java | 9 ++++++ 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/distribution/pom.xml b/distribution/pom.xml index 65a3572fd3a..f4773f6afad 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -86,7 +86,6 @@ <descriptor>src/assembly/confignode.xml</descriptor> <descriptor>src/assembly/cli.xml</descriptor> <descriptor>src/assembly/library-udf.xml</descriptor> - <descriptor>src/assembly/external-service-impl.xml</descriptor> </descriptors> <finalName>apache-iotdb-${project.version}</finalName> </configuration> diff --git a/external-service-impl/rest/pom.xml b/external-service-impl/rest/pom.xml index edb8aa3fc33..a5a1d76a1b2 100644 --- a/external-service-impl/rest/pom.xml +++ b/external-service-impl/rest/pom.xml @@ -153,7 +153,6 @@ <dependency> <groupId>jakarta.ws.rs</groupId> <artifactId>jakarta.ws.rs-api</artifactId> - <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.iotdb</groupId> diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/externalservice/ExternalServiceManagementService.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/externalservice/ExternalServiceManagementService.java index 98f231db5b8..1d108bc941a 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/externalservice/ExternalServiceManagementService.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/externalservice/ExternalServiceManagementService.java @@ -159,9 +159,9 @@ public class ExternalServiceManagementService { if (serviceInfo.getServiceInstance() == null) { // lazy create Instance serviceInfo.setServiceInstance( - createExternalServiceInstance(serviceName, serviceInfo.getClassName())); + createExternalServiceInstance(serviceName, serviceInfo.getClassName(), serviceInfo)); } - serviceInfo.getServiceInstance().start(); + runWithServiceClassLoader(serviceInfo, () -> serviceInfo.getServiceInstance().start()); } // 3. persist on CN if service is user-defined, rollback if failed @@ -170,7 +170,7 @@ public class ExternalServiceManagementService { ConfigNodeClientManager.getInstance().borrowClient(ConfigNodeInfo.CONFIG_REGION_ID)) { TSStatus status = client.startExternalService(QueryId.getDataNodeId(), serviceName); if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { - serviceInfo.getServiceInstance().stop(); + runWithServiceClassLoader(serviceInfo, () -> serviceInfo.getServiceInstance().stop()); throw new IoTDBRuntimeException(status.message, status.code); } } @@ -183,12 +183,14 @@ public class ExternalServiceManagementService { } } - private IExternalService createExternalServiceInstance(String serviceName, String className) { + private IExternalService createExternalServiceInstance( + String serviceName, String className, ServiceInfo serviceInfo) { // close ClassLoader automatically to release the file handle try { // Remind: this classLoader should be closed when service is dropped after user-defined // service supported ExternalServiceClassLoader classLoader = new ExternalServiceClassLoader(libRoot); + serviceInfo.setServiceClassLoader(classLoader); return (IExternalService) Class.forName(className, true, classLoader).getDeclaredConstructor().newInstance(); } catch (Throwable t) { @@ -203,6 +205,21 @@ public class ExternalServiceManagementService { } } + private static void runWithServiceClassLoader(ServiceInfo serviceInfo, Runnable action) { + ClassLoader serviceClassLoader = serviceInfo.getServiceClassLoader(); + if (serviceClassLoader == null) { + action.run(); + return; + } + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(serviceClassLoader); + action.run(); + } finally { + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + } + public void stopService(String serviceName) throws ClientManagerException, TException { try { lock.writeLock().lock(); @@ -231,7 +248,7 @@ public class ExternalServiceManagementService { ConfigNodeClientManager.getInstance().borrowClient(ConfigNodeInfo.CONFIG_REGION_ID); ) { TSStatus status = client.stopExternalService(QueryId.getDataNodeId(), serviceName); if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { - serviceInfo.getServiceInstance().start(); + runWithServiceClassLoader(serviceInfo, () -> serviceInfo.getServiceInstance().start()); throw new IoTDBRuntimeException(status.message, status.code); } } @@ -249,7 +266,7 @@ public class ExternalServiceManagementService { serviceInfo.getServiceInstance() != null, INSTANCE_NULL_ERROR_MSG, serviceInfo.getServiceName()); - serviceInfo.getServiceInstance().stop(); + runWithServiceClassLoader(serviceInfo, () -> serviceInfo.getServiceInstance().stop()); } public void dropService(String serviceName, boolean forcedly) @@ -325,11 +342,11 @@ public class ExternalServiceManagementService { if (serviceInfo.getState() == RUNNING) { IExternalService serviceInstance = createExternalServiceInstance( - serviceInfo.getServiceName(), serviceInfo.getClassName()); + serviceInfo.getServiceName(), serviceInfo.getClassName(), serviceInfo); checkState( serviceInstance != null, INSTANCE_NULL_ERROR_MSG, serviceInfo.getServiceName()); serviceInfo.setServiceInstance(serviceInstance); - serviceInstance.start(); + runWithServiceClassLoader(serviceInfo, serviceInstance::start); } }); } @@ -346,7 +363,7 @@ public class ExternalServiceManagementService { // service in restoreRunningServiceInstance method if (serviceInstance != null) { // only stop the instance successfully started - serviceInstance.stop(); + runWithServiceClassLoader(serviceInfo, serviceInstance::stop); } } }); diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/externalservice/ServiceInfo.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/externalservice/ServiceInfo.java index f9ef9a73c02..7a933812fb4 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/externalservice/ServiceInfo.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/externalservice/ServiceInfo.java @@ -36,6 +36,7 @@ public class ServiceInfo { private State state; private transient IExternalService serviceInstance; + private transient ClassLoader serviceClassLoader; public ServiceInfo(String serviceName, String className, ServiceType serviceType) { this.serviceName = serviceName; @@ -79,6 +80,14 @@ public class ServiceInfo { this.serviceInstance = serviceInstance; } + public ClassLoader getServiceClassLoader() { + return serviceClassLoader; + } + + public void setServiceClassLoader(ClassLoader serviceClassLoader) { + this.serviceClassLoader = serviceClassLoader; + } + public void serialize(OutputStream stream) throws IOException { ReadWriteIOUtils.write(serviceName, stream); ReadWriteIOUtils.write(className, stream);
