diff --git a/config/functions b/config/functions index 05545f1ec5..6f76f0e259 100644 --- a/config/functions +++ b/config/functions @@ -1175,7 +1175,7 @@ add_group() { } # Usage: enable_service [target] -enable_service () { +enable_service() { local unit="$1" local unit_dir="/usr/lib/systemd/system" local target="$2" @@ -1197,6 +1197,148 @@ enable_service () { done } + +### MULTI-THREADED FUNCTION HELPERS ### +# Prevent concurrent modifications to a package (unpack) or +# package:target (install/build). +# +# If a package is already locked and the owner is ourselves +# then assume we already have the required lock. +pkg_lock() { + [ "${MTWITHLOCKS}" != "yes" ] && return 0 + + local pkg="$1" task="$2" parent_pkg="$3" + local this_job="${MTJOBID}" + local lock_job lock_task lock_pkg + + exec 98>"${THREAD_CONTROL}/locks/${pkg}.${task}" + if ! flock --nonblock --exclusive 98; then + while [ : ]; do + read -r lock_job lock_task lock_pkg <<<$(cat "${THREAD_CONTROL}/locks/${pkg}.${task}.owner" 2>/dev/null) + [ -n "${lock_job}" ] && break || sleep 1 + done + + if [ ${lock_job} != ${this_job} ]; then + pkg_lock_status "STALLED" "${parent_pkg}" "${task}" "$(printf "waiting on [%02d] %s %s" ${lock_job} "${lock_task}" "${lock_pkg}")" + flock --exclusive 98 + fi + fi + echo "${this_job} ${task} ${pkg}" >"${THREAD_CONTROL}/locks/${pkg}.${task}.owner" + + pkg_lock_status "LOCKED" "${pkg}" "${task}" +} + +# Log additional information for a locked package. +pkg_lock_status() { + [ "${MTWITHLOCKS}" != "yes" ] && return 0 + + local status="$1" pkg="$2" task="$3" msg="$4" + local this_job="${MTJOBID}" line + + printf -v line "%s: <%05d> [%02d/%0*d] %-7s %-7s %-35s" \ + "$(date +%Y-%m-%d\ %H:%M:%S.%N)" $$ ${this_job} ${#MTMAXJOBS} ${PARALLEL_SEQ:-0} "${status}" "${task}" "${pkg}" + [ -n "${msg}" ] && line+=" (${msg})" + + echo "${line}" >>"${THREAD_CONTROL}/history" + + [ "${DASHBOARD}" != "no" ] && update_dashboard "${status}" "${pkg}" "${task}" "${msg}" +} + +update_dashboard() { + local status="$1" pkg="$2" task="$3" msg="$4" + local line sedline preamble num elapsed projdevarch + local boldred boldgreen boldyellow endcolor + + ( + flock --exclusive 97 + + [ -n "${MTJOBID}" ] && sedline=$((MTJOBID + 2)) || sedline=1 + + num=$(cat "${THREAD_CONTROL}/status" | wc -l) + while [ ${num} -lt ${sedline} ]; do echo "" >>"${THREAD_CONTROL}/status"; num=$((num + 1)); done + + num=$(($(cat "${THREAD_CONTROL}/progress.prev") + 1)) + projdevarch="${PROJECT}/" + [ -n "${DEVICE}" ] && projdevarch+="${DEVICE}/" + projdevarch+="${TARGET_ARCH}" + TZ=UTC0 printf -v elapsed "%(%H:%M:%S)T" $(($(date +%s) - MTBUILDSTART)) + printf -v preamble "%s Dashboard (%s) - %d of %d jobs completed, %s elapsed" "${DISTRONAME}" "${projdevarch}" ${num} ${MTMAXJOBS} "${elapsed}" + printf -v preamble "%b%-105s %s" "\e[2J\e[0;0H" "${preamble//\//\\/}" "$(date "+%Y-%m-%d %H:%M:%S")" + + # Only update the header when caller is not a worker thread + if [ -z "${MTJOBID}" ]; then + sed -e "1s/.*/${preamble}/" -i "${THREAD_CONTROL}/status" + else + if [ "${DISABLE_COLORS}" != "yes" ]; then + boldred="\e[1;31m" + boldgreen="\e[1;32m" + boldyellow="\e[1;33m" + white="\e[0;37m" + endcolor="\e[0m" + + case "${status}" in + IDLE) color="${white}";; + STALLED) color="${boldyellow}";; + MUTEX/W) color="${boldyellow}";; + FAILED ) color="${boldred}";; + *) color="${boldgreen}";; + esac + fi + + printf -v line "[%02d\/%0*d] %b%-7s%b %-7s %-35s" ${MTJOBID} ${#MTMAXJOBS} ${PARALLEL_SEQ:-0} "${color}" "${status//\//\\/}" "${endcolor}" "${task}" "${pkg}" + [ -n "${msg}" ] && line+=" ${msg//\//\\/}" + sed -e "1s/.*/${preamble}/;${sedline}s/.*/${line}/" -i "${THREAD_CONTROL}/status" + fi + ) 97>"${THREAD_CONTROL}/locks/.status" +} + +# Thread concurrency helpers to avoid concurrency issues with some code, +# eg. when Python installs directly into $TOOLCHAIN. +# Test MTJOBID so that these functions are a no-op during non-multithreaded builds. +acquire_exclusive_lock() { + [ "${MTWITHLOCKS}" != "yes" ] && return 0 + + local pkg="$1" task="$2" lockfile="${3:-.global}" + local this_job="${MTJOBID}" + local lock_job lock_task lock_pkg + + exec 96>"${THREAD_CONTROL}/locks/${lockfile}" + if ! flock --nonblock --exclusive 96; then + while [ : ]; do + read -r lock_job lock_task lock_pkg <<<$(cat "${THREAD_CONTROL}/locks/${lockfile}.owner" 2>/dev/null) + [ -n "${lock_job}" ] && break || sleep 1 + done + + if [ ${lock_job} != ${this_job} ]; then + pkg_lock_status "MUTEX/W" "${pkg}" "${task}" "$(printf "mutex: %s; waiting on [%02d] %s %s" "${lockfile}" ${lock_job} "${lock_task}" "${lock_pkg}")" + flock --exclusive 96 + fi + fi + echo "${this_job} ${task} ${pkg}" >"${THREAD_CONTROL}/locks/${lockfile}.owner" + + pkg_lock_status "MUTEX" "${pkg}" "${task}" "mutex: ${lockfile}" +} + +release_exclusive_lock() { + [ "${MTWITHLOCKS}" != "yes" ] && return 0 + + local pkg="$1" task="$2" + + flock --unlock 96 2>/dev/null + + pkg_lock_status "ACTIVE" "${pkg}" "${task}" +} + +# Execute single command using mutex +exec_thread_safe() { + local result + acquire_exclusive_lock "${PKG_NAME:exec}" "execcmd" + $@ + result=$? + release_exclusive_lock "${PKG_NAME:exec}" "execcmd" + return ${result} +} + # Use distribution functions if any if [ -f "distributions/$DISTRO/config/functions" ]; then . distributions/$DISTRO/config/functions