| 1364 | } |
| 1365 | |
| 1366 | func (f *VFSFile) Close() error { |
| 1367 | f.logger.Debug("closing file") |
| 1368 | |
| 1369 | // Stop sync loop and ticker if running (need mutex for syncStop) |
| 1370 | f.mu.Lock() |
| 1371 | if f.syncStop != nil { |
| 1372 | close(f.syncStop) |
| 1373 | f.syncStop = nil |
| 1374 | } |
| 1375 | if f.syncTicker != nil { |
| 1376 | f.syncTicker.Stop() |
| 1377 | } |
| 1378 | f.mu.Unlock() |
| 1379 | |
| 1380 | // Stop compaction monitors if running |
| 1381 | if f.compactionCancel != nil { |
| 1382 | f.compactionCancel() |
| 1383 | f.compactionWg.Wait() |
| 1384 | } |
| 1385 | |
| 1386 | // Final sync of dirty pages before closing |
| 1387 | f.mu.Lock() |
| 1388 | if f.writeEnabled && len(f.dirty) > 0 { |
| 1389 | if err := f.syncToRemoteWithLock(); err != nil { |
| 1390 | f.logger.Error("failed to sync on close", "error", err) |
| 1391 | } |
| 1392 | } |
| 1393 | f.mu.Unlock() |
| 1394 | |
| 1395 | f.cancel() |
| 1396 | f.wg.Wait() |
| 1397 | |
| 1398 | // Close and remove buffer file if open |
| 1399 | if f.bufferFile != nil { |
| 1400 | f.bufferFile.Close() |
| 1401 | os.Remove(f.bufferPath) |
| 1402 | } |
| 1403 | |
| 1404 | // Close and remove hydration file |
| 1405 | if f.hydrator != nil { |
| 1406 | if err := f.hydrator.Close(); err != nil { |
| 1407 | f.logger.Warn("failed to close hydration file", "error", err) |
| 1408 | } |
| 1409 | } |
| 1410 | |
| 1411 | if f.writeEnabled && f.vfs != nil { |
| 1412 | f.vfs.writeMu.Lock() |
| 1413 | if f.vfs.writeFile == f { |
| 1414 | f.vfs.writeFile = nil |
| 1415 | } |
| 1416 | f.vfs.writeMu.Unlock() |
| 1417 | } |
| 1418 | |
| 1419 | return nil |
| 1420 | } |
| 1421 | |
| 1422 | func (f *VFSFile) ReadAt(p []byte, off int64) (n int, err error) { |
| 1423 | f.logger.Debug("reading at", "off", off, "len", len(p)) |