Avoid ConcurrentModificationException when getting machine map in AppInfo (#205)

- Avoid ConcurrentModificationException when `getMachines()` by deep clone the machine map
- Enhance the code to avoid potential ConcurrentModificationException
- Add test cases
This commit is contained in:
Carpenter Lee 2018-10-29 20:19:29 +08:00 committed by Eric Zhao
parent 0c15dd9fe3
commit bd0de9464c
4 changed files with 75 additions and 24 deletions

View File

@ -99,6 +99,11 @@
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -15,15 +15,16 @@
*/
package com.taobao.csp.sentinel.dashboard.discovery;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
public class AppInfo {
private String app = "";
private Set<MachineInfo> machines = new TreeSet<MachineInfo>();
private Set<MachineInfo> machines = ConcurrentHashMap.newKeySet();
public AppInfo() {
}
@ -40,8 +41,13 @@ public class AppInfo {
this.app = app;
}
public synchronized Set<MachineInfo> getMachines() {
return machines;
/**
* Get the current machines.
*
* @return a new copy of the current machines.
*/
public Set<MachineInfo> getMachines() {
return new HashSet<>(machines);
}
@Override
@ -49,7 +55,7 @@ public class AppInfo {
return "AppInfo{" + "app='" + app + ", machines=" + machines + '}';
}
public synchronized boolean addMachine(MachineInfo machineInfo) {
public boolean addMachine(MachineInfo machineInfo) {
machines.remove(machineInfo);
return machines.add(machineInfo);
}

View File

@ -16,6 +16,7 @@
package com.taobao.csp.sentinel.dashboard.discovery;
import java.util.Date;
import java.util.Objects;
import com.alibaba.csp.sentinel.util.StringUtil;
@ -117,29 +118,16 @@ public class MachineInfo implements Comparable<MachineInfo> {
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof MachineInfo)) {
return false;
}
if (this == o) { return true; }
if (!(o instanceof MachineInfo)) { return false; }
MachineInfo that = (MachineInfo)o;
if (app != null ? !app.equals(that.app) : that.app != null) {
return false;
}
if (hostname != null ? !hostname.equals(that.hostname) : that.hostname != null) {
return false;
}
return ip != null ? ip.equals(that.ip) : that.ip == null;
return Objects.equals(app, that.app) &&
Objects.equals(ip, that.ip) &&
Objects.equals(port, that.port);
}
@Override
public int hashCode() {
int result = app != null ? app.hashCode() : 0;
result = 31 * result + (hostname != null ? hostname.hashCode() : 0);
result = 31 * result + (ip != null ? ip.hashCode() : 0);
return result;
return Objects.hash(app, ip, port);
}
}

View File

@ -0,0 +1,52 @@
package com.taobao.csp.sentinel.dashboard.discovery;
import java.util.ConcurrentModificationException;
import java.util.Set;
import org.junit.Test;
import static org.junit.Assert.*;
public class AppInfoTest {
@Test
public void testConcurrentGetMachines() throws Exception {
AppInfo appInfo = new AppInfo("testApp");
appInfo.addMachine(genMachineInfo("hostName1", "10.18.129.91"));
appInfo.addMachine(genMachineInfo("hostName2", "10.18.129.92"));
Set<MachineInfo> machines = appInfo.getMachines();
new Thread(() -> {
try {
for (MachineInfo m : machines) {
System.out.println(m);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
}
} catch (ConcurrentModificationException e) {
e.printStackTrace();
assertTrue(false);
}
}).start();
Thread.sleep(100);
try {
appInfo.addMachine(genMachineInfo("hostName3", "10.18.129.93"));
} catch (ConcurrentModificationException e) {
e.printStackTrace();
assertTrue(false);
}
Thread.sleep(1000);
}
private MachineInfo genMachineInfo(String hostName, String ip) {
MachineInfo machine = new MachineInfo();
machine.setApp("testApp");
machine.setHostname(hostName);
machine.setIp(ip);
machine.setPort(8719);
machine.setVersion(String.valueOf(System.currentTimeMillis()));
return machine;
}
}