Avoid register multiple cleaner task for same thread's FastThreadLocal index

Motivation:

Currently if user call set/remove/set/remove many times, it will create multiple cleaner task for same index. It may cause OOM due to long live thread will have more and more task in LIVE_SET.

Modification:

Add flag to avoid recreating tasks.

Result:
Only create 1 clean task. But use more space of indexedVariables.
This commit is contained in:
koji lin 2018-02-02 00:47:04 +09:00 committed by Norman Maurer
parent 7bbb4ef8a2
commit 1e70d3092d
3 changed files with 88 additions and 14 deletions

View File

@ -125,8 +125,11 @@ public class FastThreadLocal<V> {
private final int index; private final int index;
private final int cleanerFlagIndex;
public FastThreadLocal() { public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex(); index = InternalThreadLocalMap.nextVariableIndex();
cleanerFlagIndex = InternalThreadLocalMap.nextVariableIndex();
} }
/** /**
@ -147,7 +150,14 @@ public class FastThreadLocal<V> {
private void registerCleaner(final InternalThreadLocalMap threadLocalMap) { private void registerCleaner(final InternalThreadLocalMap threadLocalMap) {
Thread current = Thread.currentThread(); Thread current = Thread.currentThread();
if (!FastThreadLocalThread.willCleanupFastThreadLocals(current)) { if (FastThreadLocalThread.willCleanupFastThreadLocals(current) ||
threadLocalMap.indexedVariable(cleanerFlagIndex) != InternalThreadLocalMap.UNSET) {
return;
}
// removeIndexedVariable(cleanerFlagIndex) isn't necessary because the finally cleanup is tied to the lifetime
// of the thread, and this Object will be discarded if the associated thread is GCed.
threadLocalMap.setIndexedVariable(cleanerFlagIndex, Boolean.TRUE);
// We will need to ensure we will trigger remove(InternalThreadLocalMap) so everything will be released // We will need to ensure we will trigger remove(InternalThreadLocalMap) so everything will be released
// and FastThreadLocal.onRemoval(...) will be called. // and FastThreadLocal.onRemoval(...) will be called.
ObjectCleaner.register(current, new Runnable() { ObjectCleaner.register(current, new Runnable() {
@ -160,7 +170,6 @@ public class FastThreadLocal<V> {
} }
}); });
} }
}
/** /**
* Returns the current value for the specified thread local map. * Returns the current value for the specified thread local map.

View File

@ -123,6 +123,10 @@ public final class ObjectCleaner {
} }
} }
public static int getLiveSetCount() {
return LIVE_SET.size();
}
private ObjectCleaner() { private ObjectCleaner() {
// Only contains a static method. // Only contains a static method.
} }

View File

@ -16,6 +16,7 @@
package io.netty.util.concurrent; package io.netty.util.concurrent;
import io.netty.util.internal.ObjectCleaner;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -24,7 +25,8 @@ import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class FastThreadLocalTest { public class FastThreadLocalTest {
@Before @Before
@ -45,7 +47,7 @@ public class FastThreadLocalTest {
// Initialize a thread-local variable. // Initialize a thread-local variable.
assertThat(var.get(), is(nullValue())); assertThat(var.get(), is(nullValue()));
assertThat(FastThreadLocal.size(), is(1)); assertThat(FastThreadLocal.size(), is(2));
// And then remove it. // And then remove it.
FastThreadLocal.removeAll(); FastThreadLocal.removeAll();
@ -76,6 +78,65 @@ public class FastThreadLocalTest {
} }
} }
@Test
public void testMultipleSetRemove() throws Exception {
final FastThreadLocal<String> threadLocal = new FastThreadLocal<String>();
final Runnable runnable = new Runnable() {
@Override
public void run() {
threadLocal.set("1");
threadLocal.remove();
threadLocal.set("2");
threadLocal.remove();
}
};
final int sizeWhenStart = ObjectCleaner.getLiveSetCount();
Thread thread = new Thread(runnable);
thread.start();
thread.join();
assertEquals(1, ObjectCleaner.getLiveSetCount() - sizeWhenStart);
Thread thread2 = new Thread(runnable);
thread2.start();
thread2.join();
assertEquals(2, ObjectCleaner.getLiveSetCount() - sizeWhenStart);
}
@Test
public void testMultipleSetRemove_multipleThreadLocal() throws Exception {
final FastThreadLocal<String> threadLocal = new FastThreadLocal<String>();
final FastThreadLocal<String> threadLocal2 = new FastThreadLocal<String>();
final Runnable runnable = new Runnable() {
@Override
public void run() {
threadLocal.set("1");
threadLocal.remove();
threadLocal.set("2");
threadLocal.remove();
threadLocal2.set("1");
threadLocal2.remove();
threadLocal2.set("2");
threadLocal2.remove();
}
};
final int sizeWhenStart = ObjectCleaner.getLiveSetCount();
Thread thread = new Thread(runnable);
thread.start();
thread.join();
assertEquals(2, ObjectCleaner.getLiveSetCount() - sizeWhenStart);
Thread thread2 = new Thread(runnable);
thread2.start();
thread2.join();
assertEquals(4, ObjectCleaner.getLiveSetCount() - sizeWhenStart);
}
@Test(timeout = 4000) @Test(timeout = 4000)
public void testOnRemoveCalledForFastThreadLocalGet() throws Exception { public void testOnRemoveCalledForFastThreadLocalGet() throws Exception {
testOnRemoveCalled(true, true); testOnRemoveCalled(true, true);