|
17 | 17 | package store |
18 | 18 |
|
19 | 19 | import ( |
| 20 | + "os" |
| 21 | + "path/filepath" |
20 | 22 | "testing" |
21 | 23 | "time" |
22 | 24 |
|
@@ -220,3 +222,31 @@ func TestFileStoreConcurrent(t *testing.T) { |
220 | 222 | }) |
221 | 223 | assert.NilError(t, lErr, "locking should not error") |
222 | 224 | } |
| 225 | + |
| 226 | +func TestFileStoreLockFailureReleasesMutex(t *testing.T) { |
| 227 | + parentDir := t.TempDir() |
| 228 | + storeDir := filepath.Join(parentDir, "store") |
| 229 | + |
| 230 | + tempStore, err := New(storeDir, 0, 0) |
| 231 | + assert.NilError(t, err, "temporary store creation should succeed") |
| 232 | + |
| 233 | + // Locking a store whose directory and parent are gone must fail. |
| 234 | + assert.NilError(t, os.RemoveAll(parentDir)) |
| 235 | + err = tempStore.Lock() |
| 236 | + assert.ErrorIs(t, err, ErrLockFailure) |
| 237 | + |
| 238 | + // A failed Lock must not leave the store locked: once the directory is back, locking works again. |
| 239 | + assert.NilError(t, os.MkdirAll(storeDir, defaultDirPerm)) |
| 240 | + |
| 241 | + done := make(chan error, 1) |
| 242 | + go func() { |
| 243 | + done <- tempStore.WithLock(func() error { return nil }) |
| 244 | + }() |
| 245 | + |
| 246 | + select { |
| 247 | + case err = <-done: |
| 248 | + assert.NilError(t, err, "locking after a failed lock should succeed") |
| 249 | + case <-time.After(5 * time.Second): |
| 250 | + t.Fatal("Lock blocked forever after a previous Lock failure") |
| 251 | + } |
| 252 | +} |
0 commit comments