acquireLock method
Attempts to acquire local process lock for projectPath.
Implementation
Future<bool> acquireLock(
String projectPath, {
required String operation,
bool dryRun = false,
}) async {
if (dryRun) return true;
final lockPath = getLockFilePath(projectPath);
final lockFile = File(lockPath);
if (await lockFile.exists()) {
try {
final content = await lockFile.readAsString();
final json = jsonDecode(content) as Map<String, dynamic>;
final lockInfo = LockInfo.fromJson(json);
final lockTime = DateTime.tryParse(lockInfo.timestamp);
final isStale = lockTime != null &&
DateTime.now().toUtc().difference(lockTime) > _staleTimeout;
bool isProcessActive = true;
try {
// Check if process PID is still running using kill(pid, 0) signal
isProcessActive =
Process.killPid(lockInfo.pid, ProcessSignal.sigusr1) ||
Process.killPid(lockInfo.pid, ProcessSignal.sighup);
} catch (_) {
isProcessActive = false;
}
if (!isStale && isProcessActive && lockInfo.pid != pid) {
throw ValidationException(
'Concurrency lock active: Another release process (PID: ${lockInfo.pid}, Operation: "${lockInfo.operation}") is running in "$projectPath".',
);
}
} catch (e) {
if (e is ValidationException) rethrow;
// Ignore JSON decode error and overwrite stale lock
}
}
final parentDir = lockFile.parent;
if (!await parentDir.exists()) {
await parentDir.create(recursive: true);
}
final lockInfo = LockInfo(
pid: pid,
timestamp: DateTime.now().toUtc().toIso8601String(),
operation: operation,
);
await lockFile.writeAsString(jsonEncode(lockInfo.toJson()));
return true;
}