Skip to content

Commit 77fd089

Browse files
vjeffreyclaude
andauthored
⚡ Stream-decode Windows firewall rules to reduce memory (#7099)
* ⚡ stream-decode Windows firewall rules and profiles JSON Replace io.ReadAll + json.Unmarshal with json.NewDecoder streaming in ParseWindowsFirewallRules and ParseWindowsFirewallProfiles. This avoids holding the entire raw byte buffer and the parsed struct slice in memory simultaneously, roughly halving peak memory usage on machines with thousands of firewall rules. Also handles the PowerShell single-object edge case where exactly one rule/profile is emitted as a bare {} instead of [{}]. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ⚡ address review: validate token, use MultiReader, extract generic helper - Validate that a non-array token is actually '{' before treating it as a single object; return a clear error for unexpected tokens (null, etc.) - Use io.MultiReader instead of manual byte splicing to avoid intermediate allocations in the single-object fallback path - Extract streamDecodeJSONArray[T] generic helper to deduplicate the identical logic between ParseWindowsFirewallRules and ParseWindowsFirewallProfiles Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d896886 commit 77fd089

File tree

1 file changed

+45
-27
lines changed

1 file changed

+45
-27
lines changed

providers/os/resources/windows/firewall.go

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
package windows
55

66
import (
7+
"bytes"
78
"encoding/json"
9+
"fmt"
810
"io"
911
)
1012

@@ -33,24 +35,56 @@ type WindowsFirewallRule struct {
3335
PolicyStoreSourceType int64 `json:"PolicyStoreSourceType"`
3436
}
3537

36-
func ParseWindowsFirewallRules(input io.Reader) ([]WindowsFirewallRule, error) {
37-
data, err := io.ReadAll(input)
38+
// streamDecodeJSONArray stream-decodes a JSON array from input, returning
39+
// elements one at a time to avoid buffering the entire payload. It also
40+
// handles the PowerShell quirk where a single-element result is emitted as
41+
// a bare object instead of a one-element array.
42+
func streamDecodeJSONArray[T any](input io.Reader) ([]T, error) {
43+
dec := json.NewDecoder(input)
44+
45+
// Read the opening token of the JSON value.
46+
tok, err := dec.Token()
47+
if err == io.EOF {
48+
return []T{}, nil
49+
}
3850
if err != nil {
3951
return nil, err
4052
}
4153

42-
// for empty result set do not get the '{}', therefore lets abort here
43-
if len(data) == 0 {
44-
return []WindowsFirewallRule{}, nil
54+
delim, isDelim := tok.(json.Delim)
55+
56+
// PowerShell emits a bare object when there is exactly one element.
57+
if isDelim && delim == '{' {
58+
var item T
59+
if err := json.NewDecoder(io.MultiReader(
60+
bytes.NewReader([]byte{'{'}),
61+
dec.Buffered(),
62+
input,
63+
)).Decode(&item); err != nil {
64+
return nil, err
65+
}
66+
return []T{item}, nil
4567
}
4668

47-
var winFirewallRules []WindowsFirewallRule
48-
err = json.Unmarshal(data, &winFirewallRules)
49-
if err != nil {
50-
return nil, err
69+
if !isDelim || delim != '[' {
70+
return nil, fmt.Errorf("unexpected JSON token %v; expected '[' or '{'", tok)
5171
}
5272

53-
return winFirewallRules, nil
73+
// Stream-decode array elements one at a time.
74+
var items []T
75+
for dec.More() {
76+
var item T
77+
if err := dec.Decode(&item); err != nil {
78+
return nil, err
79+
}
80+
items = append(items, item)
81+
}
82+
83+
return items, nil
84+
}
85+
86+
func ParseWindowsFirewallRules(input io.Reader) ([]WindowsFirewallRule, error) {
87+
return streamDecodeJSONArray[WindowsFirewallRule](input)
5488
}
5589

5690
type WindowsFirewallSettings struct {
@@ -119,21 +153,5 @@ type WindowsFirewallProfile struct {
119153
}
120154

121155
func ParseWindowsFirewallProfiles(input io.Reader) ([]WindowsFirewallProfile, error) {
122-
data, err := io.ReadAll(input)
123-
if err != nil {
124-
return nil, err
125-
}
126-
127-
// for empty result set do not get the '{}', therefore lets abort here
128-
if len(data) == 0 {
129-
return []WindowsFirewallProfile{}, nil
130-
}
131-
132-
var winFirewallProfiles []WindowsFirewallProfile
133-
err = json.Unmarshal(data, &winFirewallProfiles)
134-
if err != nil {
135-
return nil, err
136-
}
137-
138-
return winFirewallProfiles, nil
156+
return streamDecodeJSONArray[WindowsFirewallProfile](input)
139157
}

0 commit comments

Comments
 (0)