Service Disposal & Cleanup in Parsley
When building robust Go applications, managing the lifecycle of resources such as database connections, file handles, or network clients is critical. Parsley provides built-in support for explicit resource cleanup and automatic service disposal through the Disposable interface and resolver shutdown methods.
The Disposable Interface
Any service that requires explicit cleanup when it is no longer needed can implement the types.Disposable interface:
type Disposable interface {
Dispose(ctx context.Context) error
}
When services managed by Parsley implement Dispose(ctx context.Context) error, the framework can automatically track them and invoke their cleanup routines during application shutdown or scope disposal.
Singleton Service Disposal: Resolver.Shutdown
Singleton services remain valid for the lifetime of the resolver. To clean up all registered singleton services that implement Disposable, you can call the Shutdown method on the Resolver interface:
err := resolver.Shutdown(ctx)
Example
Consider a disposable service registered as a singleton:
type DatabaseClient struct {
closed bool
}
func (db *DatabaseClient) Dispose(ctx context.Context) error {
db.closed = true
return nil
}
func NewDatabaseClient() *DatabaseClient {
return &DatabaseClient{}
}
Registering and shutting down the singleton service:
registry := registration.NewServiceRegistry()
_ = registration.RegisterSingleton(registry, NewDatabaseClient)
resolver := resolving.NewResolver(registry)
ctx := resolving.NewScopedContext(context.Background())
// Resolve the singleton service
client, _ := resolving.ResolveRequiredService[*DatabaseClient](ctx, resolver)
// Shut down the resolver to clean up singletons
err := resolver.Shutdown(ctx)
if err != nil {
// Handle error (or NewAggregateError if multiple services return errors)
}
Scoped Service Disposal: DisposeScope
For services registered with a scoped lifetime, instances are tied to a specific context (such as an HTTP request or task scope). You can dispose of all scoped services associated with a context by calling DisposeScope:
err := resolving.DisposeScope(ctx)
Example
registry := registration.NewServiceRegistry()
_ = registration.RegisterScoped(registry, NewDatabaseClient)
resolver := resolving.NewResolver(registry)
ctx := resolving.NewScopedContext(context.Background())
// Resolve scoped service
client, _ := resolving.ResolveRequiredService[*DatabaseClient](ctx, resolver)
// Dispose the scope when the request or task completes
err := resolving.DisposeScope(ctx)
Note: Transient services are not tracked by the resolver or scope bags and are not automatically disposed by
ShutdownorDisposeScope. Callers are responsible for cleaning up transient instances if needed.
Disposal Order and Thread Safety
- Reverse Order of Creation: When disposing singleton or scoped instances, Parsley automatically tracks instances in an internal container (
InstanceBag) and disposes them in the reverse order of their creation. This ensures that dependencies created earlier outlive their dependents during teardown. - Thread Safety: Service disposal is thread-safe, protecting against concurrent shutdown or scope disposal requests.
- Error Aggregation: If multiple services return an error during
Dispose, Parsley aggregates them usingParsleyErrorinpkg/types.
Automatic Application Shutdown
When bootstrapping applications using bootstrap.RunParsleyApplication, Parsley automatically handles the disposal of singleton and scoped services upon application exit, ensuring clean resource teardown without manual Shutdown calls.