From ceb1efd0e54d99963ea9820cefafffc178daa8a3 Mon Sep 17 00:00:00 2001
From: huangchengxing <841396397@qq.com>
Date: Thu, 27 Oct 2022 18:15:38 +0800
Subject: [PATCH] fix: obtained plugin list may cause thread-safe problems
during iteration
---
.../DefaultThreadPoolPluginManager.java | 49 ++++++++++++++++---
1 file changed, 41 insertions(+), 8 deletions(-)
diff --git a/hippo4j-core/src/main/java/cn/hippo4j/core/plugin/manager/DefaultThreadPoolPluginManager.java b/hippo4j-core/src/main/java/cn/hippo4j/core/plugin/manager/DefaultThreadPoolPluginManager.java
index bf7cf538..50b220d5 100644
--- a/hippo4j-core/src/main/java/cn/hippo4j/core/plugin/manager/DefaultThreadPoolPluginManager.java
+++ b/hippo4j-core/src/main/java/cn/hippo4j/core/plugin/manager/DefaultThreadPoolPluginManager.java
@@ -21,13 +21,33 @@ import cn.hippo4j.common.toolkit.Assert;
import cn.hippo4j.core.plugin.*;
import lombok.NonNull;
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
- * The default implementation of {@link ThreadPoolPluginManager}.
+ *
The default implementation of {@link ThreadPoolPluginManager}.
+ * Provide basic {@link ThreadPoolPlugin} registration, logout and acquisition functions.
+ * Most APIs ensure limited thread-safe.
+ *
+ *
Usually registered to {@link cn.hippo4j.core.executor.ExtensibleThreadPoolExecutor},
+ * or bound to an {@link java.util.concurrent.ThreadPoolExecutor} instance through {@link ThreadPoolPluginSupport}
+ * to support its plugin based extension functions.
+ *
+ *
NOTE:
+ * When the list of plugins is obtained through the {@code getXXX} method of manager, the list is not immutable.
+ * This means that until actually start iterating over the list,
+ * registering or unregistering plugins through the manager will affect the results of the iteration.
+ * Therefore, we should try to ensure that get the latest plugin list from the manager before each use.
+ *
+ * @see cn.hippo4j.core.executor.DynamicThreadPoolExecutor
+ * @see cn.hippo4j.core.executor.ExtensibleThreadPoolExecutor
*/
public class DefaultThreadPoolPluginManager implements ThreadPoolPluginManager {
@@ -39,27 +59,27 @@ public class DefaultThreadPoolPluginManager implements ThreadPoolPluginManager {
/**
* Registered {@link ThreadPoolPlugin}.
*/
- private final Map registeredPlugins = new HashMap<>(16);
+ private final Map registeredPlugins = new ConcurrentHashMap<>(16);
/**
* Registered {@link TaskAwarePlugin}.
*/
- private final List taskAwarePluginList = new ArrayList<>();
+ private final List taskAwarePluginList = new CopyOnWriteArrayList<>();
/**
* Registered {@link ExecuteAwarePlugin}.
*/
- private final List executeAwarePluginList = new ArrayList<>();
+ private final List executeAwarePluginList = new CopyOnWriteArrayList<>();
/**
* Registered {@link RejectedAwarePlugin}.
*/
- private final List rejectedAwarePluginList = new ArrayList<>();
+ private final List rejectedAwarePluginList = new CopyOnWriteArrayList<>();
/**
* Registered {@link ShutdownAwarePlugin}.
*/
- private final List shutdownAwarePluginList = new ArrayList<>();
+ private final List shutdownAwarePluginList = new CopyOnWriteArrayList<>();
/**
* Clear all.
@@ -94,7 +114,7 @@ public class DefaultThreadPoolPluginManager implements ThreadPoolPluginManager {
writeLock.lock();
try {
String id = plugin.getId();
- Assert.isTrue(!isRegistered(id), "The plug-in with id [" + id + "] has been registered");
+ Assert.isTrue(!isRegistered(id), "The plugin with id [" + id + "] has been registered");
// register plugin
registeredPlugins.put(id, plugin);
@@ -171,6 +191,13 @@ public class DefaultThreadPoolPluginManager implements ThreadPoolPluginManager {
}
}
+ /**
+ * Get all registered plugins.
+ *
+ * @return plugins
+ * @apiNote Be sure to avoid directly modifying returned collection instances,
+ * otherwise, unexpected results may be obtained through the manager
+ */
@Override
public Collection getAllPlugins() {
Lock readLock = instanceLock.readLock();
@@ -238,6 +265,8 @@ public class DefaultThreadPoolPluginManager implements ThreadPoolPluginManager {
* Get rejected plugin list.
*
* @return {@link RejectedAwarePlugin}
+ * @apiNote Be sure to avoid directly modifying returned collection instances,
+ * otherwise, unexpected results may be obtained through the manager
*/
@Override
public Collection getRejectedAwarePluginList() {
@@ -254,6 +283,8 @@ public class DefaultThreadPoolPluginManager implements ThreadPoolPluginManager {
* Get shutdown plugin list.
*
* @return {@link ShutdownAwarePlugin}
+ * @apiNote Be sure to avoid directly modifying returned collection instances,
+ * otherwise, unexpected results may be obtained through the manager
*/
@Override
public Collection getShutdownAwarePluginList() {
@@ -270,6 +301,8 @@ public class DefaultThreadPoolPluginManager implements ThreadPoolPluginManager {
* Get shutdown plugin list.
*
* @return {@link ShutdownAwarePlugin}
+ * @apiNote Be sure to avoid directly modifying returned collection instances,
+ * otherwise, unexpected results may be obtained through the manager
*/
@Override
public Collection getTaskAwarePluginList() {