Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F165125252
D58183.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
D58183.diff
View Options
diff --git a/usr.sbin/bsdconfig/include/messages.subr b/usr.sbin/bsdconfig/include/messages.subr
--- a/usr.sbin/bsdconfig/include/messages.subr
+++ b/usr.sbin/bsdconfig/include/messages.subr
@@ -174,6 +174,7 @@
msg_installed="Installed"
msg_installed_desc="Leave package as-is, installed"
msg_installed_lc="installed"
+msg_installing_n_packages="Installing %u package%s (up to %u at a time)"
msg_invalid_gateway_ipv4_address_specified="Invalid gateway IPv4 address specified"
msg_invalid_hostname_value="Invalid hostname value"
msg_invalid_ipv4_address="Invalid IPv4 address"
@@ -264,6 +265,7 @@
msg_other="other"
msg_package_is_needed_by_other_installed_packages="Warning: Package %s is needed by\n %d other installed package%s."
msg_package_not_installed_cannot_delete="Warning: package %s not installed\n No package can be deleted."
+msg_packages_failed_to_install="The following package%s failed to install:\n%s\nLogs have been saved to %s"
msg_package_temp="Package Temp"
msg_package_was_added_successfully="Package %s was added successfully"
msg_packages="packages"
diff --git a/usr.sbin/bsdconfig/share/packages/packages.subr b/usr.sbin/bsdconfig/share/packages/packages.subr
--- a/usr.sbin/bsdconfig/share/packages/packages.subr
+++ b/usr.sbin/bsdconfig/share/packages/packages.subr
@@ -48,6 +48,12 @@
#
: ${PACKAGE_MENU_PAGESIZE:=2000}
+#
+# How many packages to install simultaneously (dependencies permitting).
+# Default is the number of CPUs reported by hw.ncpu.
+#
+: ${PACKAGE_INSTALL_JOBS:=$( sysctl -n hw.ncpu 2> /dev/null || echo 1 )}
+
############################################################ GLOBALS
#
@@ -569,6 +575,256 @@
return $retval
}
+# f_package_parallel_install $package ...
+#
+# Install each $package argument concurrently, scaling to at-most
+# $PACKAGE_INSTALL_JOBS simultaneous pkg-install(8) jobs (default `hw.ncpu')
+# while honoring inter-package dependencies. A package becomes eligible for
+# installation the moment its last dependency is installed, allowing packages
+# with no outstanding dependencies to `skip the line' rather than wait on
+# unrelated installations. Dependency information comes from the INDEX (see
+# f_index_read() of packages/index.subr) while dependency solving within each
+# job is deferred to the pkg(8) SAT solver. Overall progress is displayed
+# with bsdpv(1). Packages whose dependencies fail to install are skipped.
+# Returns success if every package installed successfully.
+#
+# Job scheduling is achieved through Kahn-style topological ordering: each
+# package knows how many of its dependencies remain uninstalled and each
+# dependency knows which dependents to unblock upon completion. Workers
+# announce their exit status over a FIFO, serialized by the kernel guarantee
+# that writes of PIPE_BUF bytes or less are atomic.
+#
+f_package_parallel_install()
+{
+ local funcname=f_package_parallel_install
+ local package varpkg dep vardep rundeps rdeps state visit n
+ local maxjobs="$PACKAGE_INSTALL_JOBS"
+ local njobs=0 ndone=0 nfail=0 nskip=0 total=0
+ local ready= failed= logdir fifo pvfifo bsdpv_pid= status
+ local tmpdir
+
+ [ $# -gt 0 ] || return $SUCCESS
+
+ f_musthavepkg_init # Make sure we have a usable pkg(8) with $PKG_ABI
+
+ f_isinteger "$maxjobs" || maxjobs=1
+ [ $maxjobs -ge 1 ] || maxjobs=1
+
+ #
+ # Create a scratch area to hold the notification FIFOs and a
+ # pkg-install(8) log file for each package
+ #
+ f_getvar $VAR_PKG_TMPDIR:-/var/tmp tmpdir
+ logdir=$( mktemp -d "$tmpdir/packages.XXXXXX" ) || return $FAILURE
+ fifo="$logdir/job.fifo"
+ pvfifo="$logdir/bsdpv.fifo"
+ if ! mkfifo "$fifo" "$pvfifo"; then
+ rm -rf "$logdir"
+ return $FAILURE
+ fi
+
+ #
+ # Build the dependency graph for the requested set. Packages depended
+ # upon that are not part of the set are either already installed or
+ # will be satisfied by the pkg(8) solver within a single job.
+ #
+ for package in "$@"; do
+ f_str2varname $package varpkg
+ setvar _pjob_state_$varpkg wait
+ setvar _pjob_ndeps_$varpkg 0
+ setvar _pjob_rdeps_$varpkg ""
+ total=$(( $total + 1 ))
+ done
+ for package in "$@"; do
+ f_str2varname $package varpkg
+ rundeps=
+ debug= f_getvar _rundeps_$varpkg rundeps
+ for dep in $rundeps; do
+ f_str2varname $dep vardep
+ # Only count dependencies belonging to this job set
+ debug= f_getvar _pjob_state_$vardep state || continue
+ [ "$state" ] || continue
+ debug= f_getvar _pjob_ndeps_$varpkg n
+ setvar _pjob_ndeps_$varpkg $(( $n + 1 ))
+ debug= f_getvar _pjob_rdeps_$vardep visit
+ setvar _pjob_rdeps_$vardep "$visit $package"
+ done
+ done
+
+ #
+ # Seed the ready-queue with packages lacking pending dependencies
+ #
+ for package in "$@"; do
+ f_str2varname $package varpkg
+ debug= f_getvar _pjob_ndeps_$varpkg n
+ if [ $n -eq 0 ]; then
+ setvar _pjob_state_$varpkg ready
+ ready="$ready${ready:+ }$package"
+ fi
+ done
+ f_dprintf "%s: total=%u maxjobs=%u ready=[%s]" $funcname \
+ $total $maxjobs "$ready"
+
+ #
+ # Start the progress viewer, fed one line per package completion
+ #
+ local grammatical_s= pprompt
+ [ $total -ne 1 ] && grammatical_s=s
+ f_sprintf pprompt "$msg_installing_n_packages" \
+ $total "$grammatical_s" $maxjobs
+ if [ ! "$USE_XDIALOG" ] && f_have bsdpv; then
+ bsdpv -DklN -t "$DIALOG_TITLE" -b "$DIALOG_BACKTITLE" \
+ -p "$pprompt" "$total:$msg_packages" \
+ < "$pvfifo" &
+ bsdpv_pid=$!
+ else
+ f_show_info "%s" "$pprompt"
+ # Prevent deadlock on FIFO lacking a reader
+ cat < "$pvfifo" > /dev/null &
+ fi
+ exec 9> "$pvfifo"
+
+ # Hold the notification FIFO open so job completions never block and
+ # so read(1) below blocks (rather than EOF) while jobs are running
+ exec 8<> "$fifo"
+
+ #
+ # Dispatch jobs as dependencies allow, blocking on the notification
+ # FIFO whenever the ready-queue empties or all job slots are taken
+ #
+ while [ $(( $ndone + $nfail + $nskip )) -lt $total ]; do
+ #
+ # Fill available job slots from the ready-queue
+ #
+ while [ $njobs -lt $maxjobs -a "$ready" ]; do
+ package="${ready%% *}"
+ if [ "$package" = "$ready" ]; then
+ ready=
+ else
+ ready="${ready#* }"
+ fi
+ f_str2varname $package varpkg
+ debug= f_getvar _pjob_state_$varpkg state
+ [ "$state" = "ready" ] || continue # e.g., skipped
+ setvar _pjob_state_$varpkg busy
+ njobs=$(( $njobs + 1 ))
+ f_dprintf "%s: starting %s (%u/%u jobs)" $funcname \
+ "$package" $njobs $maxjobs
+ (
+ exec > "$logdir/$varpkg.log" 2>&1
+ export ASSUME_ALWAYS_YES=YES
+ export LOCK_WAIT=1 LOCK_RETRIES=7200
+ # Tolerate sibling pkg(8) locks
+ pkg fetch -U "$package"
+ # Parallel-friendly; failure deferred
+ # to pkg-install(8) below
+ pkg install -U "$package"
+ echo "$? $package" > "$fifo"
+ ) &
+ done
+
+ #
+ # Await a job completion notification
+ #
+ status= package=
+ read status package <&8
+ njobs=$(( $njobs - 1 ))
+ f_str2varname $package varpkg
+ if [ "$status" = "0" ]; then
+ ndone=$(( $ndone + 1 ))
+ setvar _pjob_state_$varpkg done
+ setvar _mark_$varpkg X
+ f_dprintf "%s: %s installed successfully" $funcname \
+ "$package"
+ echo "$package" >&9 # Advance bsdpv(1)
+
+ # Unblock dependents; enqueue those becoming ready
+ rdeps=
+ debug= f_getvar _pjob_rdeps_$varpkg rdeps
+ for dep in $rdeps; do
+ f_str2varname $dep vardep
+ debug= f_getvar _pjob_state_$vardep state
+ [ "$state" = "wait" ] || continue
+ debug= f_getvar _pjob_ndeps_$vardep n
+ setvar _pjob_ndeps_$vardep $(( $n - 1 ))
+ if [ $(( $n - 1 )) -eq 0 ]; then
+ setvar _pjob_state_$vardep ready
+ ready="$ready${ready:+ }$dep"
+ fi
+ done
+ else
+ nfail=$(( $nfail + 1 ))
+ setvar _pjob_state_$varpkg fail
+ failed="$failed${failed:+ }$package"
+ f_dprintf "%s: %s failed to install (see %s)" \
+ $funcname "$package" "$logdir/$varpkg.log"
+ echo "$package" >&9 # Advance bsdpv(1)
+
+ #
+ # Propagate failure breadth-first: dependents (and
+ # theirs) can never run, so mark each skipped and
+ # emit a progress line to keep accounting whole
+ #
+ visit="$package"
+ while [ "$visit" ]; do
+ package="${visit%% *}"
+ if [ "$package" = "$visit" ]; then
+ visit=
+ else
+ visit="${visit#* }"
+ fi
+ f_str2varname $package varpkg
+ rdeps=
+ debug= f_getvar _pjob_rdeps_$varpkg rdeps
+ for dep in $rdeps; do
+ f_str2varname $dep vardep
+ debug= f_getvar \
+ _pjob_state_$vardep state
+ case "$state" in
+ wait|ready) : ok ;;
+ *) continue
+ esac
+ setvar _pjob_state_$vardep skip
+ nskip=$(( $nskip + 1 ))
+ failed="$failed $dep"
+ f_dprintf "%s: %s skipped (%s)" \
+ $funcname "$dep" \
+ "dependency failed"
+ echo "$dep" >&9 # Advance bsdpv(1)
+ visit="$visit${visit:+ }$dep"
+ done
+ done
+ fi
+ done
+
+ # Dismiss the progress viewer and notification plumbing
+ exec 9>&-
+ exec 8<&-
+ [ "$bsdpv_pid" ] && wait $bsdpv_pid
+
+ # Discard per-package job state
+ for package in "$@"; do
+ f_str2varname $package varpkg
+ unset _pjob_state_$varpkg
+ unset _pjob_ndeps_$varpkg
+ unset _pjob_rdeps_$varpkg
+ done
+
+ f_dprintf "%s: ndone=%u nfail=%u nskip=%u" $funcname \
+ $ndone $nfail $nskip
+
+ if [ "$failed" ]; then
+ grammatical_s=
+ [ $(( $nfail + $nskip )) -ne 1 ] && grammatical_s=s
+ f_show_msg "$msg_packages_failed_to_install" \
+ "$grammatical_s" "$failed" "$logdir"
+ return $FAILURE
+ fi
+
+ rm -rf "$logdir"
+ return $SUCCESS
+}
+
# f_package_review
#
# Display a review screen, showing selected packages and what they are marked
@@ -626,18 +882,34 @@
#
# Process each of the selected packages:
- # + First, process packages marked for Install.
+ # + First, process packages marked for Install (parallel).
# + Second, process packages marked for Re-install.
# + Finally, process packages marked for Uninstall.
#
+ # Packages marked for Install -- and their to-be-installed
+ # dependencies (marked `D') -- are handed as one job set to
+ # f_package_parallel_install(), installing independent packages
+ # simultaneously while dependents await their dependencies.
+ #
+ local install_list=""
for package in $SELECTED_PACKAGES; do
mark=
f_str2varname "$package" varpkg
debug= f_getvar _mark_$varpkg mark
- [ "$mark" = "I" ] || continue
- f_dprintf "$funcname: Installing %s package" "$package"
- f_package_add "$package"
+ [ "$mark" = "I" -o "$mark" = "D" ] || continue
+ install_list="$install_list${install_list:+ }$package"
done
+ case "$install_list" in
+ "") : nothing to install ;;
+ *" "*)
+ f_dprintf "$funcname: Installing packages in parallel: %s" \
+ "$install_list"
+ f_package_parallel_install $install_list
+ ;;
+ *)
+ f_dprintf "$funcname: Installing %s package" "$install_list"
+ f_package_add "$install_list"
+ esac
for package in $SELECTED_PACKAGES; do
mark=
f_str2varname "$package" varpkg
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Aug 7, 6:40 AM (7 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
36150515
Default Alt Text
D58183.diff (10 KB)
Attached To
Mode
D58183: bsdconfig: dependency-aware parallel package installer with progress
Attached
Detach File
Event Timeline
Log In to Comment