Wails encodes every binding result twice, once for a debug log that is off #286

Open
opened 2026-10-06 02:40:31 +00:00 by yonlu · 0 comments
Owner

Every bound-method result is JSON-encoded twice, and the second copy exists only for a debug log line that is usually disabled.

github.com/wailsapp/wails/v3/pkg/application/messageprocessor_call.go (beta.8, and unchanged in beta.28):

defer func() {
    var jsonResult []byte
    jsonResult, _ = json.Marshal(result)
    m.Debug("Binding call complete:", ..., "args", string(jsonArgs), "result", string(jsonResult))
}()

json.Marshal and the string() conversion run whatever the log level is. For GetTracks at 26 138 tracks that is a 20.5 MB encode plus a 20.5 MB copy per call, on top of the real response encode — the processCallMethod.func4 frame is 174 MB of the 484 MB alloc_space in a profile of one launch (2026-10-05). After a forced GC, 25 MB of the 31 MB live heap is still jsontext.stateMachine.MayAppendDelim: an encoder buffer sized by the largest result ever encoded, kept by the pool.

Options, in order of preference:

  1. Upstream fix: guard with m.logger.Enabled(ctx, slog.LevelDebug). Send the PR.
  2. Meanwhile, keep results small (plan 023's track table makes this ~5× smaller), which shrinks both encodes and the retained buffer.
  3. A replace to a patched copy — not done; it is a fork to maintain, and needs a decision.
Every bound-method result is JSON-encoded **twice**, and the second copy exists only for a debug log line that is usually disabled. `github.com/wailsapp/wails/v3/pkg/application/messageprocessor_call.go` (beta.8, and unchanged in beta.28): ```go defer func() { var jsonResult []byte jsonResult, _ = json.Marshal(result) m.Debug("Binding call complete:", ..., "args", string(jsonArgs), "result", string(jsonResult)) }() ``` `json.Marshal` and the `string()` conversion run whatever the log level is. For `GetTracks` at 26 138 tracks that is a 20.5 MB encode plus a 20.5 MB copy per call, on top of the real response encode — the `processCallMethod.func4` frame is 174 MB of the 484 MB `alloc_space` in a profile of one launch (2026-10-05). After a forced GC, 25 MB of the 31 MB live heap is still `jsontext.stateMachine.MayAppendDelim`: an encoder buffer sized by the largest result ever encoded, kept by the pool. Options, in order of preference: 1. Upstream fix: guard with `m.logger.Enabled(ctx, slog.LevelDebug)`. Send the PR. 2. Meanwhile, keep results small (plan 023's track table makes this ~5× smaller), which shrinks both encodes and the retained buffer. 3. A `replace` to a patched copy — not done; it is a fork to maintain, and needs a decision.
yonlu added the
Priority
Medium
3
Platform/DesktopPlatform/AndroidKind/Enhancement
labels 2026-10-06 02:40:31 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yonlu/yellowjacket#286