Retrying a multipart request raises "Cannot write to closed encoder"
维护者通常 1 天内回复
评估
调研方向
Start with lib/auth0/internal/multipart/multipart_form_data.rb and lib/auth0/internal/multipart/multipart_encoder.rb, then trace the retry loop in lib/auth0/internal/http/raw_client.rb and header construction in Multipart::Request#encode_headers. Reproduce with the provided WebMock example and verify retries resend the multipart body with a consistent boundary; after retries are exhausted, the client should raise Auth0::Errors::ClientError for a 429.
由索引模型根据 Issue 内容生成。
描述
Checklist
- I have looked into the Readme and Examples, and have not found a suitable solution or answer.
- I have looked into the API documentation and have not found a suitable solution or answer.
- I have searched the issues and have not found a suitable solution or answer.
- I have searched the Auth0 Community forums and have not found a suitable solution or answer.
- I agree to the terms within the Auth0 Code of Conduct.
Description
When a multipart request gets a retryable status (429 or 5xx), Internal::Http::RawClient#send retries it and calls request.encode_body again. Internal::Multipart::FormData keeps a single Encoder, which is closed after the first encode, so the second call raises RuntimeError: Cannot write to closed encoder from Encoder#write_part. The retry is never sent.
This affects jobs.users_imports.create, the multipart endpoint we use. Any 429 or 5xx on a users import comes back as a RuntimeError, so callers that rescue Auth0::Errors::ResponseError don't see it.
Expected: the retry sends the same multipart body again, and once retries run out the client raises the API error (Auth0::Errors::ClientError for a 429), as it does for JSON requests.
Reproduction
Reproducible every time with the default management_max_retries:
require "auth0_client"
require "webmock"
require "tempfile"
include WebMock::API
WebMock.enable!
stub_request(:post, "https://example.auth0.com/oauth/token")
.to_return(status: 200, body: { access_token: "t", expires_in: 86_400 }.to_json,
headers: { "Content-Type" => "application/json" })
stub_request(:post, "https://example.auth0.com/api/v2/jobs/users-imports")
.to_return(status: 429, body: { statusCode: 429 }.to_json,
headers: { "Content-Type" => "application/json" })
client = Auth0Client.new(client_id: "id", client_secret: "secret", domain: "example.auth0.com")
file = Tempfile.new("users")
file.write('[{"email":"[email protected]"}]')
file.rewind
client.jobs.users_imports.create(users: file, connection_id: "con_123")
# => lib/auth0/internal/multipart/multipart_encoder.rb:68:in `write_part':
# Cannot write to closed encoder (RuntimeError)
With management_max_retries: 0 the same call raises Auth0::Errors::ClientError after one request, as expected.
Additional context
FormData#initializecreates the encoder once (lib/auth0/internal/multipart/multipart_form_data.rb:15) andFormData#encodereuses it (:63).Encoder#encodecloses it at the end.RawClient#sendbuilds the request body inside the retry loop (lib/auth0/internal/http/raw_client.rb:59), so every retry encodes the body again.- The
Content-Typeheader is built from the encoder's boundary inMultipart::Request#encode_headers, so a fix needs to keep the same boundary across attempts. For example, encode the body once per request and reuse the string, or letEncoder#encodereset its buffer instead of failing. - Like #800 / #811, this code lives under
lib/auth0/internal/, which is in.fernignore, so a regenerated release won't pick up a generator fix on its own. - Different from #796, which was about building the
userspart and is fixed in 6.5.0. - Our workaround for now: a separate client with
management_max_retries: 0for users imports, so a 429 surfaces asAuth0::Errors::ClientErrorand our background job retries it.
ruby-auth0 version
6.5.0
Ruby version
3.3.9
- 主要语言
- Ruby
- 星标
- 205
- 派生
- 146
- 平均合并
- 1 天 15 小时
- 30 天内合并 PR
- 7
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
auth0/ruby-auth0 的其他 Issue
-
feature request
难度 3/5 1-2 天 新手友好度 45/100
auth0/ruby-auth0#709 ·
维护者通常 1 天内回复
-
bug
难度 1/5 1 小时以内 新手友好度 50/100
auth0/ruby-auth0#610 · 1 条评论 ·
维护者通常 1 天内回复
-
Rate limit data returned from API methods可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭feature request
难度 5/5 一周以上 新手友好度 32/100
auth0/ruby-auth0#606 · 2 条评论 · 1 个 reaction ·
维护者通常 1 天内回复
-
MFA API Bearer token issue可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭bug
难度 3/5 1-2 天 新手友好度 35/100
auth0/ruby-auth0#556 · 2 条评论 ·
维护者通常 1 天内回复
-
feature request
难度 3/5 1-2 天 新手友好度 35/100
auth0/ruby-auth0#500 · 1 条评论 ·
维护者通常 1 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 82/100
维护者通常 1 天内回复
-
data error
难度 2/5 1-3 小时 新手友好度 74/100
riscv/riscv-unified-db#2794 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 65/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 85/100
heartcombo/simple_form#1874 ·
-
难度 2/5 1-3 小时 新手友好度 85/100
we-promise/sure#4603 ·
维护者通常 1 天内回复