【问题标题】:Test Keep failing even though map deleted key entry即使地图删除了密钥条目,测试仍然失败
【发布时间】:2018-07-12 14:24:31
【问题描述】:

我有这个注册表结构,它有一个取消注册客户端的通道。基本上从映射中删除客户端指针并关闭客户端发送通道。为什么这个测试总是失败

func (r *SocketRegistry) run() {
    for {
        select {
        case client := <-r.Register:
            r.clients[client.id] = client
        case client := <-r.UnRegister:
            delete(r.clients, client.id)
            close(client.send)
        case payload := <-r.Broadcast:
            var regPayload RegistryPayload
            json.Unmarshal(payload, &regPayload)
            client := r.clients[regPayload.ClientID]
            select {
            case client.send <- payload:
            default:
                close(client.send)
                delete(r.clients, client.id)
            }
        }
    }
}

//GetClient通过id返回Client指针

func (r *SocketRegistry) GetClient(id string) (*Client, bool) {
    if client, ok := r.clients[id]; ok {
        return client, ok
    }
    return &Client{}, false
}

这是测试

func TestRegisterClient(t *testing.T) {
    registry := Registry()
    go registry.run()
    defer registry.stop()
    var client Client
    client.id = "PROPS"
    client.send = make(chan []byte, 256)

    registry.Register <- &client

    c, _ := registry.GetClient(client.id)
    if client.id != c.id {
        t.Errorf("Expected client with id: %v got: %v", client.id, c.id)
    }

    registry.UnRegister <- &client
    c, ok := registry.GetClient(client.id)
    if ok {
        t.Errorf("Expected false got ok: %v and client id: %v got: %v", ok, client.id, c.id)
    }

}

就好像地图永远不会删除密钥一样。如果我添加一些日志语句,那么它确实会删除密钥,这让我认为这可能是 goroutines 的时间问题

【问题讨论】:

    标签: unit-testing testing go channel goroutine


    【解决方案1】:

    有一场比赛。不能保证run() 在调用registry.GetClient(client.id) 之前执行delete(r.clients, client.id)

    race detector 检测并报告问题。

    像这样实现 GetClient:

    // add this field to Registry
    get chan getRequest
    
    struct getRequest struct {
         ch chan *Client
         id string
    }
    
    func (r *SocketRegistry) GetClient(id string) (*Client, bool) {
        ch := make(chan *Client)
        r.get <- getRequest{id, ch}
        c := <- ch
        if c == nil {
           return &Client{}, false
        }
        return c, true
    }
    
    func (r *SocketRegistry) run() {
        for {
            select {
            case gr := <-r.get:
              gr.ch <- r.clients[id]
            case client := <-r.Register:
              ... as before
        }
    }
    

    我会使用互斥体而不是通道和 goroutine 来解决这个问题:

    func (r *SocketRegistry) register(c *Client) {
        r.mu.Lock()
        defer r.mu.Unlock()
        r.clients[c.id] = c
    }
    
    func (r *SocketRegistry) unregister(c *Client) {
        r.mu.Lock()
        defer r.mu.Unlock()
        delete(r.clients, c.id)
        close(c.send)
    }
    
    func (r *SocketRegister) get(id string) (*Client, bool) {
        r.mu.Lock()
        defer r.mu.Unlock()
        c, ok := r.clients[id]
        return c, ok
    }
    
    func (r *SocketRegistry) send(id string, data []byte) {
       r.mu.Lock()
       defer r.mu.Unlock()
       c := r.clients[id]
       select {
       case c.send <- data:
       default:
          close(c.send)
          delete(r.clients, c.id)
       }
    }
    

    Goroutines 很棒,但它们并不总是适合特定工作的最佳工具。

    【讨论】:

    • 啊,这就是我的想法,我可能可以使用 sync.Map 结构来解决这个问题,对吗?而不是手动执行互斥锁?
    • 你可以使用sync.Map,但是你会用别的东西来防止发送和关闭c.send上的竞争。
    猜你喜欢
    • 2021-12-15
    • 2016-11-08
    • 1970-01-01
    • 2015-01-23
    • 2021-05-18
    • 2018-05-20
    • 2015-03-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多