Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
62c72a5
chore: start v0.42.0 dev cycle
lidel Apr 12, 2026
8cebf5c
fix(fuse): accurate `st_blocks` and `st_blksize` (#11280)
lidel Apr 13, 2026
f449e8b
fix(examples): avoid bitswap race, use ed25519 (#11282)
lidel Apr 14, 2026
eb2cfac
fix: queryevent addrinfo race in kad-dht (#11288)
lidel Apr 20, 2026
e804f08
chore: bump p2p-forge to v0.8.0 (#11285)
lidel Apr 20, 2026
61018d7
fix(defaultServerFilters): strip loopback and non-public (#11286)
lidel Apr 20, 2026
6059743
fix(log): scope provide logs to "provider" subsystem (#11289)
lidel Apr 22, 2026
d62ee27
Upgrade to Boxo v0.39.0 (#11294)
gammazero Apr 23, 2026
8416f38
fix(pins): snapshot index before emitting pins (#11290)
lidel Apr 23, 2026
c4094b5
Merge remote-tracking branch 'origin/master' into merge-release-v0.41.0
lidel Apr 24, 2026
8387e66
Merge pull request #11295 from ipfs/merge-release-v0.41.0
lidel Apr 24, 2026
508f112
fix: resolve wildcard swarm in http provides (#11297)
lidel Apr 27, 2026
90c2b50
fix(test): mock GitHub API in TestUpdate (#11300)
lidel Apr 27, 2026
7cbb023
chore(deps): bump actions/github-script from 8 to 9 (#11284)
dependabot[bot] Apr 28, 2026
afab765
docs: clarify blockstore cache sizing and flatfs sharding (#11303)
lidel Apr 29, 2026
e931b37
fix(libp2p): drop empty addrs in AddrsFactory (#11302)
lidel Apr 29, 2026
afd7342
feat(config): dead listener check (#11299)
lidel May 4, 2026
43f2ed4
chore(deps): align deps with ipfs/boxo#1152 (#11313)
lidel May 4, 2026
aef5750
docs: clarify denylist scope vs routing layer (#11320)
lidel May 6, 2026
a939918
chore(deps): bump go-libp2p-kad-dht to v0.39.2 (#11323)
lidel May 7, 2026
a8f27e7
upgrade to go-ds-pebble v0.5.11 (#11327)
gammazero May 12, 2026
6f188e1
update go-log to v2.9.2 (#11325)
gammazero May 12, 2026
acba6b1
update go-ipfs-cmds to v0.16.1 (#11328)
gammazero May 12, 2026
d01246f
feat(pinner): close pinner before repo on shutdown (#11296)
lidel May 14, 2026
614b757
feat: bound graceful shutdown, add diag healthy (#11329)
lidel May 14, 2026
3ae88b2
feat(provide): add `ipfs provide once` and support Interval=0 mode (#…
lidel May 14, 2026
f485a7e
chore: update boxo to remove io.Seeker from files.File (#11254)
lidel May 15, 2026
0507e8a
fix: migration fetcher robustness (#11305)
lidel May 16, 2026
c81317e
docs: firewall (ufw) walkthrough for port 4001 (#11332)
lidel May 19, 2026
fc865be
refactor: migrate away from cheggaaa/pb v1 (#11322)
Vinayak9769 May 25, 2026
179639b
chore: bump go-libp2p-kad-dht to v0.40.0 (#11334)
guillaumemichel May 25, 2026
0acc688
feat(dag): add --local-only to dag export and import (#11229)
ChayanDass May 25, 2026
0c199f8
chore(deps): bump codecov/codecov-action from 6.0.0 to 6.0.1 (#11335)
dependabot[bot] May 26, 2026
a8fd0d1
chore: upgrade to Boxo v0.40.0 (#11338)
gammazero May 26, 2026
a716ebf
chore: set version to v0.42.0-rc1
lidel May 27, 2026
efc111c
chore(deps): bump p2p-forge to 0.9.0 (#11336)
dependabot[bot] May 28, 2026
232aeae
feat: derive AgentSuffix from build origin (#11341)
lidel Jun 2, 2026
7f0fb38
fix(libp2p): quieter dead-listener check (#11342)
lidel Jun 5, 2026
66a1ca2
chore: go 1.26.4 (#11350)
lidel Jun 5, 2026
b2f8d08
docs: note AutoTLS local testing needs UPnP
lidel Jun 5, 2026
25fb13b
docs: move p2p-forge bump to v0.42 changelog
lidel Jun 5, 2026
a2cd490
chore(mkreleaselog): exclude bot accounts
lidel Jun 8, 2026
8b46618
chore: release v0.42.0
lidel Jun 8, 2026
e339e98
chore: fix typo in v0.42 changelog
lidel Jun 8, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/gotest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
make test_unit &&
[[ ! $(jq -s -c 'map(select(.Action == "fail")) | .[]' test/unit/gotest.json) ]]
- name: Upload coverage to Codecov
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
if: failure() || success()
with:
name: unittests
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sharness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
# increasing parallelism beyond 10 doesn't speed up the tests much
PARALLEL: ${{ github.repository == 'ipfs/kubo' && 10 || 3 }}
- name: Upload coverage report
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
if: failure() || success()
with:
name: sharness
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sync-release-assets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
with:
node-version: 14
- name: Sync the latest 5 github releases
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const fs = require('fs').promises
Expand Down
19 changes: 19 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ make install # install to $GOPATH/bin
make -O test_go_lint # run linter (use this instead of golangci-lint directly)
```

**Always build with `make build`, never `go build`.** The Makefile injects required `-ldflags` for `CurrentCommit`, `taggedRelease`, and `buildOrigin`.

If you modify `go.mod` (add/remove/update dependencies), you must run `make mod_tidy` first, before building or testing. Use `make mod_tidy` instead of `go mod tidy` directly, as the project has multiple `go.mod` files.

If you modify any `.go` files outside of `test/`, you must run `make build` before running integration tests.
Expand Down Expand Up @@ -230,3 +232,20 @@ ipfs shutdown # graceful shutdown via API
```

Kill dangling daemons before re-running tests: `pkill -f "ipfs daemon"`

### Testing AutoTLS Locally

AutoTLS only requests a `*.libp2p.direct` certificate once libp2p confirms the node is publicly reachable on a TCP port. For a local test the node must be able to open that port, so enable UPnP/NAT-PMP (the `server` init profile disables it via `Swarm.DisableNatPortMap: true`):

```bash
ipfs config --json Swarm.DisableNatPortMap false # let UPnP/NAT-PMP map the swarm port
ipfs config AutoTLS.RegistrationDelay 5s # shorten the default wait before registration
```

Then start the daemon and watch the relevant logs:

```bash
GOLOG_LOG_LEVEL="error,autotls=info,nat=info" ipfs daemon
```

Poll `ipfs id` until a `tls/ws` address under your own peer ID appears. A `libp2p.direct` address ending in `/p2p-circuit/p2p/<your-id>` is a relay path, not your own AutoTLS cert. Requires a router that actually honors UPnP/NAT-PMP; without it AutoNAT reports `Private` and no certificate is issued.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Kubo Changelogs

- [v0.43](docs/changelogs/v0.43.md)
- [v0.42](docs/changelogs/v0.42.md)
- [v0.41](docs/changelogs/v0.41.md)
- [v0.40](docs/changelogs/v0.40.md)
Expand Down
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# The default below is what `docker build .` (without --build-arg) uses, and
# `.github/workflows/docker-check.yml` lints that this default stays in sync
# with go.mod. When bumping Go, update both go.mod and this default together.
ARG GO_VERSION=1.26.2
ARG GO_VERSION=1.26.4
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:${GO_VERSION} AS builder

ARG TARGETOS TARGETARCH
Expand Down Expand Up @@ -97,10 +97,10 @@ ENV GOLOG_LOG_LEVEL=""
# tini ensures proper signal handling and zombie process cleanup
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/start_ipfs"]

# Health check verifies IPFS daemon is responsive.
# Uses empty directory CID (QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn) as test
# Health check via "ipfs diag healthy": verifies RPC + DAG pipeline, and
# fails after SIGINT/SIGTERM to catch half-shutdown states.
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ipfs --api=/ip4/127.0.0.1/tcp/5001 dag stat /ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn || exit 1
CMD ipfs --api=/ip4/127.0.0.1/tcp/5001 diag healthy > /dev/null 2>&1 || exit 1

# Default: run IPFS daemon with auto-migration enabled
CMD ["daemon", "--migrate=true", "--agent-version-suffix=docker"]
5 changes: 4 additions & 1 deletion bin/mkreleaselog
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,10 @@ statsummary() {
Deletions: (map(.Deletions) | add),
Files: (map(.Files) | add)
}
' | jq '. + {Lines: (.Deletions + .Insertions)}'
' |
# Drop bot accounts (e.g. dependabot[bot]); GitHub bots use the [bot] suffix
jq 'select((.GitHub | endswith("[bot]")) or (.Author | endswith("[bot]")) | not)' |
jq '. + {Lines: (.Deletions + .Insertions)}'
}

strip_version() {
Expand Down
2 changes: 1 addition & 1 deletion cmd/ipfs/Rules.mk
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ PATH := $(realpath $(d)):$(PATH)
# DEPS_OO_$(d) += merkledag/pb/merkledag.pb.go namesys/pb/namesys.pb.go
# DEPS_OO_$(d) += pin/internal/pb/header.pb.go unixfs/pb/unixfs.pb.go

$(d)_flags =-ldflags="-X "github.com/ipfs/kubo".CurrentCommit=$(git-hash) -X "github.com/ipfs/kubo".taggedRelease=$(git-tag)"
$(d)_flags =-ldflags="-X "github.com/ipfs/kubo".CurrentCommit=$(git-hash) -X "github.com/ipfs/kubo".taggedRelease=$(git-tag) -X "github.com/ipfs/kubo".buildOrigin=$(git-origin)"

$(IPFS_BIN_$(d)): GOFLAGS += $(cmd/ipfs_flags)

Expand Down
2 changes: 1 addition & 1 deletion cmd/ipfs/kubo/add_migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func addMigrations(ctx context.Context, node *core.IpfsNode, fetcher migrations.
if err != nil {
return err
}
case *migrations.HttpFetcher, *migrations.RetryFetcher: // https://github.com/ipfs/kubo/issues/8780
case *migrations.HttpFetcher: // https://github.com/ipfs/kubo/issues/8780
// Add the downloaded migration files directly
if migrations.DownloadDirectory != "" {
var paths []string
Expand Down
26 changes: 25 additions & 1 deletion cmd/ipfs/kubo/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
options "github.com/ipfs/kubo/core/coreiface/options"
corerepo "github.com/ipfs/kubo/core/corerepo"
libp2p "github.com/ipfs/kubo/core/node/libp2p"
"github.com/ipfs/kubo/core/shutdown"
nodeMount "github.com/ipfs/kubo/fuse/node"
fsrepo "github.com/ipfs/kubo/repo/fsrepo"
"github.com/ipfs/kubo/repo/fsrepo/migrations"
Expand Down Expand Up @@ -432,6 +433,12 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
ipnsps = cfg.Ipns.UsePubsub.WithDefault(false)
}

// Resolve graceful-shutdown timeout. The generous 12h default leaves
// normal operation unchanged while guaranteeing the daemon cannot be
// stuck indefinitely on a hung FX OnStop hook. A value of 0 opts out
// entirely and restores the legacy "wait forever" behavior.
shutdownTimeout := max(cfg.Internal.ShutdownTimeout.WithDefault(config.DefaultShutdownTimeout), 0)

// Start assembling node config
ncfg := &core.BuildCfg{
Repo: repo,
Expand All @@ -442,6 +449,7 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
"pubsub": pubsub,
"ipnsps": ipnsps,
},
ShutdownTimeout: shutdownTimeout,
// TODO(Kubuxu): refactor Online vs Offline by adding Permanent vs Ephemeral
}

Expand Down Expand Up @@ -512,9 +520,13 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
return fmt.Errorf("unrecognized routing option: %s", routingOption)
}

// Set optional agent version suffix
// Resolve agent version suffix:
// Version.AgentSuffix > --agent-version-suffix > implicit (build origin).
versionSuffixFromCli, _ := req.Options[agentVersionSuffix].(string)
versionSuffix := cfg.Version.AgentSuffix.WithDefault(versionSuffixFromCli)
if versionSuffix == "" {
versionSuffix = version.ImplicitAgentSuffix()
}
if versionSuffix != "" {
version.SetUserAgentSuffix(versionSuffix)
}
Expand Down Expand Up @@ -583,6 +595,17 @@ take effect.
}

defer func() {
// Watchdog: if node.Close() does not return within shutdownTimeout,
// force-exit so orchestrators can restart the daemon.
// shutdownTimeout==0 disables the watchdog (wait forever).
if shutdownTimeout > 0 {
killSwitch := time.AfterFunc(shutdownTimeout, func() {
log.Errorf("shutdown watchdog: node.Close() did not return after %s; exiting", shutdownTimeout)
os.Exit(1)
})
defer killSwitch.Stop()
}

// We wait for the node to close first, as the node has children
// that it will wait for before closing, such as the API server.
node.Close()
Expand Down Expand Up @@ -708,6 +731,7 @@ take effect.
// Give the user some immediate feedback when they hit C-c
go func() {
<-req.Context.Done()
shutdown.MarkStarted()
notifyStopping()
fmt.Println("Received interrupt signal, shutting down...")
fmt.Println("(Hit ctrl-c again to force-shutdown the daemon.)")
Expand Down
16 changes: 16 additions & 0 deletions config/internal.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
package config

import "time"

const (
// DefaultMFSNoFlushLimit is the default limit for consecutive unflushed MFS operations
DefaultMFSNoFlushLimit = 256

// DefaultShutdownTimeout caps how long graceful shutdown is allowed to
// take before the daemon force-exits with status 1. Set generously so
// it does not change existing kubo behavior in practice but guarantees
// Docker / kubernetes infrastructure can never be stuck indefinitely
// on a hung FX OnStop hook. Smaller than the 22h DHT reprovide cycle,
// so a hung daemon recovers before missing more than one cycle.
// Set Internal.ShutdownTimeout to 0 to opt out and wait forever.
DefaultShutdownTimeout = 12 * time.Hour
)

type Internal struct {
Expand All @@ -18,6 +29,11 @@ type Internal struct {
// This is an EXPERIMENTAL feature and may change or be removed in future releases.
// See https://github.com/ipfs/kubo/issues/10842
MFSNoFlushLimit *OptionalInteger `json:",omitempty"`
// ShutdownTimeout caps how long graceful shutdown of the daemon is
// allowed to take. Defaults to DefaultShutdownTimeout. When the
// deadline expires the daemon logs which subsystem failed to close and
// exits with status 1. Set to 0 to disable the cap and wait forever.
ShutdownTimeout *OptionalDuration `json:",omitempty"`
}

type InternalBitswap struct {
Expand Down
47 changes: 38 additions & 9 deletions config/provide.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@ const (
MinProvideBloomFPRate = 1_000_000

// DHT provider defaults
DefaultProvideDHTInterval = 22 * time.Hour // https://github.com/ipfs/kubo/pull/9326
DefaultProvideDHTMaxWorkers = 16 // Unified default for both sweep and legacy providers
DefaultProvideDHTSweepEnabled = true
DefaultProvideDHTResumeEnabled = true
DefaultProvideDHTDedicatedPeriodicWorkers = 2
DefaultProvideDHTDedicatedBurstWorkers = 1
DefaultProvideDHTMaxProvideConnsPerWorker = 20
DefaultProvideDHTKeystoreBatchSize = 1 << 14 // ~544 KiB per batch (1 multihash = 34 bytes)
DefaultProvideDHTOfflineDelay = 2 * time.Hour
DefaultProvideDHTInterval = 22 * time.Hour // https://github.com/ipfs/kubo/pull/9326
DefaultProvideDHTMaxWorkers = 16 // Unified default for both sweep and legacy providers
DefaultProvideDHTSweepEnabled = true
DefaultProvideDHTResumeEnabled = true
DefaultProvideDHTDedicatedPeriodicWorkers = 2
DefaultProvideDHTDedicatedBurstWorkers = 1
DefaultProvideDHTMaxProvideConnsPerWorker = 20
DefaultProvideDHTKeystoreBatchSize = 1 << 14 // ~544 KiB per batch (1 multihash = 34 bytes)
DefaultProvideDHTOfflineDelay = 2 * time.Hour
DefaultProvideDHTSendProviderRecordTimeout = 10 * time.Second

// DefaultFastProvideTimeout is the maximum time allowed for fast-provide operations.
// Prevents hanging on network issues when providing root CID.
Expand Down Expand Up @@ -121,6 +122,13 @@ type ProvideDHT struct {
// Default: DefaultProvideDHTOfflineDelay
OfflineDelay *OptionalDuration `json:",omitempty"`

// SendProviderRecordTimeout sets the per-peer timeout applied to a single
// ADD_PROVIDER RPC. A peer that accepts the libp2p stream but never reads
// the request must not pin a provide worker goroutine indefinitely; this
// timeout bounds the wait (sweep mode only).
// Default: DefaultProvideDHTSendProviderRecordTimeout
SendProviderRecordTimeout *OptionalDuration `json:",omitempty"`

// ResumeEnabled controls whether the provider resumes from its previous state on restart.
// When enabled, the provider persists its reprovide cycle state and provide queue to the datastore,
// and restores them on restart. When disabled, the provider starts fresh on each restart.
Expand Down Expand Up @@ -209,6 +217,19 @@ func ValidateProvideConfig(cfg *Provide) error {
if interval < 0 {
return fmt.Errorf("Provide.DHT.Interval must be non-negative, got %v", interval)
}
// Provide.DHT.Interval=0 used to disable the entire provide system as a
// side effect. It now disables only the periodic reprovide schedule:
// new CIDs still announce via fast-provide-root and 'ipfs provide once'.
// Operators upgrading from earlier kubo versions must opt in to one of
// the two semantics by setting Provide.Enabled explicitly:
// - Provide.Enabled=false fully disables providing (the old behaviour).
// - Provide.Enabled=true keeps ad-hoc providing while disabling the
// periodic reprovide schedule.
if interval == 0 && cfg.Enabled == Default {
return fmt.Errorf("Provide.DHT.Interval=0 no longer disables the provide system on its own; set Provide.Enabled explicitly: " +
"Provide.Enabled=false to fully disable providing, or Provide.Enabled=true to keep ad-hoc 'ipfs provide once' " +
"and fast-provide-root working while skipping the periodic reprovide schedule")
}
}

// Validate MaxWorkers
Expand Down Expand Up @@ -259,6 +280,14 @@ func ValidateProvideConfig(cfg *Provide) error {
}
}

// Validate SendProviderRecordTimeout
if !cfg.DHT.SendProviderRecordTimeout.IsDefault() {
timeout := cfg.DHT.SendProviderRecordTimeout.WithDefault(DefaultProvideDHTSendProviderRecordTimeout)
if timeout <= 0 {
return fmt.Errorf("Provide.DHT.SendProviderRecordTimeout must be positive, got %v", timeout)
}
}

return nil
}

Expand Down
18 changes: 11 additions & 7 deletions config/provide_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,21 +155,25 @@ func TestValidateProvideConfig_Interval(t *testing.T) {
tests := []struct {
name string
interval time.Duration
enabled Flag
wantErr bool
errMsg string
}{
{"valid default (22h)", 22 * time.Hour, false, ""},
{"valid max (48h)", 48 * time.Hour, false, ""},
{"valid small (1h)", 1 * time.Hour, false, ""},
{"valid zero (disabled)", 0, false, ""},
{"invalid over limit (49h)", 49 * time.Hour, true, "must be less than or equal to DHT provider record validity"},
{"invalid over limit (72h)", 72 * time.Hour, true, "must be less than or equal to DHT provider record validity"},
{"invalid negative", -1 * time.Hour, true, "must be non-negative"},
{"valid default (22h)", 22 * time.Hour, Default, false, ""},
{"valid max (48h)", 48 * time.Hour, Default, false, ""},
{"valid small (1h)", 1 * time.Hour, Default, false, ""},
{"valid zero with explicit Enabled=true", 0, True, false, ""},
{"valid zero with explicit Enabled=false", 0, False, false, ""},
{"invalid zero without explicit Provide.Enabled", 0, Default, true, "set Provide.Enabled explicitly"},
{"invalid over limit (49h)", 49 * time.Hour, Default, true, "must be less than or equal to DHT provider record validity"},
{"invalid over limit (72h)", 72 * time.Hour, Default, true, "must be less than or equal to DHT provider record validity"},
{"invalid negative", -1 * time.Hour, Default, true, "must be non-negative"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &Provide{
Enabled: tt.enabled,
DHT: ProvideDHT{
Interval: NewOptionalDuration(tt.interval),
},
Expand Down
16 changes: 14 additions & 2 deletions core/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,21 @@ func NewNode(ctx context.Context, cfg *BuildCfg) (*IpfsNode, error) {
var stopErr error
n.stop = func() error {
once.Do(func() {
stopErr = app.Stop(context.Background())
// Bound app.Stop with a deadline so an FX OnStop hook that
// never returns cannot hang the daemon. ShutdownTimeout==0
// opts out of the cap entirely and restores the legacy
// behavior of waiting forever for hooks to complete. The
// daemon's watchdog in cmd/ipfs/kubo/daemon.go fires at the
// same deadline and is the unconditional os.Exit fallback.
stopCtx := context.Background()
if cfg.ShutdownTimeout > 0 {
var stopCancel context.CancelFunc
stopCtx, stopCancel = context.WithTimeout(stopCtx, cfg.ShutdownTimeout)
defer stopCancel()
}
stopErr = app.Stop(stopCtx)
if stopErr != nil {
log.Error("failure on stop: ", stopErr)
log.Errorf("failure on stop: %v", stopErr)
}
// Cancel the context _after_ the app has stopped.
cancel()
Expand Down
Loading
Loading