acquireLock method

Future<bool> acquireLock(
  1. String projectPath, {
  2. required String operation,
  3. bool dryRun = false,
})

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;
}