ai cooking
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix file cache writes to avoid partial read race (#241)

authored by

Paul Miller and committed by
GitHub
78228ebd 8f3666f3

+53 -16
+53 -16
internal/cache/file.go
··· 102 102 } 103 103 104 104 if opts.Condition == PutIfNoneMatch { 105 - f, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) 106 - if err != nil { 107 - if os.IsExist(err) { 108 - return ErrAlreadyExists 109 - } 110 - return err 105 + return writeIfNoneMatchAtomic(dir, fullPath, value) 106 + } 107 + 108 + // TODO: IfMatch support (write only if etag matches). 109 + return writeAtomic(dir, fullPath, value) 110 + } 111 + 112 + func writeAtomic(dir, targetPath, value string) error { 113 + tmpFile, err := os.CreateTemp(dir, ".tmp-*") 114 + if err != nil { 115 + return err 116 + } 117 + tmpPath := tmpFile.Name() 118 + defer func() { 119 + _ = os.Remove(tmpPath) 120 + }() 121 + 122 + if _, err := tmpFile.WriteString(value); err != nil { 123 + if closeErr := tmpFile.Close(); closeErr != nil { 124 + return errors.Join(err, closeErr) 111 125 } 112 - if _, err := f.WriteString(value); err != nil { 113 - if closeErr := f.Close(); closeErr != nil { 114 - return errors.Join(err, closeErr) 115 - } 116 - return err 126 + return err 127 + } 128 + if err := tmpFile.Close(); err != nil { 129 + return err 130 + } 131 + 132 + return os.Rename(tmpPath, targetPath) 133 + } 134 + 135 + func writeIfNoneMatchAtomic(dir, targetPath, value string) error { 136 + tmpFile, err := os.CreateTemp(dir, ".tmp-*") 137 + if err != nil { 138 + return err 139 + } 140 + tmpPath := tmpFile.Name() 141 + defer func() { 142 + _ = os.Remove(tmpPath) 143 + }() 144 + 145 + if _, err := tmpFile.WriteString(value); err != nil { 146 + if closeErr := tmpFile.Close(); closeErr != nil { 147 + return errors.Join(err, closeErr) 117 148 } 118 - if err := f.Close(); err != nil { 119 - return err 149 + return err 150 + } 151 + if err := tmpFile.Close(); err != nil { 152 + return err 153 + } 154 + 155 + if err := os.Link(tmpPath, targetPath); err != nil { 156 + if os.IsExist(err) { 157 + return ErrAlreadyExists 120 158 } 121 - return nil 159 + return err 122 160 } 123 161 124 - // TODO: IfMatch support (write only if etag matches). 125 - return os.WriteFile(fullPath, []byte(value), 0644) 162 + return nil 126 163 } 127 164 128 165 func (fc *FileCache) Delete(_ context.Context, key string) error {