feat(kugetsu): add lock mechanism for worktree coordination #90

Closed
shoko wants to merge 1 commits from feat/issue-71-lock-mechanism into main
Showing only changes of commit 3fa9bee166 - Show all commits

View File

@@ -8,6 +8,7 @@ REPOS_CONFIG="$KUGETSU_DIR/repos.json"
INDEX_FILE="$KUGETSU_DIR/index.json"
NOTIFICATIONS_FILE="$KUGETSU_DIR/notifications.json"
LOGS_DIR="$KUGETSU_DIR/logs"
LOCKS_DIR="$KUGETSU_DIR/locks"
MAX_CONCURRENT_AGENTS="${MAX_CONCURRENT_AGENTS:-3}"
# Load user config overrides (~/.kugetsu/config)
@@ -30,6 +31,89 @@ count_active_dev_sessions() {
echo "$count"
}
acquire_lock() {
local issue_ref="$1"
local session_id="$2"
local lock_file="$LOCKS_DIR/${issue_ref//[^a-zA-Z0-9._-]/_}.lock"
mkdir -p "$LOCKS_DIR"
if [ -f "$lock_file" ]; then
local lock_pid=$(cat "$lock_file" 2>/dev/null | cut -d: -f1)
local lock_session=$(cat "$lock_file" 2>/dev/null | cut -d: -f2)
local lock_time=$(cat "$lock_file" 2>/dev/null | cut -d: -f3)
if [ -n "$lock_pid" ] && ! kill -0 "$lock_pid" 2>/dev/null; then
echo "Stale lock detected, removing..."
rm -f "$lock_file"
elif [ "$lock_session" = "$session_id" ]; then
echo "Already holding lock for $issue_ref"
return 0
else
echo "Error: $issue_ref is locked by session $lock_session (PID $lock_pid)" >&2
return 1
fi
fi
echo "${BASHPID}:${session_id}:$(date +%s)" > "$lock_file"
echo "Lock acquired for $issue_ref: $lock_file"
return 0
}
release_lock() {
local issue_ref="$1"
local lock_file="$LOCKS_DIR/${issue_ref//[^a-zA-Z0-9._-]/_}.lock"
if [ ! -f "$lock_file" ]; then
return 0
fi
local lock_pid=$(cat "$lock_file" 2>/dev/null | cut -d: -f1)
if [ "$lock_pid" = "$BASHPID" ]; then
rm -f "$lock_file"
echo "Lock released for $issue_ref"
else
echo "Error: Cannot release lock held by PID $lock_pid (current: $BASHPID)" >&2
return 1
fi
}
release_all_locks() {
local session_id="$1"
if [ ! -d "$LOCKS_DIR" ]; then
return 0
fi
for lock_file in "$LOCKS_DIR"/*.lock; do
[ -f "$lock_file" ] || continue
local lock_session=$(cat "$lock_file" 2>/dev/null | cut -d: -f2)
if [ "$lock_session" = "$session_id" ]; then
rm -f "$lock_file"
echo "Released stale lock: $(basename "$lock_file")"
fi
done
}
check_lock() {
local issue_ref="$1"
local lock_file="$LOCKS_DIR/${issue_ref//[^a-zA-Z0-9._-]/_}.lock"
if [ -f "$lock_file" ]; then
local lock_pid=$(cat "$lock_file" 2>/dev/null | cut -d: -f1)
local lock_session=$(cat "$lock_file" 2>/dev/null | cut -d: -f2)
local lock_time=$(cat "$lock_file" 2>/dev/null | cut -d: -f3)
if [ -n "$lock_pid" ] && ! kill -0 "$lock_pid" 2>/dev/null; then
echo "Stale lock detected, removing..."
rm -f "$lock_file"
return 1
fi
echo "Locked by session $lock_session (PID $lock_pid)"
return 0
fi
return 1
}
usage() {
cat << 'EOF'
kugetsu - OpenCode Session Manager (Issue-Driven)
@@ -825,6 +909,14 @@ cmd_start() {
fi
local worktree_path=$(issue_ref_to_worktree_path "$issue_ref")
trap 'release_lock "$issue_ref" 2>/dev/null; exit' EXIT INT TERM
if ! acquire_lock "$issue_ref" "$base_session_id"; then
echo "Error: Could not acquire lock for '$issue_ref'" >&2
exit 1
fi
create_worktree "$issue_ref"
local session_file="$(issue_ref_to_filename "$issue_ref").json"
@@ -869,6 +961,8 @@ cmd_start() {
add_issue_to_index "$issue_ref" "$session_file"
release_lock "$issue_ref"
echo "Session started for '$issue_ref': $new_session_id"
echo "Worktree: $worktree_path"
}
@@ -1126,6 +1220,7 @@ cmd_destroy() {
remove_worktree_for_issue "$target"
rm -f "$session_path"
remove_issue_from_index "$target"
release_lock "$target" 2>/dev/null || true
echo "Session for '$target' destroyed"
else
echo "Delete session and worktree for '$target'? [y/N] "
@@ -1135,6 +1230,7 @@ cmd_destroy() {
remove_worktree_for_issue "$target"
rm -f "$session_path"
remove_issue_from_index "$target"
release_lock "$target" 2>/dev/null || true
echo "Session for '$target' destroyed"
else
echo "Aborted"