测试保持失败,即使映射删除了键条目



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

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 返回客户端指针

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)

比赛检测器检测并报告问题。

像这样实现 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很棒,但它们并不总是给定工作的最佳工具。

最新更新