codingapi / codingapi/tx-lcn

使用double-check获取单例的bug

Open
#455 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
4.2k
Forks
1.4k
PR merge metrics
No merged PRs in 30d

Description

5.0.2.RELEASE版本中com.codingapi.txlcn.txmsg.netty.bean包下面2类(RpcCmdContext.java和SocketManager.java)均有采用double-check获取单例的问题,在并发情况下,并不能保证能获取到单例。

可以通过添加volatile禁止指令重排序保证单例
1. SocketManager.java
```java
/*
* Copyright 2017-2019 CodingApi .
*
* 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.codingapi.txlcn.txmsg.netty.bean;

import com.codingapi.txlcn.txmsg.RpcConfig;
import com.codingapi.txlcn.txmsg.dto.AppInfo;
import com.codingapi.txlcn.txmsg.dto.MessageDto;
import com.codingapi.txlcn.txmsg.dto.RpcCmd;
import com.codingapi.txlcn.txmsg.dto.RpcResponseState;
import com.codingapi.txlcn.txmsg.exception.RpcException;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;

import java.net.SocketAddress;
import java.util.*;
import java.util.concurrent.*;

/**
* Created by lorne on 2017/6/30.
*/
@Slf4j
public class SocketManager {

private Map appNames;

private ScheduledExecutorService executorService;

private ChannelGroup channels;

private static volatile SocketManager manager = null;

private long attrDelayTime = 1000L * 60;

private SocketManager() {
channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
appNames = new ConcurrentHashMap<>();
executorService = Executors.newSingleThreadScheduledExecutor();

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
executorService.shutdown();
try {
executorService.awaitTermination(10, TimeUnit.MINUTES);
} catch (InterruptedException ignored) {
}
}));
}

public static SocketManager getInstance() {
if (manager == null) {
synchronized (SocketManager.class) {
if (manager == null) {
manager = new SocketManager();
}
}
}
return manager;
}

public void addChannel(Channel channel) {
channels.add(channel);
}

public void removeChannel(Channel channel) {
channels.remove(channel);
String key = channel.remoteAddress().toString();

// 未设置过期时间,立即过期
if (attrDelayTime < 0) {
appNames.remove(key);
return;
}

// 设置了过期时间,到时间后清除
try {
executorService.schedule(() -> {
appNames.remove(key);
}, attrDelayTime, TimeUnit.MILLISECONDS);
} catch (RejectedExecutionException ignored) {
// caused down server.
}
}

private Channel getChannel(String key) throws RpcException {
for (Channel channel : channels) {
String val = channel.remoteAddress().toString();
if (key.equals(val)) {
return channel;
}
}
throw new RpcException("channel not online.");
}

public RpcResponseState send(String key, RpcCmd cmd) throws RpcException {
Channel channel = getChannel(key);
ChannelFuture future = channel.writeAndFlush(cmd).syncUninterruptibly();
return future.isSuccess() ? RpcResponseState.success : RpcResponseState.fail;
}

public MessageDto request(String key, RpcCmd cmd, long timeout) throws RpcException {
NettyRpcCmd nettyRpcCmd = (NettyRpcCmd) cmd;
log.debug("get channel, key:{}", key);
Channel channel = getChannel(key);
channel.writeAndFlush(nettyRpcCmd);
log.debug("await response");
if (timeout < 1) {
nettyRpcCmd.await();
} else {
nettyRpcCmd.await(timeout);
}
MessageDto res = cmd.loadResult();
log.debug("response is: {}", res);
nettyRpcCmd.loadRpcContent().clear();
return res;
}

public MessageDto request(String key, RpcCmd cmd) throws RpcException {
return request(key, cmd, -1);
}

public List loadAllRemoteKey() {
List allKeys = new ArrayList<>();
for (Channel channel : channels) {
allKeys.add(channel.remoteAddress().toString());
}
return allKeys;
}

public ChannelGroup getChannels() {
return channels;
}

public int currentSize() {
return channels.size();
}

public boolean noConnect(SocketAddress socketAddress) {
for (Channel channel : channels) {
if (channel.remoteAddress().toString().equals(socketAddress.toString())) {
return false;
}
}
return true;
}

/**
* 获取模块的远程标识keys
*
* @param moduleName 模块名称
* @return remoteKeys
*/
public List remoteKeys(String moduleName) {
List allKeys = new ArrayList<>();
for (Channel channel : channels) {
if (moduleName.equals(getModuleName(channel))) {
allKeys.add(channel.remoteAddress().toString());
}
}
return allKeys;
}

/**
* 绑定连接数据
*
* @param remoteKey 远程标识
* @param appName 模块名称
* @param labelName TC标识名称
*/
public void bindModuleName(String remoteKey, String appName,String labelName) throws RpcException{
AppInfo appInfo = new AppInfo();
appInfo.setAppName(appName);
appInfo.setLabelName(labelName);
appInfo.setCreateTime(new Date());
if(containsLabelName(labelName)){
throw new RpcException("labelName:"+labelName+" has exist.");
}
appNames.put(remoteKey, appInfo);
}

public boolean containsLabelName(String moduleName){
Set keys = appNames.keySet();
for(String key:keys){
AppInfo appInfo = appNames.get(key);
if(moduleName.equals(appInfo.getLabelName())){
return true;
}
}
return false;
}

public void setRpcConfig(RpcConfig rpcConfig) {
attrDelayTime = rpcConfig.getAttrDelayTime();
}

/**
* 获取模块名称
*
* @param channel 管道信息
* @return 模块名称
*/
public String getModuleName(Channel channel) {
String key = channel.remoteAddress().toString();
return getModuleName(key);
}

/**
* 获取模块名称
*
* @param remoteKey 远程唯一标识
* @return 模块名称
*/
public String getModuleName(String remoteKey) {
AppInfo appInfo = appNames.get(remoteKey);
return appInfo == null ? null : appInfo.getLabelName();
}

public List appInfos() {
return new ArrayList<>(appNames.values());
}
}
```

2. RpcCmdContext.java
```java
/*
* Copyright 2017-2019 CodingApi .
*
* 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.codingapi.txlcn.txmsg.netty.bean;

import com.codingapi.txlcn.txmsg.RpcConfig;
import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* Description:
* Company: CodingApi
* Date: 2018/12/10
*
* @author ujued
*/
@Slf4j
public class RpcCmdContext {

private int cacheSize = 1024;

private long waitTime = 1;

private static volatile RpcCmdContext context = null;

private Map map;

private List cacheList;

private final LinkedList freeList;

public static RpcCmdContext getInstance() {
if (context == null) {
synchronized (RpcCmdContext.class) {
if (context == null) {
context = new RpcCmdContext();
}
}
}
return context;
}

private RpcCmdContext() {
map = new ConcurrentHashMap<>();

cacheList = new CopyOnWriteArrayList<>();

freeList = new LinkedList<>();
}


/**
* 并发可能会出现脏读
*
* @param key key
* @return hasKey
*/
public synchronized boolean hasKey(String key) {
return map.containsKey(key);
}

/**
* 并发可能会出重复添加
*
* @param key key
* @return RpcContent
*/
public synchronized RpcContent addKey(String key) {
RpcContent rpcContent = createRpcContent();
map.put(key, rpcContent);
return rpcContent;
}


/**
* 空闲队列处理
*
* @return RpcContent
*/
private RpcContent findRpcContent() {
synchronized (freeList) {
RpcContent cacheContent = freeList.getFirst();
if (!cacheContent.isUsed()) {
cacheContent.init();
freeList.remove(cacheContent);
return cacheContent;
}
}

RpcContent rpcContent = new RpcContent(getWaitTime());
rpcContent.init();
return rpcContent;
}

private RpcContent createRpcContent() {
if (cacheList.size() < cacheSize || freeList.size() == 0) {
RpcContent rpcContent = new RpcContent(getWaitTime());
rpcContent.init();
cacheList.add(rpcContent);
return rpcContent;
} else {
return findRpcContent();
}
}

public RpcContent getKey(String key) {
RpcContent rpcContent = map.get(key);
clearKey(key);
return rpcContent;
}

private void clearKey(String key) {
RpcContent rpcContent = map.get(key);
if (cacheList.contains(rpcContent)) {
synchronized (freeList) {
freeList.add(rpcContent);
}
}
map.remove(key);
}


public void setRpcConfig(RpcConfig rpcConfig) {
cacheSize = rpcConfig.getCacheSize();
// TC or TX init after
waitTime = rpcConfig.getWaitTime() == -1 ? 500 : rpcConfig.getWaitTime();
}

public long getWaitTime() {
return waitTime;
}
}

```

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.