Skip to content

Commit 0ebf4c3

Browse files
authored
Merge pull request #6447 from halibobo1205/feat/register_actuator_opt
feat(actuator): prevent duplicate registration and stabilize CI
2 parents 09127ab + 7c8746e commit 0ebf4c3

5 files changed

Lines changed: 230 additions & 54 deletions

File tree

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,58 @@
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.debug("Actuator already registered.");
19+
return;
20+
}
21+
22+
synchronized (TransactionRegister.class) {
23+
if (REGISTERED.get()) {
24+
logger.debug("Actuator already registered.");
25+
return;
26+
}
27+
logger.debug("Register actuator start.");
28+
Reflections reflections = new Reflections(PACKAGE_NAME);
29+
Set<Class<? extends AbstractActuator>> subTypes = reflections
30+
.getSubTypesOf(AbstractActuator.class);
31+
32+
for (Class<? extends AbstractActuator> clazz : subTypes) {
33+
try {
34+
logger.debug("Registering actuator: {} start", clazz.getName());
35+
clazz.getDeclaredConstructor().newInstance();
36+
logger.debug("Registering actuator: {} done", clazz.getName());
37+
} catch (Exception e) {
38+
Throwable cause = e.getCause() != null ? e.getCause() : e;
39+
String detail = cause.getMessage() != null ? cause.getMessage() : cause.toString();
40+
throw new TronError(clazz.getName() + ": " + detail,
41+
e, TronError.ErrCode.ACTUATOR_REGISTER);
42+
}
2043
}
44+
45+
REGISTERED.set(true);
46+
logger.debug("Register actuator done, total {}.", subTypes.size());
2147
}
2248
}
2349

50+
static boolean isRegistered() {
51+
return REGISTERED.get();
52+
}
53+
54+
// For testing only — resets registration state between tests.
55+
static void resetForTesting() {
56+
REGISTERED.set(false);
57+
}
2458
}

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: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ static def isWindows() {
3030
return org.gradle.internal.os.OperatingSystem.current().isWindows()
3131
}
3232

33-
task version(type: Exec) {
33+
tasks.register('version', Exec) {
3434
commandLine 'bash', '-c', '../ver.sh'
3535
}
3636

@@ -78,7 +78,7 @@ checkstyleMain {
7878
source = 'src/main/java'
7979
}
8080

81-
task lint(type: Checkstyle) {
81+
tasks.register('lint', Checkstyle) {
8282
// Cleaning the old log because of the creation of the new ones (not sure if totaly needed)
8383
delete fileTree(dir: "${project.rootDir}/app/build/reports")
8484
source 'src'
@@ -123,10 +123,10 @@ def configureTestTask = { Task t ->
123123
t.exclude 'org/tron/core/ShieldedTRC20BuilderTest.class'
124124
t.exclude 'org/tron/common/runtime/vm/WithdrawRewardTest.class'
125125
}
126-
t.maxHeapSize = "1024m"
126+
t.maxHeapSize = "512m"
127+
t.maxParallelForks = Math.max(1, Math.min(4, Runtime.runtime.availableProcessors()))
127128
t.doFirst {
128129
t.forkEvery = 100
129-
t.jvmArgs "-XX:MetaspaceSize=128m", "-XX:MaxMetaspaceSize=256m", "-XX:+UseG1GC"
130130
}
131131
}
132132

@@ -150,7 +150,7 @@ test {
150150
}
151151
}
152152

153-
task testWithRocksDb(type: Test) {
153+
tasks.register('testWithRocksDb', Test) {
154154
description = 'Run tests with RocksDB engine'
155155
group = 'verification'
156156
configureTestTask(it)
@@ -197,7 +197,9 @@ def binaryRelease(taskName, jarName, mainClass) {
197197
exclude "META-INF/*.SF"
198198
exclude "META-INF/*.DSA"
199199
exclude "META-INF/*.RSA"
200-
200+
// for service SPI loader for dnsjava
201+
// see https://issues.apache.org/jira/browse/HADOOP-19288
202+
exclude "META-INF/services/java.net.spi.InetAddressResolverProvider"
201203
manifest {
202204
attributes "Main-Class": "${mainClass}"
203205
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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.util.Collections;
11+
import java.util.HashSet;
12+
import java.util.concurrent.atomic.AtomicBoolean;
13+
import java.util.concurrent.atomic.AtomicInteger;
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() {
31+
Args.getInstance().setActuatorSet(new HashSet<>());
32+
TransactionRegister.resetForTesting();
33+
}
34+
35+
@After
36+
public void destroy() {
37+
Args.clearParam();
38+
}
39+
40+
@Test
41+
public void testAlreadyRegisteredSkipRegistration() {
42+
TransactionRegister.registerActuator();
43+
assertTrue("First registration should be completed", TransactionRegister.isRegistered());
44+
45+
TransactionRegister.registerActuator();
46+
assertTrue("Registration should still be true", TransactionRegister.isRegistered());
47+
}
48+
49+
@Test
50+
public void testConcurrentAccessThreadSafe() throws InterruptedException {
51+
final int threadCount = 5;
52+
Thread[] threads = new Thread[threadCount];
53+
final AtomicBoolean testPassed = new AtomicBoolean(true);
54+
55+
for (int i = 0; i < threadCount; i++) {
56+
threads[i] = new Thread(() -> {
57+
try {
58+
TransactionRegister.registerActuator();
59+
} catch (Throwable e) {
60+
testPassed.set(false);
61+
}
62+
});
63+
}
64+
65+
for (Thread thread : threads) {
66+
thread.start();
67+
}
68+
69+
for (Thread thread : threads) {
70+
thread.join();
71+
}
72+
73+
assertTrue("All threads should complete without exceptions", testPassed.get());
74+
assertTrue("Registration should be completed", TransactionRegister.isRegistered());
75+
}
76+
77+
@Test
78+
public void testDoubleCheckLockingAtomicBoolean() {
79+
assertFalse("Initial registration state should be false", TransactionRegister.isRegistered());
80+
81+
TransactionRegister.registerActuator();
82+
assertTrue("After first call, should be registered", TransactionRegister.isRegistered());
83+
84+
TransactionRegister.registerActuator();
85+
assertTrue("After second call, should still be registered", TransactionRegister.isRegistered());
86+
}
87+
88+
@Test
89+
public void testRegistrationRunsExactlyOnce() {
90+
final AtomicInteger constructorCallCount = new AtomicInteger(0);
91+
92+
try (MockedConstruction<Reflections> ignored = mockConstruction(Reflections.class,
93+
(mock, context) -> {
94+
constructorCallCount.incrementAndGet();
95+
when(mock.getSubTypesOf(AbstractActuator.class)).thenReturn(Collections.emptySet());
96+
})) {
97+
98+
// Call multiple times; Reflections should only be constructed once
99+
for (int i = 0; i < 5; i++) {
100+
TransactionRegister.registerActuator();
101+
}
102+
103+
assertEquals("Reflections should be constructed exactly once regardless of call count",
104+
1, constructorCallCount.get());
105+
assertTrue(TransactionRegister.isRegistered());
106+
}
107+
}
108+
109+
@Test
110+
public void testMultipleCallsConsistency() {
111+
assertFalse("Should start unregistered", TransactionRegister.isRegistered());
112+
113+
TransactionRegister.registerActuator();
114+
assertTrue("Should be registered after first call", TransactionRegister.isRegistered());
115+
116+
for (int i = 0; i < 5; i++) {
117+
TransactionRegister.registerActuator();
118+
assertTrue("Should remain registered after call " + (i + 2),
119+
TransactionRegister.isRegistered());
120+
}
121+
}
122+
123+
@Test
124+
public void testThrowsTronError() {
125+
try (MockedConstruction<Reflections> ignored = mockConstruction(Reflections.class,
126+
(mock, context) -> when(mock.getSubTypesOf(AbstractActuator.class))
127+
.thenReturn(Collections.singleton(TransferActuator.class)));
128+
MockedConstruction<TransferActuator> ignored1 = mockConstruction(TransferActuator.class,
129+
(mock, context) -> {
130+
throw new RuntimeException("boom");
131+
})) {
132+
TronError error = assertThrows(TronError.class, TransactionRegister::registerActuator);
133+
assertEquals(TronError.ErrCode.ACTUATOR_REGISTER, error.getErrCode());
134+
assertTrue(error.getMessage().contains("TransferActuator"));
135+
}
136+
}
137+
}
Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,42 @@
1-
<configuration>
2-
3-
<!-- FILE appender is disabled -->
4-
5-
<appender class="ch.qos.logback.core.ConsoleAppender" name="STDOUT">
6-
<encoder>
7-
<pattern>%d{HH:mm:ss.SSS} %p [%c{1}] %m%n</pattern>
8-
</encoder>
9-
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
10-
<level>INFO</level>
11-
</filter>
12-
</appender>
13-
14-
<appender class="ch.qos.logback.core.rolling.RollingFileAppender"
15-
name="FILE">
16-
<file>./logs/tron-test.log</file>
17-
<rollingPolicy
18-
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
19-
<!-- rollover daily -->
20-
<fileNamePattern>./logs/tron-test-%d{yyyy-MM-dd}.%i.log.zip
21-
</fileNamePattern>
22-
<!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
23-
<maxFileSize>100MB</maxFileSize>
24-
<maxHistory>60</maxHistory>
25-
<totalSizeCap>20GB</totalSizeCap>
26-
</rollingPolicy>
27-
<encoder>
28-
<pattern>%d{HH:mm:ss.SSS} %p [%c{1}] %m%n</pattern>
29-
</encoder>
30-
</appender>
31-
32-
<root level="DEBUG">
33-
<appender-ref ref="STDOUT"/>
34-
<appender-ref ref="FILE"/>
35-
</root>
36-
37-
<logger level="DEBUG" name="Test"/>
38-
<logger level="DEBUG" name="Manager"/>
39-
40-
</configuration>
1+
<configuration>
2+
3+
<!-- FILE appender is disabled -->
4+
5+
<appender class="ch.qos.logback.core.ConsoleAppender" name="STDOUT">
6+
<encoder>
7+
<pattern>%d{HH:mm:ss.SSS} %p [%c{1}] %m%n</pattern>
8+
</encoder>
9+
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
10+
<level>${console.log.level:-ERROR}</level>
11+
</filter>
12+
</appender>
13+
14+
<appender class="ch.qos.logback.core.rolling.RollingFileAppender"
15+
name="FILE">
16+
<file>./logs/tron-test.log</file>
17+
<bufferSize>8192</bufferSize>
18+
<immediateFlush>true</immediateFlush>
19+
<rollingPolicy
20+
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
21+
<!-- rollover daily -->
22+
<fileNamePattern>./logs/tron-test-%d{yyyy-MM-dd}.%i.log.zip
23+
</fileNamePattern>
24+
<!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
25+
<maxFileSize>100MB</maxFileSize>
26+
<maxHistory>60</maxHistory>
27+
<totalSizeCap>20GB</totalSizeCap>
28+
</rollingPolicy>
29+
<encoder>
30+
<pattern>%d{HH:mm:ss.SSS} %p [%c{1}] %m%n</pattern>
31+
</encoder>
32+
</appender>
33+
34+
<root level="DEBUG">
35+
<appender-ref ref="STDOUT"/>
36+
<appender-ref ref="FILE"/>
37+
</root>
38+
39+
<logger level="DEBUG" name="Test"/>
40+
<logger level="DEBUG" name="Manager"/>
41+
42+
</configuration>

0 commit comments

Comments
 (0)