{"record":{"id":"9a1779a44ed384a5","repo":"hashicorp/nomad","slug":"lock-release-w","errorCode":null,"errorMessage":"lock release: %w","messagePattern":"lock release: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"api/locks.go","lineNumber":269,"sourceCode":"\n\treturn ll\n}\n\n// Start wraps the start function in charge of executing the protected\n// function and maintain the lease but is in charge of releasing the\n// lock before exiting. It is a blocking function.\nfunc (ll *LockLeaser) Start(ctx context.Context, protectedFuncs ...func(ctx context.Context) error) error {\n\tvar mErr []error\n\n\terr := ll.start(ctx, protectedFuncs...)\n\tif err != nil {\n\t\tmErr = append(mErr, err)\n\t}\n\n\tif ll.locked {\n\t\terr = ll.locker.Release(ctx)\n\t\tif err != nil {\n\t\t\tmErr = append(mErr, fmt.Errorf(\"lock release: %w\", err))\n\t\t}\n\t}\n\n\treturn errors.Join(mErr...)\n}\n\n// start starts the process of maintaining the lease and executes the protected\n// function on an independent go routine. It is a blocking function, it\n// will return once the protected function is done or an execution error\n// arises.\nfunc (ll *LockLeaser) start(ctx context.Context, protectedFuncs ...func(ctx context.Context) error) error {\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\n\t// errChannel is used track execution errors\n\terrChannel := make(chan error, 1)\n\tdefer close(errChannel)\n","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/api/locks.go#L251-L287","documentation":"This error is produced by LockLeaser.Start when the underlying locker.Release call fails while cleaning up after the protected functions finish. It wraps whatever Release returned — most commonly 'release conflict' (ErrLockConflict) from losing the lock. Start collects it via errors.Join, so the returned error can combine a release failure with other errors.","triggerScenarios":"LockLeaser.Start finishing while the lock was lost mid-run (TTL expired, another instance took over) so Release gets 409 Conflict; Release failing due to network/API errors after the protected function completes; the protected function taking longer than the TTL so the lease lapses before the automatic Release.","commonSituations":"Batch jobs whose runtime exceeds DefaultLockTTL (15s) without renewal succeeding; two replicas contending for the same lock variable; tests like TestFailedRenewal exercising renewal failures then cleanup; a Renew returning ErrLockConflict that propagates through maintainLease and start's error channel before Release runs on cleanup.","solutions":["Inspect the wrapped cause with errors.Is/errors.As: if errors.Is(err, api.ErrLockConflict), the lock was already lost, so the release failure is expected and can be ignored.","Make the protected function resilient: check ctx cancellation / lock health and abort promptly when the lease is lost, before Release is attempted.","Increase the lock TTL or shorten the protected work so the lease does not expire before Start returns.","If the cause is not ErrLockConflict, check network/API connectivity and retry the Start call.","Log and continue: Start still returns the protected function's result via errors.Join; handle each joined error individually."],"exampleFix":"// before\nif err := leaser.Start(ctx, work); err != nil {\n    log.Fatal(err)\n}\n// after\nif err := leaser.Start(ctx, work); err != nil {\n    if errors.Is(err, api.ErrLockConflict) {\n        log.Println(\"lock was lost before release; ignoring cleanup conflict\")\n    } else {\n        log.Fatal(err)\n    }\n}","handlingStrategy":"type-guard","validationCode":"// preflight: validate the lock TTL so the lease can survive the protected work\nttl, err := time.ParseDuration(v.Lock.TTL)\nif err != nil || ttl <= 0 {\n    return fmt.Errorf(\"invalid lock TTL before starting leaser: %q\", v.Lock.TTL)\n}","typeGuard":"func IsLockConflict(err error) bool {\n    return errors.Is(err, api.ErrLockConflict)\n}","tryCatchPattern":"err := leaser.Start(ctx, work)\nif err != nil {\n    if errors.Is(err, api.ErrLockConflict) {\n        // joined release failure is a lost-lock cleanup conflict; log and continue\n        log.Printf(\"lock already lost: %v\", err)\n        return nil\n    }\n    return err\n}","preventionTips":["Remember Start returns errors.Join results — always check with errors.Is/As, not equality","Keep the lease alive during work (Renew/LockLeaser) so Release succeeds on cleanup","Handle ErrLockConflict on Release as expected in multi-instance deployments","Investigate non-conflict causes (network/API errors) before retrying Start","Log the joined errors individually to distinguish protected-function failures from release failures"],"tags":["go","consul","distributed-lock","error-wrapping"],"backgroundTag":"lock-conflict","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}