Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Program execution begins by initializing the main package and then invoking the function main. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.
// Без использования выделенных деструкторов.
type Resource struct {
closed bool
r1 *Resource1
r2 *Resource2
}
func NewResource() (*Resource, error) {
r1, err := NewResource1() // Опускаю обработку ошибок.
r2, err := NewResource2(r1)
if err != nil { // А тут для примера нужно написать бойлерпрейт.
r1.Close() // Тут обработка ошибки не пропущена, так нередко и пишут.
return err
}
return &Resource{r1, r2}, nil
}
func (r *Resource) Close() error {
if r.closed { /* ошибка */ }
defer r.r1.Close() // Либо использовать multierror (гарантии для паники опциональны).
defer r.r2.Close()
return nil
}
type ResourceWithLog struct {
closed bool
*Resource
log *Logger
}
func NewResourceWithLog(log *Logger) (*ResourceWithLog, error) {
r, err := NewResource() // Опускаю обработку ошибок.
return ResourceWithLog{r, log}, nil
}
func (r *ResourceWithLog) Close() error {
if r.closed { /* ошибка */ }
defer r.log.Print("Конец освобождения ресурса")
defer r.r1.Close() // Либо использовать multierror, но нет гарантий для случая паники.
defer r.r2.Close()
defer r.log.Print("Начало освобождения ресурса")
return nil
}
// С использованием деструкторов.
type Resource struct {
r2 *Resource2
}
func NewResource() (*Resource, kdone.Destructor, error) {
reaper := kdone.NewReaper()
defer reaper.MustFinalize()
r1, err := NewResource1() // Опускаю обработку ошибок.
reaper.MustAssume(r1)
r2, err := NewResource2(r1) // Опускаю обработку ошибок.
reaper.MustAssume(r2)
return &Resource{r2}, reaper.MustRelease(), nil
}
func NewResourceWithLog(log *Logger) (*Resource, kdone.Destructor, error) {
r, dtor, err := NewResource() // Опускаю обработку ошибок.
return r, kdone.DestructorFunc(func() error {
defer log.Print("Конец освобождения ресурса")
log.Print("Начало освобождения ресурса")
return dtor.Destroy()
}), nil
}
Благодарю за пример, идея стала яснее
Относительно закрытия в другом потоке — кейс из реальности: поднимается хттп-сервер, к которому цепляются хендлеры с состоянием.
Состояние читается из конфигурации, количество хендлеров зависит только от того, что в конфиге наворотит юзер.
Далее, запускается дежурная горутина, которая по сигналу перебирает срез указателей на структуры (через Closer) и закрывает их, чтобы выполнить gracefull shutdown.
Как в этом случае красиво передать пачку деструкторов?
Можно сделать срез с функциями, конечно, но если дежурная горутина берется из другой либы и ожидает Closer, придется делать обёртку
type Closable struct { kdone.Destructor }
func (c Closable) Close() error { return c.Destroy() }
// При передаче:
go Cleanup(Closable{reaper.MustRelease()})
Освобождение ресурсов в GO