Upload files to "internal/storage"
This commit is contained in:
153
internal/storage/fsync.go
Normal file
153
internal/storage/fsync.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* 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()",
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user