Skip to content

Commit 267b919

Browse files
authored
more explicit cleanup logic and tests for cleaning up the temp dir when loading c1zs (#617)
1 parent 5b4346f commit 267b919

File tree

4 files changed

+70
-40
lines changed

4 files changed

+70
-40
lines changed

pkg/dotc1z/c1file.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func NewC1ZFile(ctx context.Context, outputFilePath string, opts ...C1ZOption) (
188188
opt(options)
189189
}
190190

191-
dbFilePath, err := loadC1z(outputFilePath, options.tmpDir, options.decoderOptions...)
191+
dbFilePath, _, err := decompressC1z(outputFilePath, options.tmpDir, options.decoderOptions...)
192192
if err != nil {
193193
return nil, err
194194
}

pkg/dotc1z/dotc1z.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func C1ZFileCheckHeader(f io.ReadSeeker) (bool, error) {
5050
}
5151

5252
func NewExternalC1FileReader(ctx context.Context, tmpDir string, externalResourceC1ZPath string) (connectorstore.Reader, error) {
53-
dbFilePath, err := loadC1z(externalResourceC1ZPath, tmpDir)
53+
dbFilePath, _, err := decompressC1z(externalResourceC1ZPath, tmpDir)
5454
if err != nil {
5555
return nil, fmt.Errorf("error loading external resource c1z file: %w", err)
5656
}

pkg/dotc1z/file.go

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,56 +15,83 @@ import (
1515
"google.golang.org/grpc/status"
1616
)
1717

18-
func loadC1z(filePath string, tmpDir string, opts ...DecoderOption) (string, error) {
19-
var err error
20-
workingDir, err := os.MkdirTemp(tmpDir, "c1z")
18+
// Note(kans): decompressC1z is unfortunately called to load or create a c1z file so the error handling is rough.
19+
// It creates its own temporary directory so that it can also do its own cleanup.
20+
// It returns that directory for verification in tests.
21+
func decompressC1z(c1zPath string, workingDir string, opts ...DecoderOption) (string, string, error) {
22+
tmpDir, err := os.MkdirTemp(workingDir, "c1z")
2123
if err != nil {
22-
return "", err
24+
return "", tmpDir, err
2325
}
24-
defer func() {
25-
if err != nil {
26-
if removeErr := os.RemoveAll(workingDir); removeErr != nil {
27-
err = errors.Join(err, removeErr)
26+
27+
var dbFile *os.File
28+
var c1zFile *os.File
29+
var decoder *decoder
30+
cleanupDir := func(e error) error {
31+
if decoder != nil {
32+
err := decoder.Close()
33+
if err != nil {
34+
e = errors.Join(e, err)
2835
}
2936
}
30-
}()
31-
dbFilePath := filepath.Join(workingDir, "db")
32-
dbFile, err := os.Create(dbFilePath)
37+
if c1zFile != nil {
38+
err := c1zFile.Close()
39+
if err != nil {
40+
e = errors.Join(e, err)
41+
}
42+
}
43+
if dbFile != nil {
44+
err := dbFile.Close()
45+
if err != nil {
46+
e = errors.Join(e, err)
47+
}
48+
}
49+
if e != nil {
50+
err := os.RemoveAll(tmpDir)
51+
if err != nil {
52+
e = errors.Join(e, err)
53+
}
54+
}
55+
return e
56+
}
57+
58+
dbFilePath := filepath.Join(tmpDir, "db")
59+
dbFile, err = os.Create(dbFilePath)
3360
if err != nil {
34-
return "", err
61+
return "", tmpDir, cleanupDir(err)
3562
}
36-
defer dbFile.Close()
3763

38-
if stat, err := os.Stat(filePath); err == nil && stat.Size() != 0 {
39-
c1zFile, err := os.Open(filePath)
40-
if err != nil {
41-
return "", err
42-
}
43-
defer c1zFile.Close()
64+
stat, err := os.Stat(c1zPath)
65+
if err != nil || stat.Size() == 0 {
66+
// TODO(kans): it would be nice to know more about the error....
67+
return dbFilePath, tmpDir, cleanupDir(nil)
68+
}
4469

45-
r, err := NewDecoder(c1zFile, opts...)
46-
if err != nil {
47-
return "", err
48-
}
49-
_, err = io.Copy(dbFile, r)
50-
if err != nil {
51-
return "", err
52-
}
53-
err = r.Close()
54-
if err != nil {
55-
return "", err
56-
}
70+
c1zFile, err = os.Open(c1zPath)
71+
if err != nil {
72+
return "", tmpDir, cleanupDir(err)
73+
}
74+
75+
decoder, err = NewDecoder(c1zFile, opts...)
76+
if err != nil {
77+
return "", tmpDir, cleanupDir(err)
78+
}
79+
80+
_, err = io.Copy(dbFile, decoder)
81+
if err != nil {
82+
return "", tmpDir, cleanupDir(err)
5783
}
5884

5985
// CRITICAL: Sync the database file before returning to ensure all
6086
// decompressed data is on disk. On filesystems with aggressive caching
6187
// (like ZFS with large ARC), SQLite might otherwise open the file and
6288
// see incomplete data still in kernel buffers.
63-
if err = dbFile.Sync(); err != nil {
64-
return "", fmt.Errorf("failed to sync db file: %w", err)
89+
err = dbFile.Sync()
90+
if err != nil {
91+
return "", tmpDir, cleanupDir(err)
6592
}
6693

67-
return dbFilePath, nil
94+
return dbFilePath, tmpDir, cleanupDir(nil)
6895
}
6996

7097
func saveC1z(dbFilePath string, outputFilePath string, encoderConcurrency int) error {

pkg/dotc1z/file_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,38 @@ import (
1212
)
1313

1414
func TestLoadC1z(t *testing.T) {
15-
tmpDir := t.TempDir()
16-
1715
t.Run("temp directory cleanup on error", func(t *testing.T) {
1816
// Create a file that will cause an error during decoding
17+
tmpDir := t.TempDir()
1918
invalidFile := filepath.Join(tmpDir, "invalid2.c1z")
2019
err := os.WriteFile(invalidFile, []byte("invalid"), 0600)
2120
require.NoError(t, err)
2221
defer os.Remove(invalidFile)
2322

2423
// Try to load it - should fail and clean up temp dir
25-
dbPath, err := loadC1z(invalidFile, tmpDir)
24+
dbPath, workingDir, err := decompressC1z(invalidFile, tmpDir)
2625
require.Error(t, err)
2726
require.Empty(t, dbPath)
27+
// require that the working dir was cleaned up and has no entries
28+
require.NoDirExists(t, workingDir)
2829
})
2930

3031
t.Run("custom tmpDir", func(t *testing.T) {
32+
tmpDir := t.TempDir()
3133
customTmpDir := filepath.Join(tmpDir, "custom")
3234
err := os.MkdirAll(customTmpDir, 0755)
3335
require.NoError(t, err)
3436
defer os.RemoveAll(customTmpDir)
3537

3638
nonExistentPath := filepath.Join(tmpDir, "nonexistent2.c1z")
37-
dbPath, err := loadC1z(nonExistentPath, customTmpDir)
39+
dbPath, workingDir, err := decompressC1z(nonExistentPath, customTmpDir)
3840
require.NoError(t, err)
3941
require.NotEmpty(t, dbPath)
4042
require.FileExists(t, dbPath)
4143

4244
// Verify it was created in the custom tmpDir
4345
require.Contains(t, dbPath, customTmpDir)
46+
require.Contains(t, workingDir, customTmpDir)
4447
})
4548
}
4649

0 commit comments

Comments
 (0)