Suppose you have a type that needs explicit destruction to release resources:
type MyType struct {
...
}
func New() *MyType {
// acquire resources...
return &MyType{ ... }
}
// other (m* MyType) functions...
func (m *MyType) Close() {
// release resources
}
If the users of your library forget to call Close()
the resources won't be freed until the process terminates.
With some changes it is possible to add to MyType the ability to automatically release the resources during GC. This can be done transparently without requiring changes to the users of your type.
Rename your type (MyType -> innerType) and embed it in a new type with the same name as the original type (MyType).
This way MyType becomes an opaque handle for the real struct (now called innerType).
type MyType struct {
*innerType
}
type innerType struct {
...
}
func New() *MyType {
// acquire resources...
return &MyType{ &innerType{ ... } }
}
// other (m* innerType) functions...
func (m *innerType) Close() {
// release resources
}
If this was done correctly, your implementation should still work correctly and users in other packages should not be affected.
Create the following type that will be used to detect when the handle becomes unreachable. To avoid interations between finalizers and tiny allocations (<16 bytes) make sure the type size is at least 16 bytes.
type unreachableGuard [16]byte
Modify the handle type to include a pointer to the guard:
type MyType struct {
*innerType
*unreachableGuard
}
Modify the constructor to create the guard:
func New() *MyType {
// acquire resources...
return &MyType{ &innerType{ ... }, new(unreachableGuard) }
}
Create a finalizer that will release the resources when the handle becomes unreachable:
func New() *MyType {
// acquire resources...
i := &innerType{ ... }
m := &MyType{ i, new(unreachableGuard) }
runtime.SetFinalizer(m.unreachableGuard, func (_ *unreachableGuard) {
i.Close()
})
return m
}
Add tests to ensure that the resources are getting automatically released:
func TestAutomaticRelease(t *testing.T) {
m := New()
i := m.innerType
// m is now unreacheable
runtime.GC()
// assume i.resource is set to nil when Close() is called
for i.resource != nil {
runtime.Gosched()
}
}
Thank you!
You are super genius!