Skip to content

Commit e19c783

Browse files
authored
fix: race condition UI remotecfg (#2160)
* Refactor ui remtoecfg components to avoid race condition * Fix accidental cast to pointer that should have been struct * Update changelog
1 parent 48f9206 commit e19c783

File tree

3 files changed

+74
-43
lines changed

3 files changed

+74
-43
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Main (unreleased)
4141

4242
- Fixed a race condition that could lead to a deadlock when using `import` statements, which could lead to a memory leak on `/metrics` endpoint of an Alloy instance. (@thampiotr)
4343

44+
- Fix a race condition where the ui service was dependent on starting after the remotecfg service, which is not guaranteed. (@dehaansa & @erikbaranowski)
45+
4446
### Other changes
4547

4648
- Change the stability of the `livedebugging` feature from "experimental" to "generally available". (@wildum)

internal/service/ui/ui.go

+1-4
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,7 @@ func (s *Service) Data() any {
7878
func (s *Service) ServiceHandler(host service.Host) (base string, handler http.Handler) {
7979
r := mux.NewRouter()
8080

81-
remotecfgSvc, _ := host.GetService(remotecfg_service.ServiceName)
82-
remotecfgHost := remotecfgSvc.Data().(remotecfg_service.Data).Host
83-
84-
fa := api.NewAlloyAPI(host, remotecfgHost, s.opts.CallbackManager)
81+
fa := api.NewAlloyAPI(host, s.opts.CallbackManager)
8582
fa.RegisterRoutes(path.Join(s.opts.UIPrefix, "/api/v0/web"), r)
8683
ui.RegisterRoutes(s.opts.UIPrefix, r)
8784

internal/web/api/api.go

+71-39
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,19 @@ import (
1818
"github.com/grafana/alloy/internal/service"
1919
"github.com/grafana/alloy/internal/service/cluster"
2020
"github.com/grafana/alloy/internal/service/livedebugging"
21+
"github.com/grafana/alloy/internal/service/remotecfg"
2122
"github.com/prometheus/prometheus/util/httputil"
2223
)
2324

2425
// AlloyAPI is a wrapper around the component API.
2526
type AlloyAPI struct {
2627
alloy service.Host
27-
remotecfg service.Host
2828
CallbackManager livedebugging.CallbackManager
2929
}
3030

3131
// NewAlloyAPI instantiates a new Alloy API.
32-
func NewAlloyAPI(alloy, remotecfg service.Host, CallbackManager livedebugging.CallbackManager) *AlloyAPI {
33-
return &AlloyAPI{alloy: alloy, remotecfg: remotecfg, CallbackManager: CallbackManager}
32+
func NewAlloyAPI(alloy service.Host, CallbackManager livedebugging.CallbackManager) *AlloyAPI {
33+
return &AlloyAPI{alloy: alloy, CallbackManager: CallbackManager}
3434
}
3535

3636
// RegisterRoutes registers all the API's routes.
@@ -40,67 +40,99 @@ func (a *AlloyAPI) RegisterRoutes(urlPrefix string, r *mux.Router) {
4040
// component IDs.
4141

4242
r.Handle(path.Join(urlPrefix, "/modules/{moduleID:.+}/components"), httputil.CompressionHandler{Handler: listComponentsHandler(a.alloy)})
43-
r.Handle(path.Join(urlPrefix, "/remotecfg/modules/{moduleID:.+}/components"), httputil.CompressionHandler{Handler: listComponentsHandler(a.remotecfg)})
43+
r.Handle(path.Join(urlPrefix, "/remotecfg/modules/{moduleID:.+}/components"), httputil.CompressionHandler{Handler: listComponentsHandlerRemoteCfg(a.alloy)})
4444

4545
r.Handle(path.Join(urlPrefix, "/components"), httputil.CompressionHandler{Handler: listComponentsHandler(a.alloy)})
46-
r.Handle(path.Join(urlPrefix, "/remotecfg/components"), httputil.CompressionHandler{Handler: listComponentsHandler(a.remotecfg)})
46+
r.Handle(path.Join(urlPrefix, "/remotecfg/components"), httputil.CompressionHandler{Handler: listComponentsHandlerRemoteCfg(a.alloy)})
4747

4848
r.Handle(path.Join(urlPrefix, "/components/{id:.+}"), httputil.CompressionHandler{Handler: getComponentHandler(a.alloy)})
49-
r.Handle(path.Join(urlPrefix, "/remotecfg/components/{id:.+}"), httputil.CompressionHandler{Handler: getComponentHandler(a.remotecfg)})
49+
r.Handle(path.Join(urlPrefix, "/remotecfg/components/{id:.+}"), httputil.CompressionHandler{Handler: getComponentHandlerRemoteCfg(a.alloy)})
5050

5151
r.Handle(path.Join(urlPrefix, "/peers"), httputil.CompressionHandler{Handler: getClusteringPeersHandler(a.alloy)})
5252
r.Handle(path.Join(urlPrefix, "/debug/{id:.+}"), liveDebugging(a.alloy, a.CallbackManager))
5353
}
5454

5555
func listComponentsHandler(host service.Host) http.HandlerFunc {
5656
return func(w http.ResponseWriter, r *http.Request) {
57-
// moduleID is set from the /modules/{moduleID:.+}/components route above
58-
// but not from the /components route.
59-
var moduleID string
60-
if vars := mux.Vars(r); vars != nil {
61-
moduleID = vars["moduleID"]
62-
}
57+
listComponentsHandlerInternal(host, w, r)
58+
}
59+
}
6360

64-
components, err := host.ListComponents(moduleID, component.InfoOptions{
65-
GetHealth: true,
66-
})
67-
if err != nil {
68-
http.Error(w, err.Error(), http.StatusInternalServerError)
61+
func listComponentsHandlerRemoteCfg(host service.Host) http.HandlerFunc {
62+
return func(w http.ResponseWriter, r *http.Request) {
63+
svc, found := host.GetService(remotecfg.ServiceName)
64+
if !found {
65+
http.Error(w, "remote config service not available", http.StatusInternalServerError)
6966
return
7067
}
7168

72-
bb, err := json.Marshal(components)
73-
if err != nil {
74-
http.Error(w, err.Error(), http.StatusInternalServerError)
75-
return
76-
}
77-
_, _ = w.Write(bb)
69+
listComponentsHandlerInternal(svc.Data().(remotecfg.Data).Host, w, r)
7870
}
7971
}
8072

73+
func listComponentsHandlerInternal(host service.Host, w http.ResponseWriter, r *http.Request) {
74+
// moduleID is set from the /modules/{moduleID:.+}/components route above
75+
// but not from the /components route.
76+
var moduleID string
77+
if vars := mux.Vars(r); vars != nil {
78+
moduleID = vars["moduleID"]
79+
}
80+
81+
components, err := host.ListComponents(moduleID, component.InfoOptions{
82+
GetHealth: true,
83+
})
84+
if err != nil {
85+
http.Error(w, err.Error(), http.StatusInternalServerError)
86+
return
87+
}
88+
89+
bb, err := json.Marshal(components)
90+
if err != nil {
91+
http.Error(w, err.Error(), http.StatusInternalServerError)
92+
return
93+
}
94+
_, _ = w.Write(bb)
95+
}
96+
8197
func getComponentHandler(host service.Host) http.HandlerFunc {
8298
return func(w http.ResponseWriter, r *http.Request) {
83-
vars := mux.Vars(r)
84-
requestedComponent := component.ParseID(vars["id"])
99+
getComponentHandlerInternal(host, w, r)
100+
}
101+
}
85102

86-
component, err := host.GetComponent(requestedComponent, component.InfoOptions{
87-
GetHealth: true,
88-
GetArguments: true,
89-
GetExports: true,
90-
GetDebugInfo: true,
91-
})
92-
if err != nil {
93-
http.NotFound(w, r)
103+
func getComponentHandlerRemoteCfg(host service.Host) http.HandlerFunc {
104+
return func(w http.ResponseWriter, r *http.Request) {
105+
svc, found := host.GetService(remotecfg.ServiceName)
106+
if !found {
107+
http.Error(w, "remote config service not available", http.StatusInternalServerError)
94108
return
95109
}
96110

97-
bb, err := json.Marshal(component)
98-
if err != nil {
99-
http.Error(w, err.Error(), http.StatusInternalServerError)
100-
return
101-
}
102-
_, _ = w.Write(bb)
111+
getComponentHandlerInternal(svc.Data().(remotecfg.Data).Host, w, r)
112+
}
113+
}
114+
115+
func getComponentHandlerInternal(host service.Host, w http.ResponseWriter, r *http.Request) {
116+
vars := mux.Vars(r)
117+
requestedComponent := component.ParseID(vars["id"])
118+
119+
component, err := host.GetComponent(requestedComponent, component.InfoOptions{
120+
GetHealth: true,
121+
GetArguments: true,
122+
GetExports: true,
123+
GetDebugInfo: true,
124+
})
125+
if err != nil {
126+
http.NotFound(w, r)
127+
return
128+
}
129+
130+
bb, err := json.Marshal(component)
131+
if err != nil {
132+
http.Error(w, err.Error(), http.StatusInternalServerError)
133+
return
103134
}
135+
_, _ = w.Write(bb)
104136
}
105137

106138
func getClusteringPeersHandler(host service.Host) http.HandlerFunc {

0 commit comments

Comments
 (0)