diff --git a/changelog.html b/changelog.html index ee342bf19..0f0f49553 100644 --- a/changelog.html +++ b/changelog.html @@ -54,6 +54,7 @@

  • [Issue #71] - Send member joined notification across the cluster on clustering start
  • [Issue #74] - Warn against usage of plugin-provided classes in Hazelcast
  • [Issue #76] - Finish leftCluster handling before invoking joinCluster handlers
  • +
  • [Issue #79] - Use plugin classloader when executing task defined in a plugin
  • 2.5.1 -- September 29, 2021.

    diff --git a/src/java/org/jivesoftware/openfire/plugin/util/cache/ClusteredCacheFactory.java b/src/java/org/jivesoftware/openfire/plugin/util/cache/ClusteredCacheFactory.java index 2bc522a5b..15b1f3aa3 100644 --- a/src/java/org/jivesoftware/openfire/plugin/util/cache/ClusteredCacheFactory.java +++ b/src/java/org/jivesoftware/openfire/plugin/util/cache/ClusteredCacheFactory.java @@ -430,9 +430,14 @@ public Collection doSynchronousClusterTask(final ClusterTask task, fin final Collection result = new ArrayList<>(); if (!members.isEmpty()) { // Asynchronously execute the task on the other cluster members + logger.debug("Executing MultiTask: " + task.getClass().getName()); + final PluginClassLoader pluginClassLoader = checkForPluginClassLoader(task); + final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try { - logger.debug("Executing MultiTask: " + task.getClass().getName()); - checkForPluginClassLoader(task); + // Switch to the classloader that provides the plugin classes, if needed. + if (pluginClassLoader != null) { + Thread.currentThread().setContextClassLoader(pluginClassLoader); + } final Map> futures = hazelcast.getExecutorService(HAZELCAST_EXECUTOR_SERVICE_NAME.getValue()).submitToMembers(new CallableTask<>(task), members); long nanosLeft = TimeUnit.SECONDS.toNanos(MAX_CLUSTER_EXECUTION_TIME.getValue().getSeconds() * members.size()); for (final Future future : futures.values()) { @@ -444,6 +449,11 @@ public Collection doSynchronousClusterTask(final ClusterTask task, fin logger.error("Failed to execute cluster task within " + StringUtils.getFullElapsedTime(MAX_CLUSTER_EXECUTION_TIME.getValue()), te); } catch (final Exception e) { logger.error("Failed to execute cluster task", e); + } finally { + if (pluginClassLoader != null) { + // Revert back to the original classloader. + Thread.currentThread().setContextClassLoader(contextClassLoader); + } } } else { logger.debug("No cluster members selected for cluster task " + task.getClass().getName()); @@ -467,8 +477,13 @@ public T doSynchronousClusterTask(final ClusterTask task, final byte[] no if (member != null) { // Asynchronously execute the task on the target member logger.debug("Executing DistributedTask: " + task.getClass().getName()); - checkForPluginClassLoader(task); + final PluginClassLoader pluginClassLoader = checkForPluginClassLoader(task); + final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try { + // Switch to the classloader that provides the plugin classes, if needed. + if (pluginClassLoader != null) { + Thread.currentThread().setContextClassLoader(pluginClassLoader); + } final Future future = hazelcast.getExecutorService(HAZELCAST_EXECUTOR_SERVICE_NAME.getValue()).submitToMember(new CallableTask<>(task), member); result = future.get(MAX_CLUSTER_EXECUTION_TIME.getValue().getSeconds(), TimeUnit.SECONDS); logger.trace("DistributedTask result: {}", result); @@ -476,6 +491,11 @@ public T doSynchronousClusterTask(final ClusterTask task, final byte[] no logger.error("Failed to execute cluster task within " + MAX_CLUSTER_EXECUTION_TIME + " seconds", te); } catch (final Exception e) { logger.error("Failed to execute cluster task", e); + } finally { + if (pluginClassLoader != null) { + // Revert back to the original classloader. + Thread.currentThread().setContextClassLoader(contextClassLoader); + } } } else { final String msg = MessageFormat.format("Requested node {0} not found in cluster", new String(nodeID, StandardCharsets.UTF_8)); @@ -557,11 +577,12 @@ public Lock getLock(final Object key, Cache cache) { * limited by a time interval. * * @param o the instance for which to verify the class loader + * @return The PluginClassLoader that was used to load the instance, if it was loaded by a plugin * @see Issue #74: Warn against usage of plugin-provided classes in Hazelcast */ - protected > void checkForPluginClassLoader(final T o) { - if (o != null && o.getClass().getClassLoader() instanceof PluginClassLoader - && !pluginClassLoaderWarnings.containsKey(o.getClass().getName()) ) + protected > PluginClassLoader checkForPluginClassLoader(final T o) { + PluginClassLoader result = null; + if (o != null && o.getClass().getClassLoader() instanceof PluginClassLoader) { // Try to determine what plugin loaded the offending class. String pluginName = null; @@ -571,17 +592,23 @@ protected > void checkForPluginClassLoader(final T o) { final PluginClassLoader pluginClassloader = XMPPServer.getInstance().getPluginManager().getPluginClassloader(plugin); if (o.getClass().getClassLoader().equals(pluginClassloader)) { pluginName = XMPPServer.getInstance().getPluginManager().getCanonicalName(plugin); + result = pluginClassloader; break; } } } catch (Exception e) { logger.debug("An exception occurred while trying to determine the plugin class loader that loaded an instance of {}", o.getClass(), e); } - logger.warn("An instance of {} that is executed as a cluster task. This will cause issues when reloading " + - "the plugin that provides this class. The plugin implementation should be modified.", - pluginName != null ? o.getClass() + " (provided by plugin " + pluginName + ")" : o.getClass()); - pluginClassLoaderWarnings.put(o.getClass().getName(), Instant.now()); // Note that this Instant is unused. + + // Only print this warning once in a while (a cache entry that is added will eventually be evicted). + if (!pluginClassLoaderWarnings.containsKey(o.getClass().getName())) { + logger.warn("An instance of {} that is executed as a cluster task. This will cause issues when reloading " + + "the plugin that provides this class. The plugin implementation should be modified.", + pluginName != null ? o.getClass() + " (provided by plugin " + pluginName + ")" : o.getClass()); + pluginClassLoaderWarnings.put(o.getClass().getName(), Instant.now()); // Note that this Instant is unused. + } } + return result; } private static class ClusterLock implements Lock {