154 lines
5.8 KiB
Go
154 lines
5.8 KiB
Go
/*
|
||
* Copyright 2026 Safronov Grigorii
|
||
*
|
||
* Licensed under the CDDL, Version 1.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
*
|
||
* You may obtain a copy of the License at
|
||
* https://opensource.org/licenses/CDDL-1.0
|
||
*/
|
||
|
||
// Файл: internal/storage/fsync.go
|
||
// Назначение: Реальная синхронизация с диском (fsync) для WAL
|
||
// Поддерживает все платформы: Linux, macOS, BSD, Windows, Solaris/Illumos (OpenIndiana)
|
||
|
||
package storage
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"runtime"
|
||
"time"
|
||
)
|
||
|
||
// RealFsync выполняет реальный fsync на файле.
|
||
// Использует стандартный метод Sync() для максимальной совместимости
|
||
// со всеми платформами включая OpenIndiana (Solaris/Illumos).
|
||
func RealFsync(file *os.File) error {
|
||
if file == nil {
|
||
return nil
|
||
}
|
||
|
||
// Вызываем системный fsync через стандартный метод Sync
|
||
// Это работает на всех платформах: Linux, macOS, BSD, Windows, Solaris/Illumos
|
||
return file.Sync()
|
||
}
|
||
|
||
// RealFsyncWithRetry выполняет fsync с повторными попытками.
|
||
// Параметры:
|
||
// - file: файл для синхронизации
|
||
// - maxRetries: максимальное количество попыток
|
||
// - retryDelay: задержка между попытками
|
||
//
|
||
// Возвращает ошибку, если все попытки не удались.
|
||
func RealFsyncWithRetry(file *os.File, maxRetries int, retryDelay time.Duration) error {
|
||
if file == nil {
|
||
return nil
|
||
}
|
||
|
||
var lastErr error
|
||
for i := 0; i < maxRetries; i++ {
|
||
if err := RealFsync(file); err != nil {
|
||
lastErr = err
|
||
if i < maxRetries-1 {
|
||
time.Sleep(retryDelay)
|
||
continue
|
||
}
|
||
return fmt.Errorf("fsync failed after %d attempts: %v", maxRetries, lastErr)
|
||
}
|
||
return nil
|
||
}
|
||
return lastErr
|
||
}
|
||
|
||
// FsyncDir синхронизирует директорию (для гарантии, что создание файла записано на диск).
|
||
// На некоторых платформах (Windows, Solaris/Illumos) fsync для директорий не поддерживается.
|
||
func FsyncDir(dirPath string) error {
|
||
dir, err := os.Open(dirPath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer dir.Close()
|
||
|
||
// На Windows и Solaris/Illumos нет прямой поддержки fsync для директорий
|
||
// Используем стандартный Sync если доступен
|
||
switch runtime.GOOS {
|
||
case "windows", "solaris", "illumos", "openindiana":
|
||
// На этих платформах fsync для директорий не поддерживается или не нужен
|
||
// Возвращаем nil, так как данные уже записаны через fsync файлов
|
||
return nil
|
||
default:
|
||
// Для Linux, BSD, macOS используем Sync
|
||
return dir.Sync()
|
||
}
|
||
}
|
||
|
||
// FsyncDataOnly выполняет fsync только для данных (не метаданных).
|
||
// Использует стандартный Sync для всех платформ.
|
||
// Это обеспечивает запись данных на диск без синхронизации метаданных.
|
||
func FsyncDataOnly(file *os.File) error {
|
||
if file == nil {
|
||
return nil
|
||
}
|
||
|
||
// Используем стандартный Sync для всех платформ
|
||
// Это обеспечит запись данных на диск
|
||
return file.Sync()
|
||
}
|
||
|
||
// FsyncWithTimeout выполняет fsync с таймаутом.
|
||
// Если операция не завершается за указанное время, возвращается ошибка.
|
||
func FsyncWithTimeout(file *os.File, timeout time.Duration) error {
|
||
if file == nil {
|
||
return nil
|
||
}
|
||
|
||
// Создаём канал для результата
|
||
result := make(chan error, 1)
|
||
|
||
// Запускаем fsync в отдельной горутине
|
||
go func() {
|
||
result <- file.Sync()
|
||
}()
|
||
|
||
// Ожидаем результат или таймаут
|
||
select {
|
||
case err := <-result:
|
||
return err
|
||
case <-time.After(timeout):
|
||
return fmt.Errorf("fsync timeout after %v", timeout)
|
||
}
|
||
}
|
||
|
||
// MustFsync выполняет fsync и паникует при ошибке.
|
||
// Используется только в критических секциях, где ошибка синхронизации фатальна.
|
||
func MustFsync(file *os.File) {
|
||
if file == nil {
|
||
return
|
||
}
|
||
|
||
if err := file.Sync(); err != nil {
|
||
panic(fmt.Sprintf("Fatal: fsync failed: %v", err))
|
||
}
|
||
}
|
||
|
||
// IsFsyncSupported возвращает true если платформа поддерживает fsync.
|
||
// Все платформы поддерживают fsync через file.Sync().
|
||
func IsFsyncSupported() bool {
|
||
return true
|
||
}
|
||
|
||
// GetFsyncPlatformInfo возвращает информацию о платформе для диагностики.
|
||
func GetFsyncPlatformInfo() map[string]interface{} {
|
||
return map[string]interface{}{
|
||
"os": runtime.GOOS,
|
||
"arch": runtime.GOARCH,
|
||
"fsync_supported": IsFsyncSupported(),
|
||
"dir_fsync_supported": !(runtime.GOOS == "windows" ||
|
||
runtime.GOOS == "solaris" ||
|
||
runtime.GOOS == "illumos" ||
|
||
runtime.GOOS == "openindiana"),
|
||
"method": "file.Sync()",
|
||
}
|
||
}
|