Skip to content

[BUG] 谷歌网盘经 WebDAV 上传时进程崩溃:blockRefReadSeeker.Read 空指针,疑似请求体生命周期竞争 #3334

Open

Description

@iona-s

请确认以下事项

  • 我已确认阅读并同意 AGPL-3.0 第15条 。
    本程序不提供任何明示或暗示的担保,使用风险由您自行承担。

  • 我已确认阅读并同意 AGPL-3.0 第16条 。
    无论何种情况,版权持有人或其他分发者均不对使用本程序所造成的任何损失承担责任。

  • 我确认我的描述清晰,语法礼貌,能帮助开发者快速定位问题,并符合社区规则。

  • 我已确认阅读了OpenList文档。

  • 我已确认没有重复的问题或讨论。

  • 我已确认是OpenList的问题,而不是其他原因(例如 网络 ,依赖或操作)。

  • 我认为此问题必须由OpenList处理,而非第三方。

  • 我已确认这个问题在最新版本中没有被修复。

  • 我没有阅读这个清单,只是闭眼选中了所有的复选框,请关闭这个 Issue 。

OpenList 版本(必填)

v4.2.9

使用的存储驱动(必填)

GoogleDrive

问题描述(必填)

现象

通过 WebDAV PUT(http://127.0.0.1:5244/dav/...,没有经过反向代理)向谷歌网盘上传一个约 88 GB 的文件。约 17 分钟后,OpenList 进程因空指针 panic 退出,容器被重启,上传中断。不是 OOM。

原因分析(根据源码推断)

以下引用的都是提交 0497ee8 的代码。

  1. chunkUpload(drivers/google_drive/util.go#L335-L396)对每个分片的处理是:
    • 用 ss.GetSectionReader 取得 reader(L349),用 NewLimitedUploadStream 包一层(L353);
    • 在 retry.Do 里发起 PUT(L354–L365);
    • retry.Do 结束后调用 ss.FreeSectionReader(reader)(L389)。
  2. hybridSectionReader.FreeSectionReader(internal/stream/util.go#L292-L297)会把缓冲块放回复用缓存,并把内嵌的 sr.ReadSeeker 置为 nil。
  3. http.Client.Do 返回后,transport 仍可能在 writeLoop 里继续读取请求体,请求体稍后才会被异步关闭。这段代码没有等请求体关闭,就释放了 reader。writeLoop 再读时,就会在 nil 的 io.ReadSeeker 上调用 Read。这正好对应堆栈里的 <autogenerated> 帧(addr=0x18)和 limit.go:47。
  4. panic 发生在 writeLoop 这个 goroutine 里,上传流程里的 recover 接不住,所以整个进程退出。

同一个函数里的其他问题:

  • 没有检查 HTTP 状态码。 是否成功只看响应 JSON 里有没有 error.code。收到 308 时,也没有根据 Range 头确认谷歌实际收到的范围。
  • Content-Length 只写在了请求头里(L362),没有设置 req.ContentLength。 net/http 发送客户端请求时会忽略这个请求头。

相关 issue

修复建议

  • 每次尝试前,确认上一轮请求体的读取已经结束,再 Seek 回开头重试、复用缓冲块,或者调用 FreeSectionReader。
  • 设置 req.ContentLength = chunkSize。
  • 按谷歌的续传协议处理状态码:
    • 308:根据 Range 头推进偏移量;
    • 200/201:上传完成;
    • 5xx 和网络错误:先查询续传位置,再重试;
    • 上传会话失效:重建会话。
  • 其他在请求结束后马上回收 section reader 的驱动,建议一并检查。

日志(必填)

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0xbf9e2c]

goroutine 857177 [running]:
github.com/OpenListTeam/OpenList/v4/internal/stream.(*blockRefReadSeeker).Read(0x1?, {0x9dfdf0dc000?, 0x8000?, 0x90f0a80?})
	<autogenerated>:1 +0xc
github.com/OpenListTeam/OpenList/v4/internal/stream.(*RateLimitReader).Read(0x9dfdec4b740, {0x9dfdf0dc000, 0x8000, 0x8000})
	/home/runner/work/OpenList/OpenList/internal/stream/limit.go:47 +0x5a
io.copyBuffer({0x7fe36de31620, 0x9dfdeb9e680}, {0x9491f48, 0x9dfdec4b740}, {0x9dfdf0dc000, 0x8000, 0x8000})
	/opt/hostedtoolcache/go/1.27.3/x64/src/io/io.go:429 +0x190
io.CopyBuffer({0x7fe36de31620?, 0x9dfdeb9e680?}, {0x9491f48?, 0x9dfdec4b740?}, {0x9dfdf0dc000?, 0x9dfded7bc80?, 0x42657b?})
	/opt/hostedtoolcache/go/1.27.3/x64/src/io/io.go:402 +0x36
net/http.(*transferWriter).doBodyCopy(0x9dfdea8ec80, {0x7fe36de31620, 0x9dfdeb9e680}, {0x9491f48, 0x9dfdec4b740})
	/opt/hostedtoolcache/go/1.27.3/x64/src/net/http/transfer.go:425 +0xdc
net/http.(*transferWriter).writeBody(0x9dfdea8ec80, {0x9490308, 0x9dfdf5cc080})
	/opt/hostedtoolcache/go/1.27.3/x64/src/net/http/transfer.go:369 +0x705
net/http.(*Request).write(0x9dfde88d540, {0x9490308, 0x9dfdf5cc080}, 0x0, 0x9dfdf5c34a0, 0x0)
	/opt/hostedtoolcache/go/1.27.3/x64/src/net/http/request.go:772 +0xac6
net/http.(*persistConn).writeLoop(0x9dfde88d680)
	/opt/hostedtoolcache/go/1.27.3/x64/src/net/http/transport.go:2836 +0x174
created by net/http.(*Transport).dialConn in goroutine 857170
	/opt/hostedtoolcache/go/1.27.3/x64/src/net/http/transport.go:2124 +0x1e05
INFO[2026-10-31 12:23:15] reading config file: /opt/openlist/data/config.json
INFO[2026-10-31 12:23:15] load config from env with prefix:
INFO[2026-10-31 12:23:15] total memory: 23718MB, available: 12967MB
INFO[2026-10-31 12:23:15] min free memory: 1024MB
INFO[2026-10-31 12:23:15] max block limit: 64MB
INFO[2026-10-31 12:23:15] auto memory limit: 4MB
INFO[2026-10-31 12:23:15] init logrus...
start HTTP server @ 0.0.0.0:5244

配置文件内容(必填)

config.json(敏感字段已隐藏):

{
  "force": false,
  "site_url": "/",
  "cdn": "",
  "jwt_secret": "******",
  "token_expires_in": "******",
  "database": {
    "type": "sqlite3",
    "host": "",
    "port": 0,
    "user": "",
    "password": "",
    "name": "",
    "db_file": "/opt/openlist/data/data.db",
    "table_prefix": "x_",
    "ssl_mode": "",
    "dsn": ""
  },
  "meilisearch": {
    "host": "http://localhost:7700",
    "api_key": "",
    "index": "openlist"
  },
  "scheme": {
    "address": "0.0.0.0",
    "http_port": 5244,
    "https_port": -1,
    "force_https": false,
    "cert_file": "",
    "key_file": "",
    "unix_file": "",
    "unix_file_perm": "",
    "enable_h2c": false,
    "enable_h3": false
  },
  "temp_dir": "/opt/openlist/data/temp",
  "bleve_dir": "/opt/openlist/data/bleve",
  "dist_dir": "",
  "log": {
    "enable": true,
    "name": "/opt/openlist/data/log/log.log",
    "max_size": 50,
    "max_backups": 30,
    "max_age": 28,
    "compress": false,
    "filter": {
      "enable": false,
      "filters": [
        { "cidr": "", "path": "/ping", "method": "" },
        { "cidr": "", "path": "", "method": "HEAD" },
        { "cidr": "", "path": "/dav/", "method": "PROPFIND" }
      ]
    }
  },
  "delayed_start": 0,
  "auto_memory_limit": 4,
  "min_free_memory": 0,
  "max_block_limit": 0,
  "max_connections": 0,
  "max_concurrency": 64,
  "tls_insecure_skip_verify": false,
  "tasks": {
    "download": { "workers": 5, "max_retry": 1, "task_persistant": false },
    "transfer": { "workers": 5, "max_retry": 2, "task_persistant": false },
    "upload": { "workers": 5, "max_retry": 0, "task_persistant": false },
    "copy": { "workers": 5, "max_retry": 2, "task_persistant": false },
    "move": { "workers": 5, "max_retry": 2, "task_persistant": false },
    "decompress": { "workers": 5, "max_retry": 2, "task_persistant": false },
    "decompress_upload": { "workers": 5, "max_retry": 2, "task_persistant": false },
    "allow_retry_canceled": false
  },
  "cors": { "allow_origins": ["*"], "allow_methods": ["*"], "allow_headers": ["*"] },
  "s3": { "enable": false, "port": 5246, "ssl": false, "multipart_ttl": "24h" },
  "ftp": {
    "enable": false,
    "listen": ":5221",
    "find_pasv_port_attempts": 50,
    "active_transfer_port_non_20": false,
    "idle_timeout": 900,
    "connection_timeout": 30,
    "disable_active_mode": false,
    "default_transfer_binary": false,
    "enable_active_conn_ip_check": true,
    "enable_pasv_conn_ip_check": true
  },
  "sftp": { "enable": false, "listen": ":5222" },
  "mcp": { "enable": false },
  "last_launched_version": "v4.2.9",
  "proxy_address": ""
}
Image

复现链接(可选)

No response

AI生成内容

  • 我使用了AI工具生成此内容
  • 我没有使用AI工具生成此内容

AI模型是

opus5.5 并有fable5.1和gpt-sol6.1独立二审

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions