codingapi / codingapi/tx-lcn

使用double-check获取单例的bug

Đang mở
#455 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
Ngôn ngữ chính
Java
Star
4.2k
Fork
1.4k
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Mô tả

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;
}
}

```

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Hướng nghiên cứu

Đọc các phương thức getInstance trong txmsg/netty/bean/RpcCmdContext.java và SocketManager.java, tập trung vào khởi tạo kiểm tra kép và quyền truy cập đồng thời của chúng. Xác nhận rằng cả hai đường đi của singleton đều an toàn khi được gọi đồng thời; issue được hoàn thành khi không lớp nào trong hai lớp có thể tạo hoặc để lộ nhiều instance trong trường hợp đó.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
java
Lĩnh vực
backend, distributed-systems
Loại issue
Lỗi
Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
52/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.