hosting
API
hosting
packageAPI reference for the hosting
package.
Imports
(12)
T
type
HostState
HostState represents the current lifecycle state of a Host.
pkg/hosting/host.go:19-19
type HostState int32
S
struct
Host
Host manages the lifecycle of the application.
pkg/hosting/host.go:48-61
type Host struct
Methods
Run
Method
Parameters
ctx
context.Context
Returns
error
func (*Host) Run(ctx context.Context) error
{
ctx, cancel := context.WithCancel(ctx)
defer cancel()
h.mu.Lock()
h.cancel = cancel
h.mu.Unlock()
h.state.Store(int32(HostStarting))
for _, fn := range h.onStart {
fn()
}
startupCtx, startupCancel := context.WithTimeout(ctx, h.startupTimeout)
defer startupCancel()
for _, svc := range h.hostedServices {
if err := svc.Start(startupCtx); err != nil {
startupCancel()
h.shutdownHosted(ctx)
return fmt.Errorf("hosted service start failed: %w", err)
}
}
var wg sync.WaitGroup
errCh := make(chan error, len(h.services))
for _, svc := range h.services {
s := svc
wg.Add(1)
go func() {
defer wg.Done()
if err := s.Execute(ctx); err != nil {
errCh <- err
}
}()
}
h.state.Store(int32(HostRunning))
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
var firstErr error
select {
case <-sigCh:
case <-ctx.Done():
case err := <-errCh:
firstErr = err
}
cancel()
h.state.Store(int32(HostStopping))
h.shutdownHosted(ctx)
stopped := make(chan struct{})
go func() {
wg.Wait()
close(stopped)
}()
timeout := h.ShutdownTimeout
if timeout == 0 {
timeout = 30 * time.Second
}
select {
case <-stopped:
case <-time.After(timeout):
}
for _, fn := range h.onStop {
fn()
}
h.state.Store(int32(HostStopped))
if firstErr != nil {
return firstErr
}
return nil
}
shutdownHosted
Method
Parameters
func (*Host) shutdownHosted(_ context.Context)
{
stopCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
for i := len(h.hostedServices) - 1; i >= 0; i-- {
h.hostedServices[i].Stop(stopCtx)
}
}
Shutdown
Method
Parameters
Returns
error
func (*Host) Shutdown(_ context.Context) error
{
h.mu.Lock()
cancel := h.cancel
h.mu.Unlock()
if cancel != nil {
cancel()
}
return nil
}
OnStart
Method
Parameters
fn
func()
func (*Host) OnStart(fn func())
{
h.onStart = append(h.onStart, fn)
}
OnStop
Method
Parameters
fn
func()
func (*Host) OnStop(fn func())
{
h.onStop = append(h.onStop, fn)
}
AddHostedService
Method
Parameters
svc
HostedService
func (*Host) AddHostedService(svc HostedService)
{
h.hostedServices = append(h.hostedServices, svc)
}
Fields
| Name | Type | Description |
|---|---|---|
| services | []BackgroundService | |
| hostedServices | []HostedService | |
| onStart | []func() | |
| onStop | []func() | |
| Container | *di.Container | |
| Server | *srv.Server | |
| HealthRegistry | *health.Registry | |
| cancel | context.CancelFunc | |
| mu | sync.RWMutex | |
| state | atomic.Int32 | |
| ShutdownTimeout | time.Duration | |
| startupTimeout | time.Duration |
I
interface
BackgroundService
BackgroundService is a long-running service started in parallel.
pkg/hosting/host.go:64-66
type BackgroundService interface
Methods
I
interface
HostedService
HostedService is a managed lifecycle service with explicit Start/Stop.
pkg/hosting/host.go:69-72
type HostedService interface
Methods
S
struct
BackgroundServiceAdapter
BackgroundServiceAdapter wraps a BackgroundService as a HostedService.
pkg/hosting/host.go:75-77
type BackgroundServiceAdapter struct
Methods
Start
Method
Parameters
ctx
context.Context
Returns
error
func (*BackgroundServiceAdapter) Start(ctx context.Context) error
{
errCh := make(chan error, 1)
go func() {
errCh <- a.Svc.Execute(ctx)
}()
select {
case err := <-errCh:
return err
case <-ctx.Done():
return ctx.Err()
}
}
Stop
Method
Parameters
Returns
error
func (*BackgroundServiceAdapter) Stop(_ context.Context) error
{
return nil
}
Fields
| Name | Type | Description |
|---|---|---|
| Svc | BackgroundService |
S
struct
HostBuilder
HostBuilder provides a fluent API for constructing a Host.
pkg/hosting/host.go:218-229
type HostBuilder struct
Methods
ConfigureServices
Method
Parameters
fn
func(*di.Builder)
Returns
func (*HostBuilder) ConfigureServices(fn func(*di.Builder)) *HostBuilder
{
if b.di == nil {
b.di = di.NewBuilder()
}
fn(b.di)
return b
}
ConfigureWeb
Method
Parameters
fn
func(*srv.Server)
Returns
func (*HostBuilder) ConfigureWeb(fn func(*srv.Server)) *HostBuilder
{
if b.web == nil {
b.web = srv.New()
}
fn(b.web)
return b
}
AddService
Method
Parameters
Returns
func (*HostBuilder) AddService(svc BackgroundService) *HostBuilder
{
b.services = append(b.services, svc)
return b
}
AddHostedService
Method
Parameters
svc
HostedService
Returns
func (*HostBuilder) AddHostedService(svc HostedService) *HostBuilder
{
b.hostedServices = append(b.hostedServices, svc)
return b
}
OnStart
Method
Parameters
fn
func()
Returns
func (*HostBuilder) OnStart(fn func()) *HostBuilder
{
b.onStart = append(b.onStart, fn)
return b
}
OnStop
Method
Parameters
fn
func()
Returns
func (*HostBuilder) OnStop(fn func()) *HostBuilder
{
b.onStop = append(b.onStop, fn)
return b
}
WithAddr
Method
Parameters
addr
string
Returns
func (*HostBuilder) WithAddr(addr string) *HostBuilder
{
b.webAddr = addr
return b
}
WithShutdownTimeout
Method
Parameters
Returns
func (*HostBuilder) WithShutdownTimeout(d time.Duration) *HostBuilder
{
b.shutdownTimeout = d
return b
}
WithStartupTimeout
Method
Parameters
Returns
func (*HostBuilder) WithStartupTimeout(d time.Duration) *HostBuilder
{
b.startupTimeout = d
return b
}
WithHealthRegistry
Method
Parameters
Returns
func (*HostBuilder) WithHealthRegistry(r *health.Registry) *HostBuilder
{
b.healthRegistry = r
return b
}
Build
Method
Returns
*Host
func (*HostBuilder) Build() *Host
{
h := &Host{
services: append([]BackgroundService{}, b.services...),
hostedServices: append([]HostedService{}, b.hostedServices...),
onStart: b.onStart,
onStop: b.onStop,
ShutdownTimeout: b.shutdownTimeout,
startupTimeout: b.startupTimeout,
HealthRegistry: b.healthRegistry,
}
if b.di != nil {
c, err := b.di.Build()
if err != nil {
panic(err)
}
h.Container = c
}
if b.web != nil {
addr := b.webAddr
if addr == "" {
addr = ":8080"
}
s := &webService{server: b.web, container: h.Container, addr: addr}
h.services = append(h.services, s)
h.Server = b.web
}
if h.HealthRegistry != nil && h.Server != nil {
h.Server.MapGet("/health/live", func(ctx *srv.Context) error {
return ctx.JSON(200, map[string]string{"status": "alive"})
})
h.Server.MapGet("/health/ready", func(ctx *srv.Context) error {
results := h.HealthRegistry.CheckAll(ctx.Request.Context())
healthy := true
details := make(map[string]string, len(results))
for name, report := range results {
details[name] = report.Status.String()
if report.Status == health.StatusUnhealthy {
healthy = false
}
}
code := 200
status := "ready"
if !healthy {
code = 503
status = "not ready"
}
return ctx.JSON(code, map[string]any{
"status": status,
"details": details,
})
})
}
return h
}
Fields
| Name | Type | Description |
|---|---|---|
| services | []BackgroundService | |
| hostedServices | []HostedService | |
| onStart | []func() | |
| onStop | []func() | |
| di | *di.Builder | |
| web | *srv.Server | |
| webAddr | string | |
| shutdownTimeout | time.Duration | |
| startupTimeout | time.Duration | |
| healthRegistry | *health.Registry |
F
function
NewBuilder
NewBuilder creates a new HostBuilder.
Returns
pkg/hosting/host.go:232-236
func NewBuilder() *HostBuilder
{
return &HostBuilder{
startupTimeout: 15 * time.Second,
}
}
S
webService
pkg/hosting/host.go:352-356
type webService struct
Methods
Execute
Method
Parameters
ctx
context.Context
Returns
error
func (*webService) Execute(ctx context.Context) error
{
errCh := make(chan error, 1)
go func() {
errCh <- w.server.ListenAndServe(w.addr)
}()
select {
case <-ctx.Done():
w.server.Shutdown(context.Background())
return nil
case err := <-errCh:
return err
}
}
Fields
| Name | Type | Description |
|---|---|---|
| server | *srv.Server | |
| container | *di.Container | |
| addr | string |
S
fakeSvc
pkg/hosting/host_test.go:14-16
type fakeSvc struct
Methods
Execute
Method
Parameters
ctx
context.Context
Returns
error
func (*fakeSvc) Execute(ctx context.Context) error
{
f.running.Store(true)
<-ctx.Done()
f.running.Store(false)
return nil
}
Fields
| Name | Type | Description |
|---|---|---|
| running | atomic.Bool |
F
function
TestBuilder_ConfigureServices
Parameters
t
pkg/hosting/host_test.go:25-39
func TestBuilder_ConfigureServices(t *testing.T)
{
h := NewBuilder().
ConfigureServices(func(b *di.Builder) {
di.RegisterInstance[*fakeSvc](b, &fakeSvc{})
}).
Build()
if h.Container == nil {
t.Fatal("Container should not be nil")
}
svc := di.ResolveType[*fakeSvc](h.Container)
if svc == nil {
t.Fatal("should resolve fakeSvc")
}
}
F
function
TestBuilder_ConfigureWeb
Parameters
t
pkg/hosting/host_test.go:41-58
func TestBuilder_ConfigureWeb(t *testing.T)
{
h := NewBuilder().
ConfigureWeb(func(app *srv.Server) {
app.MapGet("/test", func(c *srv.Context) error {
c.String(200, "ok")
return nil
})
}).
WithAddr(":0").
Build()
if h.Server == nil {
t.Fatal("Server should not be nil")
}
if len(h.services) != 1 {
t.Fatalf("expected 1 service (web), got %d", len(h.services))
}
}
F
function
TestBuilder_AddService
Parameters
t
pkg/hosting/host_test.go:60-66
func TestBuilder_AddService(t *testing.T)
{
svc := &fakeSvc{}
host := NewBuilder().AddService(svc).Build()
if len(host.services) != 1 {
t.Fatalf("expected 1 service, got %d", len(host.services))
}
}
F
function
TestBuilder_WithAddr
Parameters
t
pkg/hosting/host_test.go:68-73
func TestBuilder_WithAddr(t *testing.T)
{
b := NewBuilder().WithAddr(":1234")
if b.webAddr != ":1234" {
t.Errorf("addr: got %q, want :1234", b.webAddr)
}
}
F
function
TestHost_Lifecycle
Parameters
t
pkg/hosting/host_test.go:75-98
func TestHost_Lifecycle(t *testing.T)
{
svc := &fakeSvc{}
host := NewBuilder().AddService(svc).Build()
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
go func() {
host.Run(ctx)
close(done)
}()
time.Sleep(20 * time.Millisecond)
if !svc.running.Load() {
t.Error("service should be running")
}
cancel()
<-done
time.Sleep(20 * time.Millisecond)
if svc.running.Load() {
t.Error("service should have shut down")
}
}
S
struct
fakeHosted
pkg/hosting/host_test.go:102-106
type fakeHosted struct
Methods
Fields
| Name | Type | Description |
|---|---|---|
| started | atomic.Bool | |
| stopped | atomic.Bool | |
| startErr | error |
F
function
TestHostedService_Lifecycle
Parameters
t
pkg/hosting/host_test.go:121-144
func TestHostedService_Lifecycle(t *testing.T)
{
svc := &fakeHosted{}
host := NewBuilder().AddHostedService(svc).Build()
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
go func() {
host.Run(ctx)
close(done)
}()
time.Sleep(50 * time.Millisecond)
if !svc.started.Load() {
t.Error("hosted service should be started")
}
cancel()
<-done
if !svc.stopped.Load() {
t.Error("hosted service should be stopped")
}
}
F
function
TestHostedService_StartupFailure
Parameters
t
pkg/hosting/host_test.go:146-154
func TestHostedService_StartupFailure(t *testing.T)
{
failSvc := &fakeHosted{startErr: context.DeadlineExceeded}
host := NewBuilder().AddHostedService(failSvc).Build()
err := host.Run(context.Background())
if err == nil {
t.Fatal("expected error from startup failure")
}
}
F
function
TestHost_StartupTimeout
Parameters
t
pkg/hosting/host_test.go:156-179
func TestHost_StartupTimeout(t *testing.T)
{
slowSvc := &slowHosted{}
host := NewBuilder().
AddHostedService(slowSvc).
WithStartupTimeout(100*time.Millisecond).
Build()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
errCh := make(chan error, 1)
go func() {
errCh <- host.Run(ctx)
}()
select {
case err := <-errCh:
if err == nil {
t.Fatal("expected timeout error")
}
case <-time.After(2 * time.Second):
t.Fatal("timed out waiting for Run to return")
}
}
S
struct
slowHosted
pkg/hosting/host_test.go:181-181
type slowHosted struct
Methods
Start
Method
Parameters
ctx
context.Context
Returns
error
func (*slowHosted) Start(ctx context.Context) error
{
select {
case <-time.After(5 * time.Second):
return nil
case <-ctx.Done():
return ctx.Err()
}
}
Stop
Method
Parameters
Returns
error
func (*slowHosted) Stop(_ context.Context) error
{ return nil }
F
function
TestHost_State
Parameters
t
pkg/hosting/host_test.go:193-198
func TestHost_State(t *testing.T)
{
host := NewBuilder().Build()
if host.State() != HostStarting {
t.Errorf("initial state = %v, want Starting", host.State())
}
}
F
function
TestHost_AddHostedService
Parameters
t
pkg/hosting/host_test.go:200-207
func TestHost_AddHostedService(t *testing.T)
{
svc := &fakeHosted{}
host := &Host{}
host.AddHostedService(svc)
if len(host.hostedServices) != 1 {
t.Fatalf("expected 1 hosted service, got %d", len(host.hostedServices))
}
}
F
function
TestBackgroundServiceAdapter
Parameters
t
pkg/hosting/host_test.go:209-228
func TestBackgroundServiceAdapter(t *testing.T)
{
fake := &fakeSvc{}
adapter := &BackgroundServiceAdapter{Svc: fake}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
errCh := make(chan error, 1)
go func() {
errCh <- adapter.Start(ctx)
}()
time.Sleep(20 * time.Millisecond)
cancel()
err := <-errCh
if err != nil && err != context.Canceled {
t.Errorf("unexpected error: %v", err)
}
}
F
function
TestHost_WithHealthRegistry_ReadyEndpoint
Parameters
t
pkg/hosting/host_test.go:230-243
func TestHost_WithHealthRegistry_ReadyEndpoint(t *testing.T)
{
reg := health.NewRegistry()
reg.Register("db", &testChecker{status: health.StatusHealthy})
h := NewBuilder().
ConfigureWeb(func(s *srv.Server) {}).
WithHealthRegistry(reg).
WithAddr(":0").
Build()
if h.Server == nil {
t.Fatal("Server should be set")
}
}
S
struct
testChecker
pkg/hosting/host_test.go:245-247
type testChecker struct
Methods
Check
Method
Parameters
Returns
func (*testChecker) Check(_ context.Context) health.Report
{
return health.Report{Status: c.status}
}
Fields
| Name | Type | Description |
|---|---|---|
| status | health.Status |
F
function
TestHost_ShutdownTimeout
Parameters
t
pkg/hosting/host_test.go:253-258
func TestHost_ShutdownTimeout(t *testing.T)
{
b := NewBuilder().WithShutdownTimeout(5 * time.Second)
if b.shutdownTimeout != 5*time.Second {
t.Errorf("timeout = %v, want 5s", b.shutdownTimeout)
}
}
F
function
TestHost_DefaultStartupTimeout
Parameters
t
pkg/hosting/host_test.go:260-265
func TestHost_DefaultStartupTimeout(t *testing.T)
{
b := NewBuilder()
if b.startupTimeout != 15*time.Second {
t.Errorf("default startup timeout = %v, want 15s", b.startupTimeout)
}
}