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

Research direction

Read the getInstance methods in txmsg/netty/bean/RpcCmdContext.java and SocketManager.java, focusing on their double-check initialization and concurrent access. Confirm both singleton paths are safe under concurrent calls; the issue is done when neither class can create or expose multiple instances in that scenario.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend, distributed-systems
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.