Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit e07b966

Browse files
committed
Add Close() method to Project to release resources
Problem Observed Goroutine leaks because there is no way to "close" projects Problem Detail NewProject() creates a defaultListener in it. The listener starts a new goroutine in NewDefaultListener(). We currently don't have a method to stop this goroutine. Suggested Resolution This commit adds a new method Close() to release resources tied to a Project.
1 parent 8e4221d commit e07b966

File tree

3 files changed

+34
-13
lines changed

3 files changed

+34
-13
lines changed

project/listener.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ type defaultListener struct {
3939
}
4040

4141
// NewDefaultListener create a default listener for the specified project.
42-
func NewDefaultListener(p *Project) chan<- events.Event {
43-
l := defaultListener{
42+
func NewDefaultListener(p *Project) *defaultListener {
43+
l := &defaultListener{
4444
listenChan: make(chan events.Event),
4545
project: p,
4646
}
4747
go l.start()
48-
return l.listenChan
48+
return l
4949
}
5050

5151
func (d *defaultListener) start() {
@@ -79,3 +79,7 @@ func (d *defaultListener) start() {
7979
}
8080
}
8181
}
82+
83+
func (d *defaultListener) close() {
84+
close(d.listenChan)
85+
}

project/project.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,16 @@ type Project struct {
3535
ReloadCallback func() error
3636
ParseOptions *config.ParseOptions
3737

38-
runtime RuntimeProject
39-
networks Networks
40-
volumes Volumes
41-
configVersion string
42-
context *Context
43-
reload []string
44-
upCount int
45-
listeners []chan<- events.Event
46-
hasListeners bool
38+
runtime RuntimeProject
39+
networks Networks
40+
volumes Volumes
41+
configVersion string
42+
context *Context
43+
reload []string
44+
upCount int
45+
listeners []chan<- events.Event
46+
hasListeners bool
47+
defaultListener *defaultListener
4748
}
4849

4950
// NewProject creates a new project with the specified context.
@@ -93,11 +94,18 @@ func NewProject(context *Context, runtime RuntimeProject, parseOptions *config.P
9394

9495
context.Project = p
9596

96-
p.listeners = []chan<- events.Event{NewDefaultListener(p)}
97+
p.defaultListener = NewDefaultListener(p)
98+
p.listeners = []chan<- events.Event{p.defaultListener.listenChan}
9799

98100
return p
99101
}
100102

103+
// Parse releases resources attached to the project
104+
func (p *Project) Close() error {
105+
p.defaultListener.close()
106+
return nil
107+
}
108+
101109
// Parse populates project information based on its context. It sets up the name,
102110
// the composefile and the composebytes (the composefile content).
103111
func (p *Project) Parse() error {

project/project_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func TestTwoCall(t *testing.T) {
6464
p := NewProject(&Context{
6565
ServiceFactory: factory,
6666
}, nil, nil)
67+
defer p.Close()
6768
p.ServiceConfigs = config.NewServiceConfigs()
6869
p.ServiceConfigs.Add("foo", &config.ServiceConfig{})
6970

@@ -83,6 +84,7 @@ func TestTwoCall(t *testing.T) {
8384
func TestGetServiceConfig(t *testing.T) {
8485

8586
p := NewProject(&Context{}, nil, nil)
87+
defer p.Close()
8688
p.ServiceConfigs = config.NewServiceConfigs()
8789
fooService := &config.ServiceConfig{}
8890
p.ServiceConfigs.Add("foo", fooService)
@@ -112,6 +114,7 @@ func TestParseWithBadContent(t *testing.T) {
112114
[]byte("garbage"),
113115
},
114116
}, nil, nil)
117+
defer p.Close()
115118

116119
err := p.Parse()
117120
if err == nil {
@@ -129,6 +132,7 @@ func TestParseWithGoodContent(t *testing.T) {
129132
[]byte("not-garbage:\n image: foo"),
130133
},
131134
}, nil, nil)
135+
defer p.Close()
132136

133137
err := p.Parse()
134138
if err != nil {
@@ -142,6 +146,7 @@ func TestParseWithDefaultEnvironmentLookup(t *testing.T) {
142146
[]byte("not-garbage:\n image: foo:${version}"),
143147
},
144148
}, nil, nil)
149+
defer p.Close()
145150

146151
err := p.Parse()
147152
if err != nil {
@@ -165,6 +170,7 @@ func TestEnvironmentResolve(t *testing.T) {
165170
ServiceFactory: factory,
166171
EnvironmentLookup: &TestEnvironmentLookup{},
167172
}, nil, nil)
173+
defer p.Close()
168174
p.ServiceConfigs = config.NewServiceConfigs()
169175
p.ServiceConfigs.Add("foo", &config.ServiceConfig{
170176
Environment: yaml.MaporEqualSlice([]string{
@@ -209,6 +215,7 @@ func TestParseWithMultipleComposeFiles(t *testing.T) {
209215
p := NewProject(&Context{
210216
ComposeBytes: [][]byte{configOne, configTwo},
211217
}, nil, nil)
218+
defer p.Close()
212219

213220
err := p.Parse()
214221

@@ -222,6 +229,7 @@ func TestParseWithMultipleComposeFiles(t *testing.T) {
222229
p = NewProject(&Context{
223230
ComposeBytes: [][]byte{configTwo, configOne},
224231
}, nil, nil)
232+
defer p.Close()
225233

226234
err = p.Parse()
227235

@@ -235,6 +243,7 @@ func TestParseWithMultipleComposeFiles(t *testing.T) {
235243
p = NewProject(&Context{
236244
ComposeBytes: [][]byte{configOne, configTwo, configThree},
237245
}, nil, nil)
246+
defer p.Close()
238247

239248
err = p.Parse()
240249

0 commit comments

Comments
 (0)