Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ useful than saying where the real one is.

### Fixed

- **A short `prune.every` no longer stops the engine idling**
([#496](https://github.com/wslkit/skrog/issues/496)). The scheduled prune ran
`docker` through Skrog's own pipe, so every sweep counted as client activity
and reset the idle clock; with `prune.every` at or below `idle-timeout` the
engine never idle-stopped. The supervisor now talks to the engine API
directly, which the idle clock does not see. `skrog doctor` no longer warns
about the combination, because it is no longer a problem. `skrog prune` run
by hand is unchanged.
- **`skrog start` right after `wsl --shutdown` could time out.** Since 0.9.0
a `wsl --shutdown` is left alone until the next docker command. If `skrog
start` ran before the supervisor's next health check, it wrote nothing the
Expand Down
36 changes: 22 additions & 14 deletions cmd/skrog/idle.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,7 @@ func (rwcConn) SetWriteDeadline(time.Time) error { return nil }
// transport the bridge uses. The idle stop needs a definite "no": any error
// here vetoes it (supervise.Supervisor.Busy's contract).
func busyProbe(dialer pipeproxy.Dialer, busyLog *slog.Logger) func(ctx context.Context) (bool, error) {
client := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
rwc, err := dialer.Dial(ctx)
if err != nil {
return nil, err
}
return rwcConn{rwc}, nil
},
// One probe, one connection: the engine socket is not a place to
// pool idle keep-alives that would themselves look like activity.
DisableKeepAlives: true,
},
}
client := engineClient(dialer)
return func(ctx context.Context) (bool, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
Expand Down Expand Up @@ -158,3 +145,24 @@ func engineBusy(dialer pipeproxy.Dialer, p *provision.Provisioner, opts provisio
return p.SocketBusy(ctx, opts)
}
}

// engineClient is an http.Client that reaches the engine over the bridge's own
// transport rather than through the pipe server, so what it sends is never
// client activity. The busy probe and the scheduled prune (#496) both need
// exactly that.
func engineClient(dialer pipeproxy.Dialer) *http.Client {
return &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
rwc, err := dialer.Dial(ctx)
if err != nil {
return nil, err
}
return rwcConn{rwc}, nil
},
// One request, one connection: the engine socket is not a place to
// pool idle keep-alives that would themselves look like activity.
DisableKeepAlives: true,
},
}
}
5 changes: 3 additions & 2 deletions cmd/skrog/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"

"github.com/wslkit/skrog/internal/dockerctx"
"github.com/wslkit/skrog/internal/pipeproxy"
"github.com/wslkit/skrog/internal/prune"
"github.com/wslkit/skrog/internal/supervise"
)
Expand Down Expand Up @@ -105,9 +106,9 @@ flags:
// All is on: an automatic sweep that only removed dangling layers would leave
// the tagged images that actually fill a disk, and the age guard is what keeps
// that honest rather than the dangling-only filter.
func autoPrune(log *slog.Logger) func(context.Context, supervise.PrunePolicy) (uint64, error) {
func autoPrune(dialer pipeproxy.Dialer, log *slog.Logger) func(context.Context, supervise.PrunePolicy) (uint64, error) {
return func(ctx context.Context, p supervise.PrunePolicy) (uint64, error) {
res := prune.Run(ctx, prune.DockerRunner{}, prune.Options{
res := prune.RunAPI(ctx, engineClient(dialer), prune.Options{
All: true,
Until: p.KeepSince,
BuildCache: p.BuildCache,
Expand Down
2 changes: 1 addition & 1 deletion cmd/skrog/supervise.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ flags:
BuildCache: c.PruneBuildCache,
}
},
Prune: autoPrune(log),
Prune: autoPrune(dialer, log),
}
srv.Dialer = &demandDialer{sup: sup, inner: dialer}
go sup.Run(ctx)
Expand Down
14 changes: 4 additions & 10 deletions docs/housekeeping.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,10 @@ slow pull later.
An idle stop will also wait while a prune is running, rather than stopping the
engine out from under it.

> **A short `prune.every` can suppress idle-stop entirely**
> ([#496](https://github.com/wslkit/skrog/issues/496), open). The prune talks
> to the engine, and that traffic stamps the same "last activity" clock the
> idle timeout reads — so a `prune.every` shorter than your `idle-timeout`
> keeps resetting it and the engine never goes idle. Two features that are
> each working as described, quietly cancelling each other. Until that is
> settled, keep `prune.every` comfortably longer than `idle-timeout` -- and
> `skrog doctor` warns when they are set the other way round, because the
> symptom ("my engine never releases its RAM") is not one anyone would connect
> to a housekeeping setting on their own.
The prune talks to the engine API directly rather than running `docker`, so
it does not count as client activity: a `prune.every` shorter than your
`idle-timeout` no longer keeps the engine awake
([#496](https://github.com/wslkit/skrog/issues/496)). Before 0.10.0 it did.

### Finding out what it did

Expand Down
85 changes: 0 additions & 85 deletions internal/doctor/check_prune_idle.go

This file was deleted.

142 changes: 0 additions & 142 deletions internal/doctor/check_prune_idle_test.go

This file was deleted.

1 change: 0 additions & 1 deletion internal/doctor/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ func Registry() []Check {
checkGPU(),
checkMultiArch(),
checkEmulation(),
checkPruneIdle(),
checkPublishedPorts(),
checkContainerListeners(),
checkDisk(),
Expand Down
10 changes: 0 additions & 10 deletions internal/doctor/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"os/exec"
"path/filepath"
"strings"
"time"

"github.com/wslkit/skrog/internal/config"
"github.com/wslkit/skrog/internal/dockercli"
Expand Down Expand Up @@ -98,13 +97,6 @@ type Facts struct {
// only the second question catches that.
EmulationPlatforms string

// IdleTimeout and PruneEvery are the two settings that can cancel each
// other out (#496): a prune interval at or below the idle timeout resets
// the idle window before it can expire. Zero means off, which is the
// default for both.
IdleTimeout time.Duration
PruneEvery time.Duration

// PublishedPorts is what running containers publish on the host side, and
// WSLNetworkingMode is the `networkingMode` from ~/.wslconfig ("" means the
// default, NAT). PublishScopeLAN is `network.publish-scope`. Together they
Expand Down Expand Up @@ -347,8 +339,6 @@ func Gather(ctx context.Context, opts GatherOptions) Facts {
pOpts.GPUVendor = c.GPUVendor
f.DiskWarnBelow = c.DiskWarnBelow
f.EmulationPlatforms = c.EmulationPlatforms
f.IdleTimeout = c.IdleTimeout
f.PruneEvery = c.PruneEvery
f.PublishScopeLAN = c.PublishScope == config.PublishScopeLAN
}

Expand Down
Loading
Loading