Skip to content

Commit

Permalink
Revert async.zsh changes to run stress tests against main
Browse files Browse the repository at this point in the history
  • Loading branch information
mafredri committed Jan 5, 2023
1 parent d7faf08 commit a2cc80e
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 71 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ jobs:
uses: actions/checkout@v2

- name: Run tests
timeout-minutes: 1
timeout-minutes: 10
run: |
zsh --version
script -q <<<'zsh -if ./test.zsh -v; exit $?'
for i in $(seq 1 100); do
script -q <<<'zsh -if ./test.zsh -v -run stress; exit $?' || exit $?
done
ubuntu:
name: Ubuntu
Expand Down Expand Up @@ -80,8 +82,10 @@ jobs:
uses: actions/checkout@v2

- name: Run tests
timeout-minutes: 1
timeout-minutes: 10
run: |
export PATH="/opt/zsh/bin:$PATH"
zsh --version
script -qec 'zsh -if ./test.zsh -v' /dev/null
for i in $(seq 1 100); do
script -qec 'zsh -if ./test.zsh -v -run stress' /dev/null || exit $?
done
95 changes: 28 additions & 67 deletions async.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,6 @@
typeset -g ASYNC_VERSION=1.8.6
# Produce debug output from zsh-async when set to 1.
typeset -g ASYNC_DEBUG=${ASYNC_DEBUG:-0}
# When ASYNC_DEBUG=1, worker stderr output will be redirected here.
typeset -g ASYNC_DEBUG_WORKER_STDERR=${ASYNC_DEBUG_WORKER_STDERR:-/dev/null}

# The maximum buffer size when outputting to zpty.
# Note: Subtract 4 to accommodate "\r\n" times two.
#
# When processing large amounts of data, the limit of 1024 bytes is
# slow. If you're going to output a lot more than that, consider
# increasing the buffer size.
#
# This value was chosen as a safe limit for macOS and other systems that
# have a low limit (1024) for the buffer, on Linux this can likely be
# raised significantly.
typeset -g ASYNC_MAX_BUFFER_SIZE=${ASYNC_MAX_BUFFER_SIZE:-$((1024 - 4))}

# Execute commands that can manipulate the environment inside the async worker. Return output via callback.
_async_eval() {
Expand All @@ -34,17 +20,17 @@ _async_eval() {
# simplicity, this could be improved in the future.
{
eval "$@"
} &> >(ASYNC_JOB_NAME=[async/eval] _async_job 0 'command -p cat')
} &> >(ASYNC_JOB_NAME=[async/eval] _async_job 'command -p cat')
}

# Wrapper for jobs executed by the async worker, gives output in parseable format with execution time
_async_job() {
# Disable xtrace as it would mangle the output.
setopt localoptions noxtrace

# Store start time for job.
float -F duration=$EPOCHREALTIME

# Parent pid for notifications via kill signal.
local parent_pid=$1; shift

# Run the command and capture both stdout (`eval`) and stderr (`cat`) in
# separate subshells. When the command is complete, we grab write lock
# (mutex token) and output everything except stderr inside the command
Expand All @@ -54,10 +40,6 @@ _async_job() {
local jobname=${ASYNC_JOB_NAME:-$1} out
out="$(
local stdout stderr ret tok
# Disable xtrace as it would mangle the stderr. The user can
# still enable xtrace inside the async job, if required.
setopt noxtrace
{
stdout=$(eval "$@")
ret=$?
Expand All @@ -74,32 +56,8 @@ _async_job() {
# Grab mutex lock, stalls until token is available.
read -r -k 1 -p tok || return 1

# Chunk up the output so as to not fill up the entire fd.
for ((i = 1; i < $#out; i += ASYNC_MAX_BUFFER_SIZE)); do
# Note: We are surrounding the message in newlines here in an
# attempt to force zpty to behave. Literal newlines will be
# filtered by async_process_results. Any newlines in the job
# output will survive, as they are quoted.
#
# Return output (<job_name> <return_code> <stdout> <duration> <stderr>).
if ! print -r -n - $'\n'"${out[$i,$((i + ASYNC_MAX_BUFFER_SIZE - 1))]}"$'\n'; then
# BUG(mafredri): The worker and parent process should be informed.
# Unlock mutex to prevent a deadlock.
print -n -p $tok
break
fi

# When notifications are enabled, inform the parent that the
# buffer is filling up and must be consumed.
if ((parent_pid)); then
# On older version of zsh (pre 5.2) we notify the parent through a
# SIGWINCH signal because `zpty` did not return a file descriptor
# (fd) prior to that. We use SIGWINCH for because other signals
# (INFO, ALRM, USR1, etc.) can cause a deadlock in some situations.
# (The deadlock was fixed in zsh 5.1.1.)
kill -WINCH $parent_pid
fi
done
# Return output (<job_name> <return_code> <stdout> <duration> <stderr>).
print -r -n - "$out"

# Unlock mutex by inserting a token.
print -n -p $tok
Expand All @@ -114,18 +72,11 @@ _async_worker() {
# pids of child processes.
unsetopt monitor

# Redirect stderr to `/dev/null` in case unforeseen errors produced by the
# Redirect stderr to `/dev/null` in case unforseen errors produced by the
# worker. For example: `fork failed: resource temporarily unavailable`.
# Some older versions of zsh might also print malloc errors (know to happen
# on at least zsh 5.0.2 and 5.0.8) likely due to kill signals.
if ((ASYNC_DEBUG)); then
exec 2>>${ASYNC_DEBUG_WORKER_STDERR}
if [[ $ASYNC_DEBUG_WORKER_STDERR != /dev/null ]]; then
setopt xtrace
fi
else
exec 2>/dev/null
fi
exec 2>/dev/null

# When a zpty is deleted (using -d) all the zpty instances created before
# the one being deleted receive a SIGHUP, unless we catch it, the async
Expand Down Expand Up @@ -164,8 +115,22 @@ _async_worker() {
fi
}

child_exit() {
close_idle_coproc

# On older version of zsh (pre 5.2) we notify the parent through a
# SIGWINCH signal because `zpty` did not return a file descriptor (fd)
# prior to that.
if (( notify_parent )); then
# We use SIGWINCH for compatibility with older versions of zsh
# (pre 5.1.1) where other signals (INFO, ALRM, USR1, etc.) could
# cause a deadlock in the shell under certain circumstances.
kill -WINCH $parent_pid
fi
}

# Register a SIGCHLD trap to handle the completion of child processes.
trap close_idle_coproc CHLD
trap child_exit CHLD

# Process option parameters passed to worker.
while getopts "np:uz" opt; do
Expand All @@ -176,9 +141,6 @@ _async_worker() {
z) notify_parent=0;; # Uses ZLE watcher instead.
esac
done
if ((!notify_parent)) {
parent_pid=0
}

# Terminate all running jobs, note that this function does not
# reinstall the child trap.
Expand Down Expand Up @@ -211,10 +173,10 @@ _async_worker() {
(( coproc_pid )) && read -r -k 1 -p tok

terminate_jobs
trap close_idle_coproc CHLD # Reinstall child trap.
trap child_exit CHLD # Reinstall child trap.
}

local request job do_eval=0
local request do_eval=0
local -a cmd
while :; do
# Wait for jobs sent by async_job.
Expand Down Expand Up @@ -249,7 +211,7 @@ _async_worker() {
cmd=("${(z)request}")

# Name of the job (first argument).
job=$cmd[1]
local job=$cmd[1]

# Check if a worker should perform unique jobs, unless
# this is an eval since they run synchronously.
Expand Down Expand Up @@ -281,7 +243,7 @@ _async_worker() {
_async_eval $cmd
else
# Run job in background, completed jobs are printed to stdout.
_async_job $parent_pid $cmd &
_async_job $cmd &
# Store pid because zsh job manager is extremely unflexible (show jobname as non-unique '$job')...
storage[$job]="$!"
fi
Expand Down Expand Up @@ -330,8 +292,7 @@ async_process_results() {

# Read output from zpty and parse it if available.
while zpty -r -t $worker data 2>/dev/null; do
# Trim newlines that are not part of the data.
ASYNC_PROCESS_BUFFER[$worker]+=${${data//$'\r'/}//$'\n'/}
ASYNC_PROCESS_BUFFER[$worker]+=$data
len=${#ASYNC_PROCESS_BUFFER[$worker]}
pos=${ASYNC_PROCESS_BUFFER[$worker][(i)$null]} # Get index of NULL-character (delimiter).

Expand Down

0 comments on commit a2cc80e

Please sign in to comment.