Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ For each download the release key is fetched by its fingerprint from keys.openpg
sh start.sh --stop
```

`--run` records the process id in `<jar name>.pid` (`FullNode.jar.pid` by default) next to `start.log`, and `--stop` reads it. Run `--stop` in the directory the node was started from, with the same `-j` name if one was given. After `--release` or `-cb` the node runs in `FullNode/`; `--stop` finds it there from the parent directory as well. A node started by an earlier version of the script (a `start.log` but no pid file) is still found by its jar name.
`--run` records the process id in `<jar name>.pid` (`FullNode.jar.pid` by default) next to `start.log`, and `--stop` reads it. Run `--stop` in the directory the node was started from, with the same `-j` name if one was given. After `--release` or `-cb` the node runs in `FullNode/`; `--stop` finds it there from the parent directory as well. `--stop` stops a process only when it runs the jar from that directory, reading the working directory from `/proc` on Linux or with `lsof` on macOS, so nodes with the same jar name in other directories keep running; a pid file whose process now runs from another directory counts as a stopped node. This isolation needs a readable working directory: if the working directory of the process in the pid file cannot be read, the recorded id is trusted as it is, so it must be known to belong to this node, for example when it is written into the pid file by hand. A node started by an earlier version of the script (a `start.log` but no pid file) is found by its jar name; if the working directory of such a process cannot be read, `--stop` exits with an error: write the node's process id to the pid file and run `--stop` again. If the node cannot be stopped, for example because its process belongs to another user, `--stop` exits with an error and `--run` does not start another node.

* Get the latest version of `FullNode.jar` and start it

Expand Down
103 changes: 88 additions & 15 deletions start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -401,26 +401,75 @@ matchNodePid() {
done
}

# Sets $pid to the process id recorded in <jar name>.pid; empty when that process is gone. A
# directory that has a start.log but no pid file was used by an earlier version of this script,
# which kept no pid file; there every process on the machine that runs the jar name is taken,
# as that version did, so $pid may hold several ids.
# Prints the working directory of process $1, or nothing when it cannot be read: from /proc on
# Linux and with lsof elsewhere (macOS). For another user's process both work only as root.
processDir() {
if [ -d /proc/self ]; then
readlink "/proc/$1/cwd" 2>/dev/null
else
lsof -a -p "$1" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p'
fi
}

# Prints the process ids on stdin whose working directory is the current directory. This script
# and its earlier versions start the node from its directory, and the JVM keeps that working
# directory, so this tells the node started here from nodes in other directories that run the
# same jar name. A process whose working directory cannot be read may be the node started here:
# if this user can signal it, it is reported on stderr and the function returns 1. One that this
# user cannot signal (another user's process, or one already gone) is left out.
matchDirPid() {
local here candidate dir unread=''
here=$(pwd -P)
while read -r candidate; do
dir=$(processDir "$candidate")
if [ "$dir" = "$here" ]; then
echo "$candidate"
elif [ -z "$dir" ]; then
if kill -0 "$candidate" 2>/dev/null; then
unread="$unread $candidate"
elif ps -p "$candidate" >/dev/null 2>&1; then
warn "process $candidate runs $JAR_NAME but cannot be inspected or signalled by this user, leaving it running" >&2
fi
fi
done
if [ -n "$unread" ]; then
error "cannot read the working directory of process$unread running $JAR_NAME, so the node started from $PWD cannot be told apart; write its process id to $PWD/${JAR_NAME##*/}.pid and run again"
return 1
fi
return 0
}

# Sets $pid to the process id recorded in <jar name>.pid; empty when that process is gone or runs
# from another directory. A directory that has a start.log but no pid file was used by an earlier
# version of this script, which kept no pid file; there $pid holds the processes that run the jar
# name from this directory, and the script exits when one of them cannot be told apart.
checkPid() {
local pidFile="${JAR_NAME##*/}.pid"
local saved
local saved dir
pid=''
if [ -f "$pidFile" ]; then
# Only the recorded process counts, and only while it still runs the jar. Once it is found
# gone the file is emptied, so its id is not looked up again.
# Only the recorded process counts, and only while it still runs the jar from this directory.
# Once it is found gone the file is emptied, so its id is not looked up again. A working
# directory read as another directory means the id now belongs to another node; one that
# cannot be read proves nothing, so the recorded process is kept.
saved=$(cat "$pidFile" 2>/dev/null)
if [ -n "$saved" ]; then
pid=$(ps -o pid=,args= -p "$saved" 2>/dev/null | matchNodePid)
if [ -n "$pid" ]; then
dir=$(processDir "$pid")
if [ -n "$dir" ] && [ "$dir" != "$(pwd -P)" ]; then
warn "process $pid in $pidFile now runs from $dir, the node started from $PWD has stopped" >&2
pid=''
fi
fi
if [ -z "$pid" ]; then
: > "$pidFile"
fi
fi
elif [ -f start.log ]; then
pid=$(ps -A -o pid=,args= 2>/dev/null | matchNodePid)
if ! pid=$(ps -A -o pid=,args= 2>/dev/null | matchNodePid | matchDirPid); then
exit 1
fi
fi
}

Expand All @@ -435,9 +484,11 @@ enterNodeDir() {
}

# Stops the running node: sends SIGTERM once per second for up to MAX_STOP_TIME seconds,
# then SIGKILL. Returns 1 when no node was ever started from this directory.
# then SIGKILL. Returns 1 when no node was ever started from this directory, and 2 when the node
# cannot be stopped: this user cannot signal one of its processes, or it still runs after SIGKILL.
stopService() {
local pidFile="${JAR_NAME##*/}.pid"
local p denied
enterNodeDir
if [ ! -f "$pidFile" ] && [ ! -f start.log ]; then
info "no node was started from $PWD"
Expand All @@ -446,20 +497,38 @@ stopService() {
count=1
while [ $count -le $MAX_STOP_TIME ]; do
checkPid
if [ -n "$pid" ]; then
kill -15 $pid
sleep 1
else
if [ -z "$pid" ]; then
info "java-tron stop"
return
return 0
fi
# A failed kill counts only while the process still exists: it may have exited after
# checkPid found it.
denied=''
for p in $pid; do
if ! kill -15 "$p" 2>/dev/null && ps -p "$p" > /dev/null 2>&1; then
denied="$denied $p"
fi
done
if [ -n "$denied" ]; then
error "cannot signal process$denied of the node started from $PWD, stop it as its owner"
return 2
fi
sleep 1
count=$(($count + 1))
if [ $count -eq $MAX_STOP_TIME ]; then
kill -9 $pid
for p in $pid; do
kill -9 "$p" 2>/dev/null
done
sleep 1
fi
done
sleep 5
checkPid
if [ -n "$pid" ]; then
error "java-tron still runs with pid ${pid//$'\n'/ } after SIGKILL"
return 2
fi
info "java-tron stop"
}

# Linux only: refuses to start with less than ALLOW_MIN_MEMORY MB of memory, or when -mem
Expand Down Expand Up @@ -742,7 +811,11 @@ restart() {
# running, so its download and key-server lookup do not add to the stop-to-start downtime.
enterNodeDir
ensureArchiveJar
# 1 only means that no node was started from this directory before, so this is its first start.
stopService
if [ $? -eq 2 ]; then
exit 1
fi
if [[ $IS_BACKUP_GC_LOG = true ]]; then
backupGCLog
fi
Expand Down
Loading