Skip to content

[server] init subscriber slices with len=0 in helper constructors - #19643

Closed
SarthakB11 wants to merge 1 commit into
meshery:masterfrom
SarthakB11:fix/init-helper-slices-zero-len
Closed

SarthakB11 wants to merge 1 commit into
meshery:masterfrom
SarthakB11:fix/init-helper-slices-zero-len

Conversation

@SarthakB11

Copy link
Copy Markdown

Fixes #19581.

NewConfigurationHelper (server/models/configuration_helper.go:14-20) and NewSummaryHelper (server/models/meshmodel/helper.go:12-16) initialise their subscriber slices with make([]chan struct{}, 10). The slice has length 10 of zero-value (nil) channels before any subscriber appends. Subscribe* then appends real subscribers onto the slice, so iteration in Publish* walks 10 nil entries first and the real subscribers after.

Sending to a nil channel blocks forever. The pre-existing utils.IsClosed guard short-circuited on nil channels (explicit if ch == nil { return true }), but the same helper also drains buffered channels of any queued value (case <-ch: return true), which is bug #17011's root cause.

Change

Switch to make([]chan struct{}, 0) so Subscribe* grows the slice with real channels only.

// server/models/configuration_helper.go
func NewConfigurationHelper() *ConfigurationChannel {
    return &ConfigurationChannel{
        ApplicationsChannel: make([]chan struct{}, 0),
        PatternsChannel:     make([]chan struct{}, 0),
        FiltersChannel:      make([]chan struct{}, 0),
    }
}

// server/models/meshmodel/helper.go
func NewSummaryHelper() *SummaryChannel {
    return &SummaryChannel{
        channel: make([]chan struct{}, 0),
    }
}

Matches the existing pattern in NewDashboardK8sResourcesHelper (server/models/dashboard_k8s_resources.go:14) and NewContextHelper (server/models/k8scontext_helper.go:14), both of which already use length-zero slices.

Why a separate PR

The IsClosed cleanup chain (#19572 → #19580 → #19585) replaces the broken gate. After that chain lands, the publishers no longer block on nil slots (they hit the default branch of the non-blocking select), but they still silently iterate ten no-op slots per publish. Fixing the root cause at construction is independent and correct on master too.

Signed commits

  • Yes, I signed my commits.

NewConfigurationHelper and NewSummaryHelper pre-fill subscriber slices
with 10 nil channels via make([]chan struct{}, 10), so iteration in the
matching Publish* methods walks 10 nil entries before any real
subscriber. Sending to a nil channel blocks forever; the pre-existing
utils.IsClosed guard short-circuited (it explicitly returns true on
nil), but it also drained buffered channels of any queued value.

Switch to make([]chan struct{}, 0) so Subscribe* grows the slice with
real channels only. Matches the existing pattern in
NewDashboardK8sResourcesHelper and NewContextHelper.

Fixes meshery#19581.

Signed-off-by: SarthakB11 <[email protected]>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request modifies the initialization of channel slices in ConfigurationChannel and SummaryChannel by setting their initial length to 0. The reviewer identified critical data race vulnerabilities in both files, noting that slices are iterated over without synchronization during publication while being modified under a mutex during subscription. Recommendations include protecting all slice accesses with the existing mutexes and utilizing capacity pre-allocation to maintain performance.

Comment on lines +18 to +20
ApplicationsChannel: make([]chan struct{}, 0),
PatternsChannel: make([]chan struct{}, 0),
FiltersChannel: make([]chan struct{}, 0),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

While initializing with a length of 0 correctly prevents iterating over nil slices, the ConfigurationChannel struct implementation contains a significant data race. The Publish* methods (e.g., PublishApplications at line 30) iterate over these slices without any synchronization, while the Subscribe* methods modify them using the mx mutex. Accessing a slice while it is being modified in another goroutine is a data race and can lead to crashes or undefined behavior. You should ensure that all accesses to these slices are protected by the mutex, for example by copying the slice while holding the lock before iterating.

Additionally, to retain the performance benefit of pre-allocation (which the previous code attempted with make(..., 10)), you can use a capacity of 10 with a length of 0.

Suggested change
ApplicationsChannel: make([]chan struct{}, 0),
PatternsChannel: make([]chan struct{}, 0),
FiltersChannel: make([]chan struct{}, 0),
ApplicationsChannel: make([]chan struct{}, 0, 10),
PatternsChannel: make([]chan struct{}, 0, 10),
FiltersChannel: make([]chan struct{}, 0, 10),

func NewSummaryHelper() *SummaryChannel {
return &SummaryChannel{
channel: make([]chan struct{}, 10),
channel: make([]chan struct{}, 0),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to ConfigurationChannel, the SummaryChannel struct suffers from a data race. The Publish method (line 26) reads the channel slice without holding the mx mutex, which is used by the Subscribe method. Iterating over a slice while it is being modified in another goroutine is a data race and can lead to crashes or undefined behavior.

Consider using make([]chan struct{}, 0, 10) to provide an initial capacity while keeping the initial length at 0, maintaining the optimization from the previous implementation.

Suggested change
channel: make([]chan struct{}, 0),
channel: make([]chan struct{}, 0, 10),

@github-actions

Copy link
Copy Markdown
Contributor

Commit SHA: 8378196f35b770afbddcb189e969cc026bd3b7f7

END-TO-END TESTS

  • Testing started at: May 23rd 2026, 6:13:52 pm

📦 Test Result Summary

  • ✅ 0 passed
  • ❌ 1 failed
  • ⚠️ 0 flaked
  • ⏩ 0 skipped

⌛ Duration: 1 minutes and 6 seconds

Overall Result: 👎 Some tests failed.

[Show/Hide] Test Result Details
Test Provider Browser Test Case Tags Result
1 Local local-setup authenticate as Local provider ❌

❌ Failed Test Details

The following tests failed. Click on each to see the failure reason:

❌ authenticate as Local provider (Local - local-setup)

File Location: Not Found

Error Message:

�[31mTest timeout of 60000ms exceeded.�[39m

@SarthakB11

Copy link
Copy Markdown
Author

Master already inits these slices with make([]chan struct{}, 0) via a03fd9a (#20375). Closing as superseded.

@SarthakB11 SarthakB11 closed this Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[server] NewConfigurationHelper and NewSummaryHelper pre-fill subscriber slices with nil channels

1 participant