Repository: pinpoint-apm/pinpoint
Issue: Fix potential thread safe problem in InterceptorScope
Labels: bug

InterceptorScopePool Field of DefaultProfilerPluginContext is not thread safe.

fixed version : 1.6.0-RC2

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/ConcurrentPool.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/ConcurrentPool.java
new file mode 100644
index 000000000000..8d8c83b031b5
--- /dev/null
+++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/ConcurrentPool.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016 NAVER Corp.
+ *
+ * Licensed 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 com.navercorp.pinpoint.profiler.context.scope;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * @author Woonduk Kang(emeroad)
+ */
+public class ConcurrentPool<K, V> implements Pool<K, V> {
+
+    private final ConcurrentMap<K, V> pool = new ConcurrentHashMap<K, V>();
+
+    private final PoolObjectFactory<K, V> objectFactory;
+
+    public ConcurrentPool(PoolObjectFactory<K, V> objectFactory) {
+        if (objectFactory == null) {
+            throw new NullPointerException("objectFactory must not be null");
+        }
+        this.objectFactory = objectFactory;
+    }
+
+    @Override
+    public V get(K key) {
+        if (key == null) {
+            throw new IllegalArgumentException("key must not be null");
+        }
+
+        return pool.get(key);
+    }
+
+    @Override
+    public V add(K key) {
+        if (key == null) {
+            throw new IllegalArgumentException("name must not be null");
+        }
+
+        final V alreadyExist = this.pool.get(key);
+        if (alreadyExist != null) {
+            return alreadyExist;
+        }
+
+        final V newValue = this.objectFactory.create(key);
+        final V oldValue = this.pool.putIfAbsent(key, newValue);
+        if (oldValue != null) {
+            return oldValue;
+        }
+        return newValue;
+    }
+
+
+}
diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/InterceptorScopeFactory.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/InterceptorScopeFactory.java
new file mode 100644
index 000000000000..f72fd5b5cce9
--- /dev/null
+++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/InterceptorScopeFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 NAVER Corp.
+ *
+ * Licensed 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 com.navercorp.pinpoint.profiler.context.scope;
+
+import com.navercorp.pinpoint.bootstrap.interceptor.scope.InterceptorScope;
+import com.navercorp.pinpoint.profiler.interceptor.scope.DefaultInterceptorScope;
+
+/**
+ * @author Woonduk Kang(emeroad)
+ */
+public class InterceptorScopeFactory implements PoolObjectFactory<String, InterceptorScope> {
+    @Override
+    public InterceptorScope create(String scopeName) {
+        return new DefaultInterceptorScope(scopeName);
+    }
+}
diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/Pool.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/Pool.java
new file mode 100644
index 000000000000..44259a89d0ef
--- /dev/null
+++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/Pool.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 NAVER Corp.
+ *
+ * Licensed 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 com.navercorp.pinpoint.profiler.context.scope;
+
+/**
+ * @author Woonduk Kang(emeroad)
+ */
+public interface Pool<K, V> {
+
+    V get(K key);
+
+    V add(K key);
+
+}
diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/PoolObjectFactory.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/PoolObjectFactory.java
new file mode 100644
index 000000000000..707c569dd611
--- /dev/null
+++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/scope/PoolObjectFactory.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016 NAVER Corp.
+ *
+ * Licensed 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 com.navercorp.pinpoint.profiler.context.scope;
+
+/**
+ * @author Woonduk Kang(emeroad)
+ */
+public interface PoolObjectFactory<K, V> {
+    V create(K key);
+}
diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/DefaultProfilerPluginContext.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/DefaultProfilerPluginContext.java
index 87c3875ade85..a2eb93556591 100644
--- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/DefaultProfilerPluginContext.java
+++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/DefaultProfilerPluginContext.java
@@ -34,11 +34,13 @@
 import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPluginSetupContext;
 import com.navercorp.pinpoint.profiler.DefaultAgent;
 import com.navercorp.pinpoint.profiler.DynamicTransformService;
+import com.navercorp.pinpoint.profiler.context.scope.ConcurrentPool;
+import com.navercorp.pinpoint.profiler.context.scope.InterceptorScopeFactory;
+import com.navercorp.pinpoint.profiler.context.scope.Pool;
 import com.navercorp.pinpoint.profiler.instrument.ClassInjector;
 import com.navercorp.pinpoint.profiler.instrument.PluginClassInjector;
-import com.navercorp.pinpoint.profiler.interceptor.scope.DefaultInterceptorScope;
 import com.navercorp.pinpoint.profiler.util.JavaAssistUtils;
-import com.navercorp.pinpoint.profiler.util.NameValueList;
+
 
 public class DefaultProfilerPluginContext implements ProfilerPluginSetupContext, InstrumentContext {
     private final DefaultAgent agent;
@@ -47,7 +49,7 @@ public class DefaultProfilerPluginContext implements ProfilerPluginSetupContext,
     private final List<ApplicationTypeDetector> serverTypeDetectors = new ArrayList<ApplicationTypeDetector>();
     private final List<ClassFileTransformer> classTransformers = new ArrayList<ClassFileTransformer>();
     
-    private final NameValueList<InterceptorScope> interceptorScopeList = new NameValueList<InterceptorScope>();
+    private final Pool<String, InterceptorScope> interceptorScopePool = new ConcurrentPool<String, InterceptorScope>(new InterceptorScopeFactory());
 
     public DefaultProfilerPluginContext(DefaultAgent agent, ClassInjector classInjector) {
         if (agent == null) {
@@ -187,13 +189,7 @@ public InterceptorScope getInterceptorScope(String name) {
         if (name == null) {
             throw new NullPointerException("name must not be null");
         }
-        InterceptorScope scope = interceptorScopeList.get(name);
-        
-        if (scope == null) {
-            scope = new DefaultInterceptorScope(name);
-            interceptorScopeList.add(name, scope);
-        }
-        
-        return scope;
+
+        return interceptorScopePool.add(name);
     }
 }
diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/xml/interceptor/AnnotatedInterceptorInjector.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/xml/interceptor/AnnotatedInterceptorInjector.java
index 9fa30d0a69b7..8edddd903935 100644
--- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/xml/interceptor/AnnotatedInterceptorInjector.java
+++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/xml/interceptor/AnnotatedInterceptorInjector.java
@@ -60,7 +60,7 @@ private InterceptorScope getScope() {
         if (scopeName == null) {
             return null;
         } else {
-            return  pluginContext.getInterceptorScope(scopeName);
+            return pluginContext.getInterceptorScope(scopeName);
         }
     }
 
diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/util/NameValueList.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/util/NameValueList.java
index f01b7d09ea76..a62c064d1554 100644
--- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/util/NameValueList.java
+++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/util/NameValueList.java
@@ -15,13 +15,14 @@
 package com.navercorp.pinpoint.profiler.util;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @author Jongho Moon
  *
  */
 public class NameValueList<T> {
-    private final ArrayList<NameValue<T>> list;
+    private final List<NameValue<T>> list;
     
     public NameValueList() {
         this(0);


Test patch:
diff --git a/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/scope/ConcurrentPoolTest.java b/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/scope/ConcurrentPoolTest.java
new file mode 100644
index 000000000000..0f08dbd40e40
--- /dev/null
+++ b/profiler/src/test/java/com/navercorp/pinpoint/profiler/context/scope/ConcurrentPoolTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 NAVER Corp.
+ *
+ * Licensed 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 com.navercorp.pinpoint.profiler.context.scope;
+
+import com.navercorp.pinpoint.bootstrap.interceptor.scope.InterceptorScope;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Woonduk Kang(emeroad)
+ */
+public class ConcurrentPoolTest {
+    @Test
+    public void testConcurrentPool() throws Exception {
+
+        InterceptorScopeFactory traceScopeFactory = new InterceptorScopeFactory();
+        Pool<String, InterceptorScope> pool = new ConcurrentPool<String, InterceptorScope>(traceScopeFactory);
+
+        final String OBJECT_NAME = "test";
+
+        Assert.assertNull(pool.get(OBJECT_NAME));
+
+        InterceptorScope addedScope = pool.add(OBJECT_NAME);
+        Assert.assertSame(pool.get(OBJECT_NAME), addedScope);
+
+        InterceptorScope exist = pool.add(OBJECT_NAME);
+        Assert.assertSame(exist, addedScope);
+    }
+
+}
\ No newline at end of file