Skip to content

Commit e1a5ca4

Browse files
committed
fix(ci/full-run): Use sqlite WAL and busy timeout
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
1 parent 10c3ae2 commit e1a5ca4

2 files changed

Lines changed: 70 additions & 26 deletions

File tree

.github/workflows/cluster-faces-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ jobs:
637637
if: steps.db-cache.outputs.cache-hit != 'true'
638638
run: |
639639
./occ app_api:daemon:register --net host manual_install "Manual Install" manual-install http localhost http://localhost:8080
640-
./occ app_api:app:register recognize_backend manual_install --json-info "{\"appid\":\"recognize_backend\",\"name\":\"Recognize Backend\",\"daemon_config_name\":\"manual_install\",\"version\":\"${{ steps.backendinfo.outputs.result }}\",\"secret\":\"12345\",\"port\":9031,\"scopes\":[\"TASK_PROCESSING\",\"FILES\"],\"system_app\":0}" --force-scopes --wait-finish
640+
./occ app_api:app:register recognize_backend manual_install --json-info "{\"appid\":\"recognize_backend\",\"name\":\"Recognize Backend\",\"daemon_config_name\":\"manual_install\",\"version\":\"${{ steps.backendinfo.outputs.result }}\",\"secret\":\"12345\",\"port\":9031,\"scopes\":[\"TASK_PROCESSING\",\"FILES\"]}" --force-scopes --wait-finish
641641
642642
- name: install sqlite3
643643
if: steps.db-cache.outputs.cache-hit != 'true' && env.ACT # Skip this on normal GitHub Actions

.github/workflows/full-run-test.yml

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,15 @@ jobs:
323323
# 4 workers by default
324324
composer run serve &
325325
326+
- name: Enable SQLite WAL mode
327+
if: ${{ matrix.databases == 'sqlite' }}
328+
run: |
329+
# WAL lets the ExApp's readers and cron's writers proceed concurrently
330+
# instead of serializing on SQLite's single-writer lock, which otherwise
331+
# stalls task scheduling for many minutes per cron run. The mode is
332+
# persisted in the database header, so it survives across connections.
333+
sqlite3 data/nextcloud.db "PRAGMA journal_mode=WAL;"
334+
326335
- name: Checkout app_api
327336
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
328337
with:
@@ -379,7 +388,7 @@ jobs:
379388
run: |
380389
./occ app_api:daemon:register --net host manual_install "Manual Install" manual-install http localhost http://localhost:8080
381390
./occ app_api:app:register ${{ env.APP_ID }} manual_install --json-info \
382-
"{\"appid\":\"${{ env.APP_ID }}\",\"name\":\"Recognize Backend\",\"daemon_config_name\":\"manual_install\",\"version\":\"${{ env.APP_VERSION }}\",\"secret\":\"${{ env.APP_SECRET }}\",\"port\":${{ env.APP_PORT }},\"scopes\":[\"TASK_PROCESSING\",\"FILES\"],\"system_app\":1}" \
391+
"{\"appid\":\"${{ env.APP_ID }}\",\"name\":\"Recognize Backend\",\"daemon_config_name\":\"manual_install\",\"version\":\"${{ env.APP_VERSION }}\",\"secret\":\"${{ env.APP_SECRET }}\",\"port\":${{ env.APP_PORT }},\"scopes\":[\"TASK_PROCESSING\",\"FILES\"]}" \
383392
--force-scopes --wait-finish
384393
385394
- name: Upload photos
@@ -394,36 +403,71 @@ jobs:
394403
./occ config:app:set --value ${{ matrix.musicnn-enabled }} recognize musicnn.enabled
395404
./occ config:app:set --value ${{ matrix.movinet-enabled }} recognize movinet.enabled
396405
397-
- name: Schedule classification tasks
398-
env:
399-
GITHUB_REF: ${{ github.ref }}
406+
- name: Run classification via TaskProcessing
407+
id: classify
400408
run: |
409+
# Probe available disk so we can tell whether runs die on ENOSPC.
410+
disk_free() { df -h / | awk 'NR==2 {print $4" free ("$5" used)"}'; }
411+
# Query the DB with a busy timeout so a transient WAL lock (the ExApp and
412+
# cron write concurrently) makes the poll wait instead of failing with
413+
# "database is locked" (exit 5), which would otherwise kill the step.
414+
sq() { sqlite3 -cmd ".timeout 60000" data/nextcloud.db "$1"; }
415+
echo "disk before classification: $(disk_free)"
416+
./occ upgrade # in case server master has new migrations in the meantime
401417
./occ files:scan admin
402-
# recognize:classify does not work in taskprocessing mode; instead let the
403-
# background jobs crawl the storages and schedule TaskProcessing tasks.
418+
# Kick off a full classification run: SchedulerJob -> StorageCrawlJob
419+
# fills the faces queue and ClassifyFacesJob hands each batch to the
420+
# recognize_backend ExApp as a TaskProcessing task. Results are written
421+
# back asynchronously by the TaskResultListener when the ExApp reports.
404422
./occ recognize:recrawl
405-
# Run cron a few times so SchedulerJob -> StorageCrawlJob -> Classify*Job run
406-
# in sequence and schedule the TaskProcessing tasks for the uploaded files.
407-
for i in $(seq 1 12); do
408-
php cron.php -v
409-
sleep 30
423+
# Drive the background jobs by running cron in a loop until the faces
424+
# queue is drained and no crawl/scheduler jobs remain.
425+
for i in $(seq 1 180); do
426+
# Clustering is done explicitly in the "Run clustering" step below, so
427+
# drop these jobs before each cron run to keep classification cron fast.
428+
sq "delete from oc_jobs where class like '%ClusterFacesJob';" || true
429+
php cron.php || true
430+
QUEUE=$(sq "select count(*) from oc_recognize_queue_faces;")
431+
CRAWL=$(sq "select count(*) from oc_jobs where class like '%StorageCrawlJob' or class like '%SchedulerJob';")
432+
echo "round $i: faces queue=$QUEUE, pending crawl/scheduler jobs=$CRAWL, disk=$(disk_free)"
433+
if [ "$QUEUE" -eq 0 ] && [ "$CRAWL" -eq 0 ] && [ "$i" -gt 3 ]; then break; fi
434+
sleep 10
410435
done
411-
412-
- name: Wait for tasks to be processed by recognize_backend
413-
run: |
414-
set -x
415-
# The backend downloads the models on the first run and processes the tasks;
416-
# TaskResultListener applies the results as each task succeeds.
417-
NEXT_WAIT_TIME=0
418-
DETECTIONS=0
419-
until [ $NEXT_WAIT_TIME -eq 60 ] || [ "$DETECTIONS" -gt 0 ]; do
420-
php cron.php -v
421-
DETECTIONS=$(sqlite3 data/nextcloud.db "select count(*) from oc_recognize_face_detections;" 2>/dev/null || echo 0)
422-
echo "face detections so far: $DETECTIONS (iteration $NEXT_WAIT_TIME)"
436+
# Wait for the ExApp to finish processing all scheduled TaskProcessing
437+
# tasks (status 0=unknown, 1=scheduled, 2=running are still pending).
438+
for i in $(seq 1 240); do
439+
PENDING=$(sq "select count(*) from oc_taskprocessing_tasks where app_id = 'recognize' and status in (0, 1, 2);")
440+
echo "wait $i: pending recognize taskprocessing tasks=$PENDING, disk=$(disk_free)"
441+
if [ "$PENDING" -eq 0 ]; then break; fi
423442
sleep 30
424-
NEXT_WAIT_TIME=$((NEXT_WAIT_TIME + 1))
425443
done
426-
# Fail if the backend never produced any results
444+
echo "disk after classification: $(disk_free)"
445+
446+
# Whether the run actually finished. The faces queue emptying is not
447+
# enough: files leave the queue when their TaskProcessing task is
448+
# *scheduled*, not when its results are written back, so a drained queue
449+
# with tasks still pending means those photos have no detections at all.
450+
# Detection counts cannot be used here - a photo with no face in it
451+
# legitimately produces no rows - so completeness is queue + task state.
452+
QUEUE=$(sq "select count(*) from oc_recognize_queue_faces;")
453+
PENDING=$(sq "select count(*) from oc_taskprocessing_tasks where app_id = 'recognize' and status in (0, 1, 2);")
454+
FAILED=$(sq "select count(*) from oc_taskprocessing_tasks where app_id = 'recognize' and status = 4;")
455+
TOTAL=$(sq "select count(*) from oc_taskprocessing_tasks where app_id = 'recognize';")
456+
echo "final: faces queue=$QUEUE, pending tasks=$PENDING, failed tasks=$FAILED, total tasks=$TOTAL"
457+
echo "photos on disk: $(find data/admin/files -type f | wc -l)"
458+
DETECTIONS=$(sq "select count(*) from oc_recognize_face_detections;")
459+
echo "photos with detections: $(sq "select count(distinct file_id) from oc_recognize_face_detections where user_id = 'admin';")"
460+
echo "face detections: $DETECTIONS"
461+
462+
if [ "$QUEUE" -eq 0 ] && [ "$PENDING" -eq 0 ] && [ "$FAILED" -eq 0 ]; then
463+
echo "complete=true" >> "$GITHUB_OUTPUT"
464+
else
465+
echo "complete=false" >> "$GITHUB_OUTPUT"
466+
echo "::error title=Incomplete classification run::faces queue=$QUEUE, pending tasks=$PENDING, failed tasks=$FAILED of $TOTAL. Each pending task covers up to 500 photos, whose detections were never written. The detection DB will NOT be cached; the metrics below score only the photos that made it through."
467+
fi
468+
469+
# Hard signal for this job: the TaskProcessing pipeline must actually
470+
# persist face detections.
427471
[ "$DETECTIONS" -gt 0 ]
428472
429473
- name: Save recognize_backend models cache

0 commit comments

Comments
 (0)