| 1 | #!/bin/bash
|
| 2 | # P4 finding: two concurrent `oak push` to the same branch on loopback `oak serve`
|
| 3 | # both exit 0; the independent server DB (sqlite3) shows one acknowledged commit
|
| 4 | # is not an ancestor of the final branch head (lost acknowledged write).
|
| 5 | # Usage: concurrent-push-lost-ack.sh <oak> <outdir> [trials]
|
| 6 | set -u
|
| 7 | OAK=$1; OUT=$2; TRIALS=${3:-5}
|
| 8 | rm -rf "$OUT"; mkdir -p "$OUT/home" "$OUT/seed"
|
| 9 | export HOME=$OUT/home OAK_NO_UPDATE_CHECK=1 OAK_API_KEY=p4tok OAK_AUTHOR=p4
|
| 10 | unset OAK_REMOTE OAK_REPO OAK_TOKEN
|
| 11 | PORT=$(python3 -c 'import socket;s=socket.socket();s.bind(("127.0.0.1",0));print(s.getsockname()[1])')
|
| 12 | "$OAK" serve --dir "$OUT/srv" --port $PORT --token p4tok >"$OUT/serve.log" 2>&1 & SP=$!
|
| 13 | trap 'kill $SP 2>/dev/null' EXIT
|
| 14 | for i in $(seq 1 100); do curl -s -o /dev/null http://127.0.0.1:$PORT/api/capabilities && break; sleep 0.05; done
|
| 15 | cd "$OUT/seed"; "$OAK" init . >/dev/null 2>&1; echo base > base.txt; "$OAK" commit >/dev/null 2>&1
|
| 16 | "$OAK" push --repo p4/r -r http://127.0.0.1:$PORT >/dev/null 2>&1 || { echo seed push failed; exit 2; }
|
| 17 | BR=$(sqlite3 .oak/oak.db "select value from metadata where key='current_branch'")
|
| 18 | lost_total=0
|
| 19 | for t in $(seq 1 $TRIALS); do
|
| 20 | rm -rf "$OUT/a" "$OUT/b"; "$OAK" pull >/dev/null 2>&1; cp -R "$OUT/seed" "$OUT/a"; cp -R "$OUT/seed" "$OUT/b"
|
| 21 | (cd "$OUT/a"; echo "a$t" > a$t.txt; "$OAK" commit >/dev/null 2>&1)
|
| 22 | (cd "$OUT/b"; echo "b$t" > b$t.txt; "$OAK" commit >/dev/null 2>&1)
|
| 23 | HA=$(sqlite3 "$OUT/a/.oak/oak.db" "select value from metadata where key='head'")
|
| 24 | HB=$(sqlite3 "$OUT/b/.oak/oak.db" "select value from metadata where key='head'")
|
| 25 | (cd "$OUT/a"; "$OAK" push >"$OUT/a.out" 2>&1; echo $? > "$OUT/a.rc") & PA=$!
|
| 26 | (cd "$OUT/b"; "$OAK" push >"$OUT/b.out" 2>&1; echo $? > "$OUT/b.rc") & PB=$!
|
| 27 | wait $PA $PB
|
| 28 | FINAL=$(sqlite3 "$OUT/srv/p4/r.oakdb" "select head_hash from branch_heads where branch_name='$BR'")
|
| 29 | # ancestors of FINAL via the independent server graph
|
| 30 | ANC=$(sqlite3 "$OUT/srv/p4/r.oakdb" "with recursive a(h) as (select '$FINAL' union select c.parent_hash from commits c join a on c.hash=a.h where c.parent_hash is not null union select c.merge_parent_hash from commits c join a on c.hash=a.h where c.merge_parent_hash is not null) select h from a")
|
| 31 | RA=$(cat "$OUT/a.rc"); RB=$(cat "$OUT/b.rc")
|
| 32 | inA=$(echo "$ANC" | grep -c "$HA"); inB=$(echo "$ANC" | grep -c "$HB")
|
| 33 | verdict=ok
|
| 34 | if [ "$RA" = 0 ] && [ "$inA" = 0 ]; then verdict=LOST_ACK_A; lost_total=$((lost_total+1)); fi
|
| 35 | if [ "$RB" = 0 ] && [ "$inB" = 0 ]; then verdict=LOST_ACK_B; lost_total=$((lost_total+1)); fi
|
| 36 | echo "trial=$t a_rc=$RA b_rc=$RB final=${FINAL:0:12} a=${HA:0:12}(in_final=$inA) b=${HB:0:12}(in_final=$inB) $verdict"
|
| 37 | done
|
| 38 | echo "lost_acknowledged_pushes=$lost_total/$TRIALS"
|