Skip to content

Commit 0fbdf52

Browse files
committed
feat(TransactionRegister): prevent duplicate registration and stabilize CI
Changes: - Add a registration status check to prevent duplicate initialization during CI test runs. - Throw TronError on actuator instantiation failures to improve error visibility and debugging. - Narrow package scanning from "org.tron" to "org.tron.core.actuator" to reduce reflection overhead and speed up registration. - Remove JVM args for CI test to avoid JDK8 G1 GC bugs and Evacuation Pause failures. - Optimize console output to prevent test OOM - Adjust memory and parallelism settings - exclude dnsjava InetAddressResolverProvider
1 parent e19b2a1 commit 0fbdf52

File tree

5 files changed

+204
-13
lines changed

5 files changed

+204
-13
lines changed
Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,49 @@
11
package org.tron.core.utils;
22

33
import java.util.Set;
4+
import java.util.concurrent.atomic.AtomicBoolean;
45
import lombok.extern.slf4j.Slf4j;
56
import org.reflections.Reflections;
67
import org.tron.core.actuator.AbstractActuator;
8+
import org.tron.core.exception.TronError;
79

810
@Slf4j(topic = "TransactionRegister")
911
public class TransactionRegister {
1012

13+
private static final AtomicBoolean REGISTERED = new AtomicBoolean(false);
14+
private static final String PACKAGE_NAME = "org.tron.core.actuator";
15+
1116
public static void registerActuator() {
12-
Reflections reflections = new Reflections("org.tron");
13-
Set<Class<? extends AbstractActuator>> subTypes = reflections
14-
.getSubTypesOf(AbstractActuator.class);
15-
for (Class _class : subTypes) {
16-
try {
17-
_class.newInstance();
18-
} catch (Exception e) {
19-
logger.error("{} contract actuator register fail!", _class, e);
17+
if (REGISTERED.get()) {
18+
logger.info("Actuator already registered.");
19+
return;
20+
}
21+
22+
synchronized (TransactionRegister.class) {
23+
if (REGISTERED.get()) {
24+
logger.info("Actuator already registered.");
25+
return;
26+
}
27+
28+
logger.info("Register actuator start.");
29+
Reflections reflections = new Reflections(PACKAGE_NAME);
30+
Set<Class<? extends AbstractActuator>> subTypes = reflections
31+
.getSubTypesOf(AbstractActuator.class);
32+
33+
for (Class<? extends AbstractActuator> clazz : subTypes) {
34+
try {
35+
logger.debug("Registering actuator: {} start", clazz.getName());
36+
clazz.getDeclaredConstructor().newInstance();
37+
logger.debug("Registering actuator: {} done", clazz.getName());
38+
} catch (Exception e) {
39+
throw new TronError(clazz.getName() + ": "
40+
+ (e.getCause() == null ? e.getMessage() : e.getCause().getMessage()),
41+
e, TronError.ErrCode.ACTUATOR_REGISTER);
42+
}
2043
}
44+
45+
REGISTERED.set(true);
46+
logger.info("Register actuator done, total {}.", subTypes.size());
2147
}
2248
}
23-
2449
}

common/src/main/java/org/tron/core/exception/TronError.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public enum ErrCode {
4949
RATE_LIMITER_INIT(1),
5050
SOLID_NODE_INIT(0),
5151
PARAMETER_INIT(1),
52+
ACTUATOR_REGISTER(1),
5253
JDK_VERSION(1);
5354

5455
private final int code;

framework/build.gradle

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,11 @@ test {
136136
exclude 'org/tron/core/ShieldedTRC20BuilderTest.class'
137137
exclude 'org/tron/common/runtime/vm/WithdrawRewardTest.class'
138138
}
139-
maxHeapSize = "1024m"
139+
maxHeapSize = "512m"
140+
maxParallelForks = Math.max(1, Math.min(2, Runtime.runtime.availableProcessors()))
140141
doFirst {
141142
// Restart the JVM after every 100 tests to avoid memory leaks and ensure test isolation
142143
forkEvery = 100
143-
jvmArgs "-XX:MetaspaceSize=128m","-XX:MaxMetaspaceSize=256m", "-XX:+UseG1GC"
144144
}
145145
}
146146

@@ -175,7 +175,9 @@ def binaryRelease(taskName, jarName, mainClass) {
175175
exclude "META-INF/*.SF"
176176
exclude "META-INF/*.DSA"
177177
exclude "META-INF/*.RSA"
178-
178+
// for service SPI loader for dnsjava
179+
// see https://issues.apache.org/jira/browse/HADOOP-19288
180+
exclude "META-INF/services/java.net.spi.InetAddressResolverProvider"
179181
manifest {
180182
attributes "Main-Class": "${mainClass}"
181183
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package org.tron.core.utils;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertThrows;
6+
import static org.junit.Assert.assertTrue;
7+
import static org.mockito.Mockito.mockConstruction;
8+
import static org.mockito.Mockito.when;
9+
10+
import java.lang.reflect.Field;
11+
import java.util.Collections;
12+
import java.util.HashSet;
13+
import java.util.concurrent.atomic.AtomicBoolean;
14+
import org.junit.After;
15+
import org.junit.Before;
16+
import org.junit.Test;
17+
import org.junit.runner.RunWith;
18+
import org.mockito.MockedConstruction;
19+
import org.mockito.junit.MockitoJUnitRunner;
20+
import org.reflections.Reflections;
21+
import org.tron.core.actuator.AbstractActuator;
22+
import org.tron.core.actuator.TransferActuator;
23+
import org.tron.core.config.args.Args;
24+
import org.tron.core.exception.TronError;
25+
26+
@RunWith(MockitoJUnitRunner.class)
27+
public class TransactionRegisterTest {
28+
29+
@Before
30+
public void init() throws Exception {
31+
Args.getInstance().setActuatorSet(new HashSet<>());
32+
resetRegisteredField();
33+
}
34+
35+
@After
36+
public void destroy() {
37+
Args.clearParam();
38+
}
39+
40+
private void resetRegisteredField() throws Exception {
41+
Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
42+
registeredField.setAccessible(true);
43+
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);
44+
registered.set(false);
45+
}
46+
47+
@Test
48+
public void testAlreadyRegisteredSkipRegistration() throws Exception {
49+
50+
TransactionRegister.registerActuator();
51+
52+
Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
53+
registeredField.setAccessible(true);
54+
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);
55+
assertTrue("First registration should be completed", registered.get());
56+
57+
TransactionRegister.registerActuator();
58+
assertTrue("Registration should still be true", registered.get());
59+
}
60+
61+
@Test
62+
public void testConcurrentAccessThreadSafe() throws Exception {
63+
final int threadCount = 5;
64+
Thread[] threads = new Thread[threadCount];
65+
final AtomicBoolean testPassed = new AtomicBoolean(true);
66+
67+
for (int i = 0; i < threadCount; i++) {
68+
threads[i] = new Thread(() -> {
69+
try {
70+
TransactionRegister.registerActuator();
71+
} catch (Exception e) {
72+
testPassed.set(false);
73+
}
74+
});
75+
}
76+
77+
for (Thread thread : threads) {
78+
thread.start();
79+
}
80+
81+
for (Thread thread : threads) {
82+
thread.join();
83+
}
84+
85+
assertTrue("All threads should complete successfully", testPassed.get());
86+
87+
Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
88+
registeredField.setAccessible(true);
89+
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);
90+
assertTrue("Registration should be completed", registered.get());
91+
}
92+
93+
@Test
94+
public void testDoubleCheckLockingAtomicBoolean() throws Exception {
95+
Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
96+
registeredField.setAccessible(true);
97+
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);
98+
99+
assertFalse("Initial registration state should be false", registered.get());
100+
101+
TransactionRegister.registerActuator();
102+
assertTrue("After first call, should be registered", registered.get());
103+
104+
TransactionRegister.registerActuator();
105+
assertTrue("After second call, should still be registered", registered.get());
106+
}
107+
108+
@Test
109+
public void testSynchronizationBlock() throws Exception {
110+
final AtomicBoolean completedRegistration = new AtomicBoolean(false);
111+
112+
Thread registrationThread = new Thread(() -> {
113+
TransactionRegister.registerActuator();
114+
completedRegistration.set(true);
115+
116+
});
117+
118+
registrationThread.start();
119+
registrationThread.join();
120+
121+
assertTrue("Registration should have completed", completedRegistration.get());
122+
123+
Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
124+
registeredField.setAccessible(true);
125+
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);
126+
assertTrue("Should be registered after completion", registered.get());
127+
}
128+
129+
@Test
130+
public void testMultipleCallsConsistency() throws Exception {
131+
Field registeredField = TransactionRegister.class.getDeclaredField("REGISTERED");
132+
registeredField.setAccessible(true);
133+
AtomicBoolean registered = (AtomicBoolean) registeredField.get(null);
134+
135+
assertFalse("Should start unregistered", registered.get());
136+
137+
TransactionRegister.registerActuator();
138+
139+
assertTrue("Should be registered after first call", registered.get());
140+
141+
for (int i = 0; i < 5; i++) {
142+
TransactionRegister.registerActuator();
143+
assertTrue("Should remain registered after call " + (i + 2), registered.get());
144+
}
145+
}
146+
147+
@Test
148+
public void testThrowsTronError() {
149+
try (MockedConstruction<Reflections> ignored = mockConstruction(Reflections.class,
150+
(mock, context) -> when(mock.getSubTypesOf(AbstractActuator.class))
151+
.thenReturn(Collections.singleton(TransferActuator.class)));
152+
MockedConstruction<TransferActuator> ignored1 = mockConstruction(TransferActuator.class,
153+
(mock, context) -> {
154+
throw new RuntimeException("boom");
155+
})) {
156+
TronError error = assertThrows(TronError.class, TransactionRegister::registerActuator);
157+
assertEquals(TronError.ErrCode.ACTUATOR_REGISTER, error.getErrCode());
158+
assertTrue(error.getMessage().contains("TransferActuator"));
159+
}
160+
}
161+
}

framework/src/test/resources/logback-test.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
<pattern>%d{HH:mm:ss.SSS} %p [%c{1}] %m%n</pattern>
88
</encoder>
99
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
10-
<level>INFO</level>
10+
<level>ERROR</level>
1111
</filter>
1212
</appender>
1313

1414
<appender class="ch.qos.logback.core.rolling.RollingFileAppender"
1515
name="FILE">
1616
<file>./logs/tron-test.log</file>
17+
<bufferSize>8192</bufferSize>
18+
<immediateFlush>true</immediateFlush>
1719
<rollingPolicy
1820
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
1921
<!-- rollover daily -->

0 commit comments

Comments
 (0)