Skip to content

Commit

Permalink
Prefix async job output to prevent external corruption
Browse files Browse the repository at this point in the history
If an external process interferes with the async worker (e.g. writes its
pty), the output can become corrupt. To minimize this occurance we now
prefix all job output with the NULL character, instead of just separate
on NULLs (suffix).
  • Loading branch information
mafredri committed Aug 6, 2018
1 parent da48ccb commit b15ecf9
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 7 deletions.
8 changes: 5 additions & 3 deletions async.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ _async_job() {
read -r -k 1 -p tok || exit 1

# Return output (<job_name> <return_code> <stdout> <duration> <stderr>).
print -r -n - ${(q)1} $ret ${(q)stdout} $duration
print -r -n - $'\0'${(q)1} $ret ${(q)stdout} $duration
} 2> >(stderr=$(cat) && print -r -n - " "${(q)stderr}$'\0')

# Unlock mutex by inserting a token.
Expand Down Expand Up @@ -245,13 +245,15 @@ async_process_results() {
if (( $#items == 5 )); then
items+=($has_next)
$callback "${(@)items}" # Send all parsed items to the callback.
(( num_processed++ ))
elif [[ -z $items ]]; then
# Empty items occur between results due to double-null ($'\0\0')
# caused by commands being both pre and suffixed with null.
else
# In case of corrupt data, invoke callback with *async* as job
# name, non-zero exit status and an error message on stderr.
$callback "async" 1 "" 0 "$0:$LINENO: error: bad format, got ${#items} items (${(q)items})" $has_next
fi

(( num_processed++ ))
done
done

Expand Down
10 changes: 6 additions & 4 deletions async_test.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ test__async_job_print_hi() {
local line
local -a out
line=$(_async_job print hi)
# Remove trailing null, parse, unquote and interpret as array.
line=$line[1,$#line-1]
# Remove leading/trailing null, parse, unquote and interpret as array.
line=$line[2,$#line-1]
out=("${(@Q)${(z)line}}")

coproc exit
Expand Down Expand Up @@ -396,8 +396,10 @@ test_async_flush_jobs() {
# TODO: Confirm that they no longer exist in the process tree.
local output
output="${(Q)$(ASYNC_DEBUG=1 async_flush_jobs test)}"
[[ $output = *'print_four 0 4'* ]] || {
t_error "want discarded output 'print_four 0 4' when ASYNC_DEBUG=1, got ${(Vq-)output}"
# NOTE(mafredri): First 'p' in print_four is lost when null-prefixing
# _async_job output.
[[ $output = *'rint_four 0 4'* ]] || {
t_error "want discarded output 'rint_four 0 4' when ASYNC_DEBUG=1, got ${(Vq-)output}"
}

# Check that the killed job did not produce output.
Expand Down

0 comments on commit b15ecf9

Please sign in to comment.