using System.Text.Json;
namespace SqrtSpace.SpaceTime.Core;
///
/// Manages checkpointing for fault-tolerant operations
///
public class CheckpointManager : IDisposable
{
private readonly string _checkpointDirectory;
private readonly CheckpointStrategy _strategy;
private readonly int _checkpointInterval;
private int _operationCount;
private readonly List _checkpointFiles = new();
///
/// Initializes a new checkpoint manager
///
/// Directory to store checkpoints
/// Checkpointing strategy
/// Total expected operations (for √n calculation)
public CheckpointManager(
string? checkpointDirectory = null,
CheckpointStrategy strategy = CheckpointStrategy.SqrtN,
long totalOperations = 1_000_000)
{
_checkpointDirectory = checkpointDirectory ?? Path.Combine(Path.GetTempPath(), $"spacetime_checkpoint_{Guid.NewGuid()}");
_strategy = strategy;
_checkpointInterval = SpaceTimeCalculator.CalculateCheckpointCount(totalOperations, strategy);
Directory.CreateDirectory(_checkpointDirectory);
}
///
/// Checks if a checkpoint should be created
///
/// True if checkpoint should be created
public bool ShouldCheckpoint()
{
_operationCount++;
return _strategy switch
{
CheckpointStrategy.None => false,
CheckpointStrategy.SqrtN => _operationCount % _checkpointInterval == 0,
CheckpointStrategy.Linear => _operationCount % 1000 == 0,
CheckpointStrategy.Logarithmic => IsPowerOfTwo(_operationCount),
_ => false
};
}
///
/// Creates a checkpoint for the given state
///
/// Type of state to checkpoint
/// State to save
/// Optional checkpoint ID
/// Path to checkpoint file
public async Task CreateCheckpointAsync(T state, string? checkpointId = null)
{
checkpointId ??= $"checkpoint_{_operationCount}_{DateTime.UtcNow.Ticks}";
var filePath = Path.Combine(_checkpointDirectory, $"{checkpointId}.json");
var json = JsonSerializer.Serialize(state, new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
await File.WriteAllTextAsync(filePath, json);
_checkpointFiles.Add(filePath);
// Clean up old checkpoints if using √n strategy
if (_strategy == CheckpointStrategy.SqrtN && _checkpointFiles.Count > Math.Sqrt(_operationCount))
{
CleanupOldCheckpoints();
}
return filePath;
}
///
/// Restores state from the latest checkpoint
///
/// Type of state to restore
/// Restored state or null if no checkpoint exists
public async Task RestoreLatestCheckpointAsync()
{
var latestCheckpoint = Directory.GetFiles(_checkpointDirectory, "*.json")
.OrderByDescending(f => new FileInfo(f).LastWriteTimeUtc)
.FirstOrDefault();
if (latestCheckpoint == null)
return default;
var json = await File.ReadAllTextAsync(latestCheckpoint);
return JsonSerializer.Deserialize(json, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
}
///
/// Restores state from a specific checkpoint
///
/// Type of state to restore
/// Checkpoint ID to restore
/// Restored state or null if checkpoint doesn't exist
public async Task RestoreCheckpointAsync(string checkpointId)
{
var filePath = Path.Combine(_checkpointDirectory, $"{checkpointId}.json");
if (!File.Exists(filePath))
return default;
var json = await File.ReadAllTextAsync(filePath);
return JsonSerializer.Deserialize(json, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
}
///
/// Gets the number of operations since last checkpoint
///
public int OperationsSinceLastCheckpoint => _operationCount % _checkpointInterval;
///
/// Saves state for a specific checkpoint and key
///
/// Type of state to save
/// Checkpoint ID
/// State key
/// State to save
/// Cancellation token
public async Task SaveStateAsync(string checkpointId, string key, T state, CancellationToken cancellationToken = default) where T : class
{
var filePath = Path.Combine(_checkpointDirectory, $"{checkpointId}_{key}.json");
var json = JsonSerializer.Serialize(state, new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
await File.WriteAllTextAsync(filePath, json, cancellationToken);
}
///
/// Loads state for a specific checkpoint and key
///
/// Type of state to load
/// Checkpoint ID
/// State key
/// Cancellation token
/// Loaded state or null if not found
public async Task LoadStateAsync(string checkpointId, string key, CancellationToken cancellationToken = default) where T : class
{
var filePath = Path.Combine(_checkpointDirectory, $"{checkpointId}_{key}.json");
if (!File.Exists(filePath))
return null;
var json = await File.ReadAllTextAsync(filePath, cancellationToken);
return JsonSerializer.Deserialize(json, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
}
///
/// Cleans up checkpoint files
///
public void Dispose()
{
try
{
if (Directory.Exists(_checkpointDirectory))
{
Directory.Delete(_checkpointDirectory, recursive: true);
}
}
catch
{
// Best effort cleanup
}
}
private void CleanupOldCheckpoints()
{
// Keep only the most recent √n checkpoints
var toKeep = (int)Math.Sqrt(_operationCount);
var toDelete = _checkpointFiles
.OrderBy(f => new FileInfo(f).LastWriteTimeUtc)
.Take(_checkpointFiles.Count - toKeep)
.ToList();
foreach (var file in toDelete)
{
try
{
File.Delete(file);
_checkpointFiles.Remove(file);
}
catch
{
// Best effort
}
}
}
private static bool IsPowerOfTwo(int n)
{
return n > 0 && (n & (n - 1)) == 0;
}
}
///
/// Attribute to mark methods as checkpointable
///
[AttributeUsage(AttributeTargets.Method)]
public class CheckpointableAttribute : Attribute
{
///
/// Checkpointing strategy to use
///
public CheckpointStrategy Strategy { get; set; } = CheckpointStrategy.SqrtN;
///
/// Whether to automatically restore from checkpoint on failure
///
public bool AutoRestore { get; set; } = true;
///
/// Custom checkpoint directory
///
public string? CheckpointDirectory { get; set; }
}