Skip to content

Add libs/atomicfile and use it for in-place file saves - #6708

Open
denik wants to merge 5 commits into
mainfrom
denik/atomic-save
Open

denik wants to merge 5 commits into
mainfrom
denik/atomic-save

Conversation

@denik

@denik denik commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Adds libs/atomicfile.Write (same-directory temp file + chmod + os.Rename) and routes in-place file saves through it, so a crash or a concurrent reader never sees a partial file. The written file always gets the caller-specified mode and does not inherit the replaced file's permissions. An optional atomicfile.MkDir(perm) creates the parent directory in the same call (the directory mode stays a deliberate per-caller choice: 0o700 for secrets, 0o755 otherwise).

Pure refactor (already atomic, behavior unchanged)

  • bundle/direct/dstate/state.go — deployment resources.json
  • libs/auth/storage/filestore.go — token cache
  • libs/aitools/installer/state.go
  • libs/localenv/constraints.go (inlined the now-thin writeCacheAtomic)
  • libs/clicompat/clicompat.go
  • experimental/ssh/internal/sshconfig/knownhosts.go
  • cmd/genie/conversations.go — best-effort store (also drops a double-close)

Bug fixes

  • libs/cache/file_cache.go — removed the os.Remove-before-Rename hack that deleted the destination first and defeated atomicity. os.Rename already replaces an existing file on Windows.
  • cmd/sandbox/state.go, cmd/sandbox/sshconfig.go — used a fixed <path>.tmp temp name, so concurrent CLI runs collided on it. CreateTemp avoids that.

New atomicity (was a plain os.WriteFile)

Files a user owns (a torn write would lose their content):

  • bundle/configsync/output.go — local source config files rewritten on sync
  • libs/completion/{install,uninstall}.go — the user's shell RC file; mode preserved via the existing info.Mode(). install now rewrites the file instead of O_APPEND, matching uninstall.
  • libs/localenv/pipeline.gopyproject.toml on merge

CLI-owned state/cache read back on the next run:

  • bundle/statemgmt/state_pull.go — state pulled from the remote (closes the // TODO: write + rename)
  • cmd/labs/project/{login,project}.go — labs auth and version files
  • cmd/labs/localcache/jsonfile.go — labs JSON cache
  • bundle/configsync/diff.go — local config snapshot used for the next diff

Deliberately left out

  • libs/databrickscfg/ops.go — writes via go-ini SaveTo in place; needs a render-to-temp approach, its own PR
  • experimental/ssh in-place ~/.ssh/config and VSCode settings editors — experimental, higher-risk read-modify-write
  • libs/aitools/installer file writes — staged in a temp dir already promoted by rename, so the atomicity boundary is the directory

This pull request and its description were written by Isaac.

Introduce libs/atomicfile.Write (same-dir temp + chmod + rename) and route
the hand-rolled atomic writers and a few plain os.WriteFile state saves
through it. The result always gets the caller-specified mode; it does not
inherit the replaced file's permissions.

Pure refactor (already atomic, behavior unchanged):
- bundle/direct/dstate/state.go (deployment resources.json)
- libs/auth/storage/filestore.go (token cache)
- libs/aitools/installer/state.go
- libs/localenv/constraints.go
- libs/clicompat/clicompat.go
- experimental/ssh/internal/sshconfig/knownhosts.go
- cmd/genie/conversations.go (best-effort store; also drops a double-close)

Bug fixes:
- libs/cache/file_cache.go: removed the os.Remove-before-rename hack, which
  deleted the destination first and defeated atomicity (os.Rename already
  replaces an existing file on Windows).
- cmd/sandbox/state.go, cmd/sandbox/sshconfig.go: used a fixed "<path>.tmp"
  temp name, so concurrent CLI runs collided on it; CreateTemp avoids that.

New atomicity (was a plain os.WriteFile):
- bundle/statemgmt/state_pull.go: the state file pulled from the remote is
  now written atomically (closes the "TODO: write + rename").

Co-authored-by: Isaac <no-reply@databricks.com>
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Approval status: pending

/bundle/ - needs approval

4 files changed
Suggested: @pietern
Also eligible: @shreyas-goenka, @anton-107, @janniklasrose, @andrewnester, @lennartkats-db

/cmd/labs/ - needs approval

Files: cmd/labs/localcache/jsonfile.go, cmd/labs/project/login.go, cmd/labs/project/project.go
Eligible: @alexott, @asnare

/cmd/sandbox/ - needs approval

Files: cmd/sandbox/sshconfig.go, cmd/sandbox/state.go
Suggested: @pietern
Also eligible: @akshaysingla-db, @shuochen0311, @anwell-db, @samhuan-db

/libs/aitools/ - needs approval

Files: libs/aitools/installer/state.go
Suggested: @rugpanov
Also eligible: @anton-107, @rclarey, @lennartkats-db, @pkosiec, @fjakobs, @Shridhad, @atilafassina, @keugenek, @igrekun, @MarioCadenas, @pffigueiredo, @ditadi, @calvarjorge, @misha-db, @parthban-db

/libs/auth/ - needs approval

Files: libs/auth/storage/filestore.go
Suggested: @simonfaltum
Also eligible: @renaudhartert-db, @tanmay-db, @hectorcast-db, @parthban-db, @Divyansh-db, @tejaskochar-db, @mihaimitrea-db, @chrisst, @rauchy

/libs/clicompat/ - needs approval

Files: libs/clicompat/clicompat.go
Suggested: @simonfaltum
Also eligible: @renaudhartert-db, @pkosiec, @tanmay-db, @fjakobs, @Shridhad, @atilafassina, @keugenek, @igrekun, @MarioCadenas, @pffigueiredo, @ditadi, @calvarjorge, @hectorcast-db, @parthban-db, @Divyansh-db, @tejaskochar-db, @mihaimitrea-db, @chrisst, @rauchy

/libs/localenv/ - needs approval

Files: libs/localenv/constraints.go, libs/localenv/pipeline.go
Suggested: @rugpanov
Also eligible: @anton-107, @rclarey, @misha-db, @parthban-db

General files (require maintainer)

8 files changed
Based on git history:

  • @rugpanov -- recent work in libs/localenv/, .nextchanges/cli/

Any maintainer (@andrewnester, @anton-107, @pietern, @shreyas-goenka, @simonfaltum, @renaudhartert-db, @janniklasrose, @lennartkats-db, @rugpanov, @rclarey) can approve all areas.
See OWNERS for ownership rules.

Co-authored-by: Isaac <no-reply@databricks.com>
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 0adf30c

Run: 35143463321

Env 🔄​flaky ✅​pass 🙈​skip Time
✅​ aws linux 276 15 4:19
✅​ aws windows 278 13 3:47
✅​ azure linux 275 15 4:07
✅​ azure windows 277 13 3:50
✅​ gcp linux 276 15 4:47
🔄​ gcp windows 6 272 13 6:04
6 interesting tests: 6 flaky
Test Name gcp windows
🔄​ TestFsCpDir 🔄​f
🔄​ TestFsCpDir/uc-volumes_to_uc-volumes 🔄​f
🔄​ TestFsCpFileToFile/uc-volumes_to_dbfs 🔄​f
🔄​ TestFsCpFileToFile/uc-volumes_to_local 🔄​f
🔄​ TestFsCpFileToFile/uc-volumes_to_uc-volumes 🔄​f
🔄​ TestSyncEnsureRemotePathIsUsableIfRepoExists 🔄​f
Top 3 slowest tests (at least 2 minutes):
duration env testname
3:48 azure windows TestAccept
3:45 aws windows TestAccept
3:21 gcp windows TestAccept

denik and others added 3 commits September 16, 2026 14:51
Add a functional-options MkDir(perm) to atomicfile.Write so a caller can
create the parent directory (with its own deliberate mode: 0o700 for secrets,
0o755 otherwise) in the same call. Route the eight sites that did an explicit
MkdirAll right before the write through it, and inline localenv.writeCacheAtomic,
which became a single-caller pass-through.

Co-authored-by: Isaac <no-reply@databricks.com>
Route the remaining in-place os.WriteFile saves that overwrite a persistent
file (torn write would lose the prior content) through atomicfile.Write:

New atomicity for files a user owns:
- bundle/configsync/output.go: rewriting local source config files on sync
- libs/completion/{install,uninstall}.go: editing the user's shell RC file
  (mode preserved via the existing info.Mode()); install now rewrites instead
  of appending, matching uninstall
- libs/localenv/pipeline.go: overwriting pyproject.toml on merge

New atomicity for CLI-owned state/cache read back later:
- cmd/labs/project/{login,project}.go: labs auth and version files
- cmd/labs/localcache/jsonfile.go: labs JSON cache (write+mkdir+retry collapsed
  into one MkDir call)
- bundle/configsync/diff.go: local config snapshot used for the next diff

Left as-is: the aitools installer writes to a temp staging dir already promoted
by rename, .databrickscfg (go-ini SaveTo, needs its own approach), and the
experimental/ssh in-place config editors.

Co-authored-by: Isaac <no-reply@databricks.com>
Remove atomicWriteFile and installFish, which became single-expression
pass-throughs to atomicfile.Write, inlining them into their callers.

Co-authored-by: Isaac <no-reply@databricks.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants