JimClient返回null的一个bug

简要说明

jimclient连接失败的问题。
实际上连接成功了,但是呢,因为线程的原因,却导致了返回失败。

这个bug符合多线程问题的一贯表现。要么不出现,要么总是出现,要么随机。
官方的demo总也不出现,我自己照着官网的demo撸了一遍,开始的时候不出现,后来总是出现。

问题描述

项目代码
https://gitee.com/xchao/j-im/tree/master

具体代码
https://gitee.com/xchao/j-im/blob/master/jim-client-demo/src/main/java/org/jim/client/HelloClientStarter.java

//连接服务端
imClientChannelContext = jimClient.connect(serverNode);

会返回null
查看里面的代码

 public ImClientChannelContext connect(Node serverNode, Integer timeout) throws Exception {
        log.warn("J-IM client connect");
        this.tioClient = new TioClient((ClientTioConfig)this.imClientConfig.getTioConfig());
        ClientChannelContext clientChannelContext = this.tioClient.connect(serverNode, this.imClientConfig.getBindIp(), this.imClientConfig.getBindPort(), timeout);
        if (Objects.nonNull(clientChannelContext)) {
            log.warn("J-IM client connected success at serverAddress:[{}], bind localAddress:[{}]", serverNode.toString(), this.imClientConfig.toBindAddressString());
            return (ImClientChannelContext)clientChannelContext.get("im_channel_context_key");
        } else {
            return null;
        }
    }

里面的
(ImClientChannelContext)clientChannelContext.get(“im_channel_context_key”);
里面有一个map,在connect成功之后调用线程进行填充。
但是呢,就是因为线程的原因,填充之前就进行了获取。
自己打断点调试就知道了。

解决办法

方法1 (放弃了)

跟进去,里面有tio的代码,添加线程锁啥的,进行同步。
我跟到里面的一个connect


     CountDownLatch countDownLatch = new CountDownLatch(1);
     attachment.setCountDownLatch(countDownLatch);
     asynchronousSocketChannel.connect(inetSocketAddress, attachment, this.clientTioConfig.getConnectionCompletionHandler());
     boolean f = countDownLatch.await((long)realTimeout, TimeUnit.SECONDS);
     if (f) {
         return attachment.getChannelContext();
     } else {
         log.error("countDownLatch.await(realTimeout, TimeUnit.SECONDS) 返回false ");
         return attachment.getChannelContext();
     }

我放弃了,里面又启动了一个线程。

本人刚学java不久,改不了这么精密的程序,故放弃~~

方法2 (可用)

重新写一个JimClient类

package cn.xxxx.yyy.service.JimClients;


import org.jim.client.ImClientChannelContext;
import org.jim.client.JimClient;
import org.jim.client.config.ImClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.client.ClientChannelContext;
import org.tio.client.ClientTioConfig;
import org.tio.client.TioClient;
import org.tio.core.Node;

import java.util.Objects;

/***
 * auth: robin
 * date: 2021/01/21
 * 这里基本复制的JimClient,没直接用JimClient的原因是,
 * clientChannelContext.get("im_channel_context_key") 很大的概率会返回null,
 * 因为里面有个map,赋值是通过多线程来完成的,如果另一个线程没有赋值的话,这里得到的就是null
 *
 */

public class WJimClient {
    private static Logger log = LoggerFactory.getLogger(JimClient.class);
    private TioClient tioClient = null;
    private ImClientConfig imClientConfig;

    public WJimClient(ImClientConfig imClientConfig) {
        this.imClientConfig = imClientConfig;
    }

    public ImClientChannelContext connect(Node serverNode) throws Exception {
        return this.connect(serverNode, (Integer)null);
    }

    public ImClientChannelContext connect(Node serverNode, Integer timeout) throws Exception {
        log.warn("J-IM client connect");
        this.tioClient = new TioClient((ClientTioConfig)this.imClientConfig.getTioConfig());
        ClientChannelContext clientChannelContext = this.tioClient.connect(serverNode, this.imClientConfig.getBindIp(), this.imClientConfig.getBindPort(), timeout);
        if (Objects.nonNull(clientChannelContext)) {
            log.warn("J-IM client connected success at serverAddress:[{}], bind localAddress:[{}]", serverNode.toString(), this.imClientConfig.toBindAddressString());
            ImClientChannelContext t_rtnContext = (ImClientChannelContext)clientChannelContext.get("im_channel_context_key");
            int t_maxCount = 200; //最大循环次数,实际测试中只循环了一次
            while (null == t_rtnContext
            && t_maxCount > 0){//这里是在等待另一个线程写入
                Thread.sleep(50);
                t_rtnContext = (ImClientChannelContext)clientChannelContext.get("im_channel_context_key");
                t_maxCount--;
            }
            System.out.println("robin:count:"+(200-t_maxCount));
            return t_rtnContext;
        } else {
            return null;
        }
    }

    public void stop() {
        this.tioClient.stop();
    }
}

主要是里面加了一个sleep。虽然里面最大sleep时间是10s,但是实际上只循环了一次就正常了。

搞定。

提交bug了

那个项目很久没提交了,已经提交了issue。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值