偶尔出现“切片边界超出范围”的恐慌情况。

huangapple go评论84阅读模式
英文:

Occasional 'slice bounds out of range' panic

问题

我正在运行一个脚本,将 Webhook 转发到 WebSocket。发送 Webhook 到 WebSocket 的部分会检查非活动连接,并在转发 Webhook 时尝试将它们移除,但有时会出现以下错误:

http: panic serving 10.244.38.169:40958: runtime error: slice bounds out of range

(IP/端口每次都不同,这只是一个示例。)

相关代码:

// 存储所有 WebSocket 客户端及其订阅的端点的映射
var clients = make(map[string][]*websocket.Conn)
var upgrader = websocket.Upgrader{}

// 当新客户端连接到 WebSocket 时执行的函数
func handleClient(w http.ResponseWriter, r *http.Request, endpoint string) {
	conn, err := upgrader.Upgrade(w, r, nil)
    // ...
	// 将客户端添加到端点的切片中
	clients[endpoint] = append(clients[endpoint], conn)
}

// 发送 Webhook 到 WebSocket 端点的函数
func handleHook(w http.ResponseWriter, r *http.Request, endpoint string) {
    msg := Message{}
    // ...   
    // 获取所有监听当前端点的客户端
	conns := clients[endpoint]

	if conns != nil {
		for i, conn := range conns {
			if conn.WriteJSON(msg) != nil {
				// 如果发送失败,则移除客户端并关闭连接
				conns = append(conns[:i], conns[i+1:]...)   // 这一行有时会触发 panic
				conn.Close()
			}
		}
	}

	clients[endpoint] = conns
}

我无法弄清楚为什么在迭代连接并将它们附加到切片时有时会触发 panic。

英文:

I'm running a script that forwards webhooks to a WebSocket.
The part that sends the webhook to the WebSocket checks for inactive connections and tries to remove them when forwarding webhooks sometimes fails with this error:

http: panic serving 10.244.38.169:40958: runtime error: slice bounds out of range

(The IP/port is always different, this is just an example.)

Relevant code:

// Map holding all Websocket clients and the endpoints they are subscribed to
var clients = make(map[string][]*websocket.Conn)
var upgrader = websocket.Upgrader{}

// function to execute when a new client connects to the websocket
func handleClient(w http.ResponseWriter, r *http.Request, endpoint string) {
	conn, err := upgrader.Upgrade(w, r, nil)
    // ...
	// Add client to endpoint slice
	clients[endpoint] = append(clients[endpoint], conn)
}

// function to send a webhook to a websocket endpoint
func handleHook(w http.ResponseWriter, r *http.Request, endpoint string) {
    msg := Message{}
    // ...   
    // Get all clients listening to the current endpoint
	conns := clients[endpoint]

	if conns != nil {
		for i, conn := range conns {
			if conn.WriteJSON(msg) != nil {
				// Remove client and close connection if sending failed
				conns = append(conns[:i], conns[i+1:]...)   // this is the line that sometimes triggers the panic
				conn.Close()
			}
		}
	}

	clients[endpoint] = conns
}

I cannot figure out why iterating over the connections and appending them sometimes triggers the panic.

答案1

得分: 1

我想说几点:

  1. 确保你的程序没有竞态条件(例如,如果同时发生读/写或写/写操作,clients 是全局可访问的,应该进行保护)。

  2. 当遍历切片时,使用 for [...] range [...],你不需要检查切片是否为非空,因为 range 已经处理了这个(参见我分享的代码)。

  3. 有时会发生这种情况,是因为 conn.WriteJSON 失败并返回错误,而在遍历过程中删除元素的错误逻辑导致程序崩溃(参见我分享的代码)。

package main

import "fmt"

func main() {
	var conns []string = nil

	// 不需要检查 "if conns != nil",因为 "for [...] range [...]" 可以处理。可以直接使用 "range" 是安全的。
	for i, conn := range conns {
		fmt.Println(i, conn)
	}

	conns = []string{"1", "2", "3"}

	// 将会导致程序崩溃
	for i := range conns {
		fmt.Printf("access: %d, length: %d\n", i, len(conns))
		conns = append(conns[:i], conns[i+1:]...)
	}
}

在这个示例中,你可以看到你尝试访问的索引大于或等于切片的长度,这触发了 panic。我认为这个答案应该能帮助你纠正你的逻辑,或者你也可以使用 map 来存储连接,但它也有自己的注意事项,比如没有顺序保证,即从 map 中读取的顺序。

英文:

Few points that I'd like to say:

  1. Make sure that your program has no race condition (eg. clients is globally accessible and should be protected if read/write or write/write
    happening concurrently).

  2. When ranging over a slice for [...] range [...] you do not need to check if slice the non-nil as range handles that already (see the code I've shared).

  3. It is happening to your sometimes because there are times when conn.WriteJSON is failing and returning an error and the buggy logic of deleting element while ranging over is making your program panic. (see the code I've shared)

package main

import "fmt"

func main() {
	var conns []string = nil

	// "if conns != nil" check is not required as "for [...] range [...]"
	// can handle that. It is safe to use for "range" directly.
	for i, conn := range conns {
		fmt.Println(i, conn)
	}

	conns = []string{"1", "2", "3"}
    
    // Will panic
	for i := range conns {
		fmt.Printf("access: %d, length: %d\n", i, len(conns))
		conns = append(conns[:i], conns[i+1:]...)
	}
}

In the example, you can see the index that you are trying to access it more than or equal to the length of the slice which is triggering the panic. I think this answer should help you to correct your logic or you can use a map as well for storing connections but it again comes with its own caveats like no ordering guarantee i.e., in which order it reads from the map.

答案2

得分: 0

应该按照数组中的索引进行删除

包 main

import "fmt"

func main() {
var conns []string = nil

// "if conns != nil" 检查是不必要的,因为 "for [...] range [...]" 可以处理。可以直接使用 "range" 是安全的。
for i, conn := range conns {
	fmt.Println(i, conn)
}

conns = []string{"1", "2", "3"}

// 将会引发 panic
indexs := []int{}
for i := range conns {
	if i < 2 {
		indexs = append(indexs, i)
	}
}
for i, val := range indexs {
	index := val - i
	fmt.Printf("access: %d, length: %d\n", i, len(conns))
	conns = append(conns[:index], conns[index+1:]...)
}
fmt.Println(conns)

}

英文:

should be remove by indexs in array

package main

import "fmt"

func main() {
var conns []string = nil

// &quot;if conns != nil&quot; check is not required as &quot;for [...] range [...]&quot;
// can handle that. It is safe to use for &quot;range&quot; directly.
for i, conn := range conns {
	fmt.Println(i, conn)
}

conns = []string{&quot;1&quot;, &quot;2&quot;, &quot;3&quot;}

// Will panic
indexs := []int{}
for i := range conns {
	if i &lt; 2 {
		indexs = append(indexs, i)
	}
}
for i, val := range indexs {
    index = val - i
	fmt.Printf(&quot;access: %d, length: %d\n&quot;, i, len(conns))
	conns = append(conns[:index], conns[index+1:]...)
}
fmt.Println(conns)

}

huangapple
  • 本文由 发表于 2021年7月22日 10:04:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/68478308.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定