-
Notifications
You must be signed in to change notification settings - Fork 5
/
bq
executable file
·192 lines (151 loc) · 5.86 KB
/
bq
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#!/bin/bash
# simple task queue; output files are in /dev/shm/bq-$USER. Uses no locks;
# use 'mv' command, which is atomic (within the same file system anyway) to
# prevent contention.
# ref: https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html
# run "bq -w" once to start a worker
# run "bq command [args...]" to put tasks in queue
# run "bq" to view the output directory using vifm, unless $BQ_FILEMANAGER is set
# see bq.mkd for more (e.g., using different queues, increasing/decreasing the
# number of workers in a queue, etc.)
# ----------------------------------------------------------------------
die() { echo "$@" >&2; exit 1; }
[ "$1" = "-h" ] && {
cat <<-EOF
Example usage:
# start a worker
bq -w
# submit a job
bq some-command arg1 arg2 [...]
# check status
bq # uses vifm as the file manager
export BQ_FILEMANAGER=mc; bq # env var overrides default
# you can only run one simple command; if you have a command with
# shell meta characters (;, &, &&, ||, >, <, etc), do this:
bq bash -c 'echo hello; echo there >junk.\$RANDOM'
EOF
exit 1
}
# ----------------------------------------------------------------------
# SETUP
TMP=/dev/shm
[ -d $TMP ] || TMP=/tmp
# I doubt I will ever use multiple Qs, but it's easy enough to implement
Q=default
[ "$1" = "-q" ] && {
[ -z "$2" ] && die "-q needs a queue name"
Q=$2; shift; shift
}
[ -z "$QDIR" ] && export QDIR=$TMP/bq-$USER-$Q
mkdir -p $QDIR/w
mkdir -p $QDIR/q
mkdir -p $QDIR/OK
# ----------------------------------------------------------------------
# WORK 1 TASK
_work_1() {
ID=$1
# "claim" the task in q by renaming q/$ID to a name that contains our own PID
mv q/$ID $ID.running.$$ 2>/dev/null
# if the "claim" succeeded, we won the race to run the task
if [ -f $ID.running.$$ ]
then
# get the command line arguments and run them
readarray -t cmd < $ID.running.$$
# the first line is the directory to be in; shift that out first
newpwd="${cmd[0]}"
cmd=("${cmd[@]:1}")
# log the command for debugging later
echo -n "cd $newpwd; ${cmd[@]}" >> w/$$
# the directory may have disappeared between submitting the
# job and running it now. Catch that by trying to cd to it
cd "$newpwd" || cmd=(cd "$newpwd")
# if the cd failed, we simply replace the actual command with
# the same "cd", and let it run and catch the error. Bit of a
# subterfuge, actually, but it works fine.
# finally we run the task. Note that our PWD now is NOT $QDIR, so
# the two redirected filenames have to be fully qualified
"${cmd[@]}" > $QDIR/$ID.1 2> $QDIR/$ID.2
ec=$?
cd $QDIR
mv $ID.running.$$ $ID.exitcode=$ec
[ "$ec" = "0" ] && mv $ID.* OK
echo " # $ec" >> w/$$
if command -v notify-send &> /dev/null; then
notify-send "`wc -l w/$$`" "`tail -1 w/$$`"
fi
fi
}
# ----------------------------------------------------------------------
# START AND DAEMONISE A WORKER
# '-w' starts a worker; each worker runs one job at a time, so if you want
# more jobs to run simultaneously, run this multiple times!
[ "$1" = "-w" ] && [ -z "$2" ] && {
# if the user is starting a worker, any existing kill commands don't apply
rm -f $QDIR/q/0.*.-k
# daemonize
nohup "$0" -w $QDIR &
# remind the user how many workers he has started, in case he forgot
sleep 0.5 # wait for the other task to kick off
echo `cd $QDIR/w; ls | grep -v exited | wc -l` workers running
exit 0
}
# ----------------------------------------------------------------------
# STOP A WORKER
[ "$1" = "-k" ] && [ -z "$2" ] && {
touch $QDIR/q/0.$$.-k
# starting with a "0" assures that in an "ls" this file will come before
# any normal task files (which all start with `date +%s`). The contents
# don't matter, since it won't be "executed" in the normal manner.
exit 0
}
# ----------------------------------------------------------------------
# WORKER LOOP
[ "$1" = "-w" ] && {
touch $QDIR/w/$$
while :
do
cd $QDIR
ID=`cd q; ls | head -1`
# if nothing is waiting in q, go to sleep, but use inotifywait so you
# get woken up immediately if a new task lands
[ -z "$ID" ] && {
inotifywait -q -t 60 -e create q >/dev/null
continue
# whether we got an event or just timed out, we just go back round
}
# note there is still a bit of a race here. If tasks were submitted
# *between* the "ID=" and the "inotifywait" above, they will end up
# waiting 60 seconds before they get picked up. Hopefully that's a
# corner case, and anyway at worst it only causes a delay.
# handle exit, again using the "mv is atomic" principle
[[ $ID == 0.*.-k ]] && {
mv q/$ID $ID.exiting.$$
[ -f $ID.exiting.$$ ] && {
mv w/$$ w/$$.exited
rm $ID.exiting.$$
exit 0
}
}
# ok there was at least one task waiting; try to "work" it
_work_1 $ID
done
# we should never get here
touch $QDIR/worker.$$.unexpected-error
}
# ----------------------------------------------------------------------
# STATUS
# examine the output directory using $BQ_FILEMANAGER (defaulting to vifm)
[ -z "$1" ] && exec sh -c "${BQ_FILEMANAGER:-vifm} $QDIR"
# ----------------------------------------------------------------------
# some command was given; add it to the queue
# check for a task label via `bq -L label cmd ...`
if [ "$1" == "-L" ]; then
[ -z "$3" ] && die "-L needs a task label"
LABEL=$2; shift; shift
else
LABEL=$1
fi
ID=`date +%s`.$$.${LABEL//[^a-z0-9_.-]/}
pwd > $QDIR/q/$ID
printf "%s\n" "$@" >> $QDIR/q/$ID
echo "$ID"