#!/bin/bash
# Simulasi dua terminal yang saling mengunci (deadlock):
# - Terminal A transfer Budi -> Siti : kunci id 1 dulu, lalu id 2.
# - Terminal B transfer Siti -> Budi : kunci id 2 dulu, lalu id 1.
# PostgreSQL mendeteksi siklus dan membatalkan salah satunya.

: "${PGDATABASE:=latihan}"
export PGDATABASE

TMPD=$(mktemp -d)
mkfifo "$TMPD/komando-a.fifo" "$TMPD/komando-b.fifo"

PGAPPNAME=terminal-a psql -X < "$TMPD/komando-a.fifo" > "$TMPD/a.log" 2>&1 &
SESIA=$!
PGAPPNAME=terminal-b psql -X < "$TMPD/komando-b.fifo" > "$TMPD/b.log" 2>&1 &
SESIB=$!
exec 3>"$TMPD/komando-a.fifo" 4>"$TMPD/komando-b.fifo"

# Terminal A mengunci baris id = 1
printf 'BEGIN;\nUPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;\n' >&3
for i in $(seq 1 100); do
  n=$(psql -X -tA -c "SELECT count(*) FROM pg_stat_activity
                      WHERE datname = current_database()
                        AND application_name = 'terminal-a'
                        AND state = 'idle in transaction'")
  [ "$n" = "1" ] && break
  sleep 0.05
done

# Terminal B mengunci baris id = 2
printf 'BEGIN;\nUPDATE rekening SET saldo = saldo - 50000 WHERE id = 2;\n' >&4
for i in $(seq 1 100); do
  n=$(psql -X -tA -c "SELECT count(*) FROM pg_stat_activity
                      WHERE datname = current_database()
                        AND application_name = 'terminal-b'
                        AND state = 'idle in transaction'")
  [ "$n" = "1" ] && break
  sleep 0.05
done

# Terminal A minta baris id = 2 -> terblokir, menunggu B
printf 'UPDATE rekening SET saldo = saldo + 100000 WHERE id = 2;\n' >&3
for i in $(seq 1 200); do
  n=$(psql -X -tA -c "SELECT count(*) FROM pg_stat_activity
                      WHERE datname = current_database()
                        AND application_name = 'terminal-a'
                        AND state = 'active' AND wait_event_type = 'Lock'")
  [ "$n" = "1" ] && break
  sleep 0.05
done

# Terminal B minta baris id = 1 -> terblokir juga; siklus terbentuk,
# sekitar satu detik kemudian PostgreSQL membatalkan terminal A
printf 'UPDATE rekening SET saldo = saldo + 50000 WHERE id = 1;\n' >&4

printf 'COMMIT;\n' >&3
printf 'COMMIT;\n' >&4
exec 3>&- 4>&-
wait $SESIA $SESIB

echo "== Terminal A (korban: dibatalkan PostgreSQL) =="
cat "$TMPD/a.log"
echo
echo "== Terminal B (selamat: transaksinya lanjut dan commit) =="
cat "$TMPD/b.log"
echo
echo "== Isi rekening setelah kejadian =="
psql -X -c "SELECT id, nama, saldo FROM rekening ORDER BY id;"

rm -rf "$TMPD"
