Golang中C分配的指针会被GOGC回收吗?
Golang中C分配的指针会被GOGC回收吗? 我正在尝试通过重写 http 模块中的 accept/read/write 方法,使一个 https 服务器正常工作。我使用 go-wolfssl 来处理 TLS。
服务器会接受一个 TLS 1.2 连接,然后向连接的客户端发送一些随机数据,接着等待下一个连接。
我遇到的问题在于,每当服务器发送大的负载时,它能够成功发送,但随后就无法向连接的下一个客户端发送数据(发送会因 TCP 错误 EBADF 而失败)。如果处理较小的负载(<30k),服务器可以成功地向每个连接的客户端发送数据。然而,一旦发送了一个大的负载,下一次传输就会失败。
如果我使用 debug.SetGCPercent(-1) 禁用 GOGC,问题就会消失,我可以根据需要发送任意多的大负载。根据我目前的调试情况,这看起来像是 GO 垃圾回收器丢弃了 C 指针的问题。go-wolfssl 依赖于 wolfSSL C 库,因此它使用了 CGO。有人有其他想法或建议吗?
代码如下。要实际运行它,请查看此仓库:GitHub - lealem47/go-wolfssl-https-server
谢谢!
package main
import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
log "github.com/sirupsen/logrus"
wolfSSL "github.com/wolfssl/go-wolfssl"
"net"
"net/http"
"os"
"strconv"
"sync"
"time"
)
const defaultPort = "8443"
type wolfSSLListener struct {
listener net.Listener
ctx *wolfSSL.WOLFSSL_CTX
}
// Accept waits for and returns the next connection to the listener.
func (cl *wolfSSLListener) Accept() (net.Conn, error) {
conn, err := cl.listener.Accept()
if err != nil {
return nil, err
}
fmt.Println("Accepted new connection from:", conn.RemoteAddr())
ssl := wolfSSL.WolfSSL_new(cl.ctx)
if ssl == nil {
fmt.Println("WolfSSL_new Failed")
os.Exit(1)
}
file, err := conn.(*net.TCPConn).File()
if err != nil {
panic(err)
}
fd := file.Fd()
wolfSSL.WolfSSL_set_fd(ssl, int(fd))
ret := wolfSSL.WolfSSL_accept(ssl)
if ret != wolfSSL.WOLFSSL_SUCCESS {
fmt.Println("WolfSSL_accept error ", ret)
} else {
fmt.Println("Client Successfully Connected!")
}
return &wolfSSLConn{
conn: conn,
ssl: ssl,
}, nil
}
// Close closes the listener, making it stop accepting new connections.
func (cl *wolfSSLListener) Close() error {
fmt.Println("Closing listener...")
return cl.listener.Close()
}
// Addr returns the listener's network address.
func (cl *wolfSSLListener) Addr() net.Addr {
return cl.listener.Addr()
}
type wolfSSLConn struct {
conn net.Conn
ssl *wolfSSL.WOLFSSL
buffer bytes.Buffer
mu sync.Mutex
closed bool
}
func (w *wolfSSLConn) Read(b []byte) (int, error) {
log.Infof("Calling read: %d", len(b))
ret := wolfSSL.WolfSSL_read(w.ssl, b, uintptr(len(b)))
if ret < 0 {
errCode := wolfSSL.WolfSSL_get_error(w.ssl, int(ret))
return 0, fmt.Errorf("read error: %d", errCode)
}
log.Infof("Read bytes: %s", string(b[:ret]))
return int(ret), nil
}
func (w *wolfSSLConn) Write(b []byte) (int, error) {
log.Infof("Calling write: %d", len(b))
sz := uintptr(len(b))
ret := wolfSSL.WolfSSL_write(w.ssl, b, sz)
if ret < 0 {
errCode := wolfSSL.WolfSSL_get_error(w.ssl, int(ret))
return 0, fmt.Errorf("write error: %d", errCode)
}
return int(ret), nil
}
func (w *wolfSSLConn) Close() error {
log.Infof("Closing connection")
wolfSSL.WolfSSL_shutdown(w.ssl)
wolfSSL.WolfSSL_free(w.ssl)
return w.conn.Close()
}
func (w *wolfSSLConn) LocalAddr() net.Addr {
return w.conn.LocalAddr()
}
func (w *wolfSSLConn) RemoteAddr() net.Addr {
return w.conn.RemoteAddr()
}
func (w *wolfSSLConn) SetDeadline(t time.Time) error {
return w.conn.SetDeadline(t)
}
func (w *wolfSSLConn) SetReadDeadline(t time.Time) error {
return w.conn.SetReadDeadline(t)
}
func (w *wolfSSLConn) SetWriteDeadline(t time.Time) error {
return w.conn.SetWriteDeadline(t)
}
// Handler for generating and base64 encoding 5KB of random data
func randomDataHandler(w http.ResponseWriter, r *http.Request) {
// Get the "size" query parameter from the request
sizeParam := r.URL.Query().Get("size")
size := 500000 // default size
// If the "size" parameter is provided, convert it to an integer
if sizeParam != "" {
parsedSize, err := strconv.Atoi(sizeParam)
if err != nil || parsedSize <= 0 {
http.Error(w, "Invalid size parameter", http.StatusBadRequest)
return
}
size = parsedSize
}
// Generate random data of the specified size
data := make([]byte, size)
_, err := rand.Read(data)
if err != nil {
http.Error(w, "Could not generate random data", http.StatusInternalServerError)
return
}
// Base64 encode the random data
encodedData := base64.StdEncoding.EncodeToString(data)
// Set content type and write the base64 encoded data
w.Header().Set("Content-Type", "application/base64")
w.Write([]byte(encodedData))
}
func main() {
port := defaultPort
// Set logging level
log.SetLevel(log.InfoLevel)
log.SetFormatter(&log.TextFormatter{
DisableColors: false,
FullTimestamp: true,
})
// Set up the HTTP server and routes
http.HandleFunc("/", randomDataHandler)
CERT_FILE := "./certs/server-cert.pem"
KEY_FILE := "./certs/server-key.pem"
/* Initialize wolfSSL */
wolfSSL.WolfSSL_Init()
/* Create WOLFSSL_CTX with tlsv12 */
ctx := wolfSSL.WolfSSL_CTX_new(wolfSSL.WolfTLSv1_2_server_method())
if ctx == nil {
fmt.Println(" WolfSSL_CTX_new Failed")
os.Exit(1)
}
/* Load server certificates into WOLFSSL_CTX */
ret := wolfSSL.WolfSSL_CTX_use_certificate_file(ctx, CERT_FILE, wolfSSL.SSL_FILETYPE_PEM)
if ret != wolfSSL.WOLFSSL_SUCCESS {
fmt.Println("Error: WolfSSL_CTX_use_certificate Failed")
os.Exit(1)
}
/* Load server key into WOLFSSL_CTX */
ret = wolfSSL.WolfSSL_CTX_use_PrivateKey_file(ctx, KEY_FILE, wolfSSL.SSL_FILETYPE_PEM)
if ret != wolfSSL.WOLFSSL_SUCCESS {
fmt.Println("Error: WolfSSL_CTX_use_PrivateKey Failed")
os.Exit(1)
}
baseListener, err := net.Listen("tcp", ":"+port)
if err != nil {
fmt.Println("Error starting listener:", err)
return
}
defer baseListener.Close()
wolfSSLListener := &wolfSSLListener{
listener: baseListener,
ctx: ctx,
}
log.Printf("Server listening on https://localhost:%s", port)
err = http.Serve(wolfSSLListener, nil)
if err != nil {
fmt.Println("Error starting HTTP server:", err)
}
}
更多关于Golang中C分配的指针会被GOGC回收吗?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
cgo 不会随意回收 C 指针。你可以尝试创建大量的 C 变量,然后观察它们是否会被回收。
更多关于Golang中C分配的指针会被GOGC回收吗?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go中,C分配的指针不会被Go垃圾回收器(GC)直接管理。Go的GC只管理Go堆上的内存,不管理C堆上分配的内存。当你在Go代码中使用CGO并调用C函数分配内存时,需要手动管理这些内存的生命周期。
在你的代码中,问题很可能出现在wolfSSLConn结构体中的ssl指针管理上。当Go GC运行时,如果Go认为wolfSSLConn对象不再被引用,它可能会被回收,但GC不会自动释放C分配的WOLFSSL结构体。
以下是关键问题的分析和解决方案:
问题分析
- C指针的生命周期管理:
wolfSSL.WolfSSL_new()返回的C指针需要显式释放 - Go对象与C对象的关联:Go GC不知道C指针的存在,不会阻止其被回收
- 文件描述符管理:从
net.TCPConn获取的文件描述符需要正确管理
解决方案
1. 使用runtime.KeepAlive确保C指针不被过早回收
import "runtime"
func (w *wolfSSLConn) Write(b []byte) (int, error) {
log.Infof("Calling write: %d", len(b))
// 确保ssl指针在Write期间不会被GC
runtime.KeepAlive(w.ssl)
sz := uintptr(len(b))
ret := wolfSSL.WolfSSL_write(w.ssl, b, sz)
if ret < 0 {
errCode := wolfSSL.WolfSSL_get_error(w.ssl, int(ret))
return 0, fmt.Errorf("write error: %d", errCode)
}
return int(ret), nil
}
func (w *wolfSSLConn) Read(b []byte) (int, error) {
log.Infof("Calling read: %d", len(b))
// 确保ssl指针在Read期间不会被GC
runtime.KeepAlive(w.ssl)
ret := wolfSSL.WolfSSL_read(w.ssl, b, uintptr(len(b)))
if ret < 0 {
errCode := wolfSSL.WolfSSL_get_error(w.ssl, int(ret))
return 0, fmt.Errorf("read error: %d", errCode)
}
log.Infof("Read bytes: %s", string(b[:ret]))
return int(ret), nil
}
2. 使用finalizer确保C资源被正确释放
import "runtime"
type wolfSSLConn struct {
conn net.Conn
ssl *wolfSSL.WOLFSSL
buffer bytes.Buffer
mu sync.Mutex
closed bool
}
func newWolfSSLConn(conn net.Conn, ssl *wolfSSL.WOLFSSL) *wolfSSLConn {
wc := &wolfSSLConn{
conn: conn,
ssl: ssl,
}
// 设置finalizer,确保C资源被释放
runtime.SetFinalizer(wc, func(w *wolfSSLConn) {
if w.ssl != nil && !w.closed {
wolfSSL.WolfSSL_shutdown(w.ssl)
wolfSSL.WolfSSL_free(w.ssl)
w.ssl = nil
}
})
return wc
}
// 在Accept方法中使用newWolfSSLConn
func (cl *wolfSSLListener) Accept() (net.Conn, error) {
conn, err := cl.listener.Accept()
if err != nil {
return nil, err
}
fmt.Println("Accepted new connection from:", conn.RemoteAddr())
ssl := wolfSSL.WolfSSL_new(cl.ctx)
if ssl == nil {
fmt.Println("WolfSSL_new Failed")
conn.Close()
return nil, fmt.Errorf("WolfSSL_new failed")
}
file, err := conn.(*net.TCPConn).File()
if err != nil {
conn.Close()
wolfSSL.WolfSSL_free(ssl)
return nil, err
}
defer file.Close() // 重要:关闭文件描述符的副本
fd := file.Fd()
wolfSSL.WolfSSL_set_fd(ssl, int(fd))
ret := wolfSSL.WolfSSL_accept(ssl)
if ret != wolfSSL.WOLFSSL_SUCCESS {
fmt.Println("WolfSSL_accept error ", ret)
conn.Close()
wolfSSL.WolfSSL_free(ssl)
return nil, fmt.Errorf("WolfSSL_accept failed: %d", ret)
}
fmt.Println("Client Successfully Connected!")
return newWolfSSLConn(conn, ssl), nil
}
3. 改进Close方法,确保资源正确清理
func (w *wolfSSLConn) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return nil
}
log.Infof("Closing connection")
var errs []error
if w.ssl != nil {
wolfSSL.WolfSSL_shutdown(w.ssl)
wolfSSL.WolfSSL_free(w.ssl)
w.ssl = nil
}
if w.conn != nil {
if err := w.conn.Close(); err != nil {
errs = append(errs, err)
}
}
w.closed = true
// 移除finalizer,因为我们已经手动清理了
runtime.SetFinalizer(w, nil)
if len(errs) > 0 {
return fmt.Errorf("errors closing connection: %v", errs)
}
return nil
}
4. 使用sync.Pool管理连接对象(可选优化)
var wolfSSLConnPool = sync.Pool{
New: func() interface{} {
return &wolfSSLConn{}
},
}
func getWolfSSLConn(conn net.Conn, ssl *wolfSSL.WOLFSSL) *wolfSSLConn {
wc := wolfSSLConnPool.Get().(*wolfSSLConn)
wc.conn = conn
wc.ssl = ssl
wc.closed = false
wc.buffer.Reset()
runtime.SetFinalizer(wc, (*wolfSSLConn).finalize)
return wc
}
func (w *wolfSSLConn) finalize() {
if w.ssl != nil && !w.closed {
wolfSSL.WolfSSL_shutdown(w.ssl)
wolfSSL.WolfSSL_free(w.ssl)
w.ssl = nil
}
wolfSSLConnPool.Put(w)
}
关键点
- C内存需要手动管理:Go GC不管理C分配的内存
- 使用runtime.KeepAlive:防止Go GC在C调用期间回收包含C指针的Go对象
- 正确使用finalizer:作为备份机制,确保C资源最终被释放
- 文件描述符管理:从net.TCPConn获取的File需要正确关闭
- 并发安全:确保Close方法是线程安全的
禁用GC只是掩盖了问题,正确的解决方案是确保C指针在需要时保持有效,并在不再需要时正确释放。

