即使映射删除了键条目,测试仍然失败

Test Keep failing even though map deleted key entry

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

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 returns Client pointer by id

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

的时间问题

有一场比赛。无法保证 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 很棒,但它们并不总是给定工作的最佳工具。