• Stars
    star
    710
  • Rank 63,751 (Top 2 %)
  • Language
    Shell
  • License
    MIT License
  • Created over 9 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Because your terminal should be able to perform tasks asynchronously without external tools!

zsh-async

Test

Because your terminal should be able to perform tasks asynchronously without external tools!

Intro (TL;DR)

With zsh-async you can run multiple asynchronous jobs, enforce unique jobs (multiple instances of the same job will not run), flush all currently running jobs and create multiple workers (each with their own jobs). For each worker you can register a callback-function through which you will be notified about the job results (job name, return code, output and execution time).

Overview

zsh-async is a small library for running asynchronous tasks in zsh without requiring any external tools. It utilizes zsh/zpty to launch a pseudo-terminal in which all commands get executed without blocking any other processes. Checking for completed tasks can be done manually, by polling, or better yet, automatically whenever a process has finished executing by notifying through a SIGWINCH kill-signal.

This library bridges the gap between spawning child processes and disowning them. Child processes launched by normal means clutter the terminal with output about their state, and disowned processes become separate entities, no longer under control of the parent. Now you can have both!

Usage

The async worker is a separate environment (think web worker). You send it a job (command + parameters) to execute and it returns the result of that execution through a callback function. If you find that you need to stop/start a worker to update global state (variables) you should consider refactoring so that state is passed during the async_job call (e.g. async_job my_worker my_function $state1 $state2).

Note that because the worker is a separate forked environment, any functions you want to use as jobs in the worker need to be defined before the worker is started, otherwise you will get a command not found error when you try to launch the job.

Installation

Manual

You can either source the async.zsh script directly or insert under your $fpath as async and autoload it through autoload -Uz async && async.

Integration

zplug
zplug "mafredri/zsh-async", from:"github", use:"async.zsh"

Functions

The zsh-async library has a bunch of functions that need to be used to perform async actions:

async_init

Initializes the async library (not required if using async from $fpath with autoload.)

async_start_worker <worker_name> [-u] [-n] [-p <pid>]

Start a new async worker with optional parameters, a worker can be told to only run unique tasks and to notify a process when tasks are complete.

  • -u unique. Only unique job names can run, e.g. the command git status will have git as the unique job name identifier

  • -n notify through SIGWINCH signal. Needs to be caught with a trap '' WINCH in the process defined by -p

    NOTE: When zsh-async is used in an interactive shell with ZLE enabled this option is not needed. Signaling through SIGWINCH has been replaced by a ZLE watcher that is triggered on output from the zpty instance (still requires a callback function through async_register_callback though). Technically zsh versions prior to 5.2 do not return the file descriptor for zpty instances, however, zsh-async attempts to deduce it anyway.

  • -p pid to notify (defaults to current pid)

async_stop_worker <worker_name_1> [<worker_name_2>]

Simply stops a worker and all active jobs will be terminated immediately.

async_job <worker_name> <my_function> [<function_params>]

Start a new asynchronous job on specified worker, assumes the worker is running. Note if you are using a function for the job, it must have been defined before the worker was started or you will get a command not found error.

async_worker_eval <worker_name> <my_function> [<function_params>]

Evaluate a command (like async_job) inside the async worker, then worker environment can be manipulated. For example, issuing a cd command will change the PWD of the worker which will then be inherited by all future async jobs.

Output will be returned via callback, job name will be [async/eval].

async_process_results <worker_name> <callback_function>

Get results from finished jobs and pass it to the to callback function. This is the only way to reliably return the job name, return code, output and execution time and with minimal effort.

If the async process buffer becomes corrupt, the callback will be invoked with the first argument being [async] (job name), non-zero return code and fifth argument describing the error (stderr).

The callback_function is called with the following parameters:

  • $1 job name, e.g. the function passed to async_job
  • $2 return code
    • Returns -1 if return code is missing, this should never happen, if it does, you have likely run into a bug. Please open a new issue with a detailed description of what you were doing.
  • $3 resulting (stdout) output from job execution
  • $4 execution time, floating point e.g. 0.0076138973 seconds
  • $5 resulting (stderr) error output from job execution
  • $6 has next result in buffer (0 = buffer empty, 1 = yes)
    • This means another async job has completed and is pending in the buffer, it's very likely that your callback function will be called a second time (or more) in this execution. It's generally a good idea to e.g. delay prompt updates (zle reset-prompt) until the buffer is empty to prevent strange states in ZLE.

Possible error return codes for the job name [async]:

  • 1 Corrupt worker output.
  • 2 ZLE watcher detected an error on the worker fd.
  • 3 Response from async_job when worker is missing.
  • 130 Async worker crashed, this should not happen but it can mean the file descriptor has become corrupt. This must be followed by a async_stop_worker [name] and then the worker and tasks should be restarted. It is unknown why this happens.

async_register_callback <worker_name> <callback_function>

Register a callback for completed jobs. As soon as a job is finished, async_process_results will be called with the specified callback function. This requires that a worker is initialized with the -n (notify) option.

async_unregister_callback <worker_name>

Unregister the callback for a specific worker.

async_flush_jobs <worker_name>

Flush all current jobs running on a worker. This will terminate any and all running processes under the worker by sending a SIGTERM to the entire process group, use with caution.

Example code

#!/usr/bin/env zsh
source ./async.zsh
async_init

# Initialize a new worker (with notify option)
async_start_worker my_worker -n

# Create a callback function to process results
COMPLETED=0
completed_callback() {
	COMPLETED=$(( COMPLETED + 1 ))
	print $@
}

# Register callback function for the workers completed jobs
async_register_callback my_worker completed_callback

# Give the worker some tasks to perform
async_job my_worker print hello
async_job my_worker sleep 0.3

# Wait for the two tasks to be completed
while (( COMPLETED < 2 )); do
	print "Waiting..."
	sleep 0.1
done

print "Completed $COMPLETED tasks!"

# Output:
#	Waiting...
#	print 0 hello 0.001583099365234375
#	Waiting...
#	Waiting...
#	sleep 0 0.30631208419799805
#	Completed 2 tasks!

Testing

Tests are located in *_test.zsh and can be run by executing the test runner: ./test.zsh.

Example:

$ ./test.zsh
ok	./async_test.zsh	2.334s

The test suite can also run specific tasks that match a pattern, for example:

$ ./test.zsh -v -run zle
=== RUN   test_zle_watcher
--- PASS: test_zle_watcher (0.07s)
PASS
ok	./async_test.zsh	0.070s

Limitations

  • A NULL-character ($'\0') is used by async_job to signify the end of the command, it is recommended not to pass them as arguments, although they should work when passing multiple arguments to async_job (because of quoting).
  • Tell me? :)

Tips

If you do not wish to use the notify feature, you can couple zsh-async with zsh/sched or the zsh periodic function for scheduling the worker results to be processed.

Why did I make this?

I found a great theme for zsh, Pure by Sindre Sorhus. After using it for a while I noticed some graphical glitches due to the terminal being updated by a disowned process. Thus, I became inspired to get my hands dirty and find a solution. I tried many things, coprocesses (seemed too limited by themselves), different combinations of trapping kill-signals, etc. I also had problems with the zsh process ending up in a deadlock due to some zsh bug. After working out the kinks, I ended up with this and thought, hey, why not make it a library.

More Repositories

1

cdp

Package cdp provides type-safe bindings for the Chrome DevTools Protocol (CDP), written in the Go programming language.
Go
686
star
2

vyatta-wireguard-installer

Install, upgrade or remove WireGuard (WireGuard/wireguard-vyatta-ubnt) on Ubiquiti hardware
Shell
142
star
3

phoenix-config

My personal Phoenix (kasper/phoenix) configuration, written in TypeScript
TypeScript
38
star
4

phoenix-typings

TypeScript typings for Phoenix (kasper/phoenix)
29
star
5

macos-display-overrides

Display overrides to make (my) monitors work better on macOS
Ruby
22
star
6

go-trueskill

An implementation of the TrueSkillâ„¢ ranking system (by Microsoft) in Go
Go
22
star
7

macos-brightness

Simple CLI for changing brightness on macOS
Go
20
star
8

asustor-platform-driver

Linux kernel platform driver for ASUSTOR NAS hardware (leds, buttons)
C
19
star
9

zeet

My personal Zsh setup
Shell
16
star
10

openbttn

Open source firmware for bt.tn buttons
C
16
star
11

asustor_as-6xxt

All things ASUSTOR AS-6XXT sans ADM
Shell
13
star
12

asustor-packages

Collection of packages for ASUSTOR ADM
Shell
9
star
13

lcm

Implementation of the ASUSTOR NAS LCD serial port communication protocol
Go
8
star
14

asdev

ASUSTOR development tool, upload apps, etc.
Go
7
star
15

safari-bookmarklets

Bookmarklets for Safari (macOS / iOS)
JavaScript
7
star
16

gpg-notify

Notify about GPG events (pinentry, smartcard)
Go
6
star
17

fineli-sql

Convert Fineli, the Finnish Food Composition Database, into sqlite3 / SQL
Shell
5
star
18

macos-darkmode

Simple CLI for controlling macOS Dark Mode via Apples private API
Go
5
star
19

debian-my-live-build

Set up a custom debian live image (CD / USB)
Shell
5
star
20

goodspeaker

This package speaks the LG speaker wire protocol which is used by both LG Music Flow Player and LG Wi-Fi Speaker
Go
4
star
21

asustor-radarr

Radarr for ASUSTOR ADM
Shell
4
star
22

magic4linux

Allows you to use the magic remote on your webOS LG TV as a keyboard/mouse for your Linux machine
Go
4
star
23

asustor-deluge

Build Deluge package for ASUSTOR ADM
Shell
4
star
24

asustor-libcec

Shell
3
star
25

go-gaussian

Normal (gaussian) distribution for Go
Go
3
star
26

musicflow

This package and its commands can be used to control LG speakers that use the Music Flow app
Go
3
star
27

kodi-watchedlist-sync

Sync Kodi watched status without watchedlist plugin (MySQL)
PLpgSQL
2
star
28

vyatta-dhcp-option-6rd-reporter

Add IPv6 6RD reporting for UniFi Security Gateway
Shell
2
star
29

svenska-yle-rss-content-fixer

Attach content to Svenska Yle RSS feeds
Go
2
star
30

go-trueskilld

A simple http API for mafredri/go-trueskill (https://github.com/mafredri/go-trueskill).
Go
2
star
31

asustor-qbittorrent

qBittorrent for ASUSTOR ADM
Shell
2
star
32

kinesis-advantage2

Configuration for my Kinesis Advantage2
Shell
1
star
33

asustor-sonarr

Sonarr for ASUSTOR ADM
Shell
1
star
34

asustor-go

The Go Programming Language for ASUSTOR ADM
Shell
1
star
35

asustor-jackett

Jackett for ASUSTOR ADM
Shell
1
star
36

vyatta-dhcp-global-option

Add support for configuring dhclient global options on UniFi Security Gateway
Perl
1
star
37

asustor-mono-latest

An up-to-date version of Mono for ASUSTOR ADM
Shell
1
star
38

go-mathextra

Extra math functions for Go
Go
1
star
39

asustor-python-cec

Python
1
star
40

as-search

Search from ASUSTOR NAS using Searchligt
Python
1
star
41

mafredri.github.io

HTML
1
star
42

asustor-rtorrent

rTorrent for Asustor ADM
Shell
1
star
43

playgoround

My Golang playground (playGoRound)
Go
1
star