다음 스크립트를 실행하는 이유를 이해할 수 없습니다.
$ cat z.sh
saved_stty=$(stty -g)
echo "saved_stty: ${saved_stty}"
stty "${saved_stty}"
$ ./z.sh
saved_stty: 500:5:bf:8a3b:3:1c:7f:15:4:0:1:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0
$ echo "${?}"
0
시간 초과로 stty를 호출한 후 변경 사항:
$ cat z.sh
saved_stty=$(stty -g)
echo "saved_stty: ${saved_stty}"
timeout 10 stty "${saved_stty}"
$ ./z.sh
saved_stty: 500:5:bf:8a3b:3:1c:7f:15:4:0:1:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0
$ echo "${?}"
124
$ . ./z.sh
saved_stty: 500:5:bf:8a3b:3:1c:7f:15:4:0:1:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0
$ echo "${?}"
0
첫 번째 경우에는 호출이 ./z.sh
즉시 완료되지만(반환 값 0), 두 번째 경우에는 호출에 ./z.sh
10초가 걸립니다(반환 값 124로 시간 초과됨). 그러나 호출은 . ./z.sh
여전히 즉시 완료됩니다(반환 값 0).
stty를 디버깅하여 시스템 호출 시 실행이 중단되는 것을 관찰했습니다.
return INLINE_SYSCALL (ioctl, 3, fd, cmd, &k_termios);
int __tcsetattr (int fd, int optional_actions, const struct termios *termios_p)
함수에서 glibc/sysdeps/unix/sysv/linux/tcsetattr.c
.
문제의 최소화된 예를 보여 드렸습니다. 이제 좀 더 현실적으로 말해보겠습니다.
$ cat z.scala
object Test {
def main(args: Array[String]): Unit = {
var j = 0
val k = 1000
for (i <- 1 to k) {
for (i <- 1 to k) {
j += i
}
}
println(j)
}
}
$ cat run.sh
timeout 10 scala ./z.scala
echo "${?}"
$ ./run.sh
500500000
124
$ cat z.scala
object Test {
def main(args: Array[String]): Unit = {
var j = 0
val k = 1000000
for (i <- 1 to k) {
for (i <- 1 to k) {
j += i
}
}
println(j)
}
}
$ cat run.sh
timeout --foreground 10 scala ./z.scala
echo "${?}"
$ ./run.sh
124
scala에 대한 첫 번째 호출은 결과를 즉시 계산하지만 시간이 초과될 때까지 stty에 대한 내부 호출을 중단합니다. scala에 대한 두 번째 호출은 시간 초과로 종료되어야 하지만 scala를 실행하는 프로세스는 스크립트가 반환된 후에도 계속 실행 중입니다. 이는 문서와 일치 timeout --foreground
하지만 Scala에서 시간 초과를 올바르게 처리하는 방법을 알고 싶습니다.
답변1
timeout
이는 명령이 별도의 프로세스 그룹에서 백그라운드로 실행되기 때문에 발생합니다 .
a) 프로세스가 제어 터미널에 연결되어 있고 b) 포그라운드 그룹에 있지 않고 터미널 설정 변경을 사용하려고 하면 이를 중지하는 신호를 tcsetattr()
받습니다 .SIGTTOU
이것이 바로 귀하의 예에서 일어나는 일입니다.
GNU에는 제어 터미널을 방해하려는 간단한 프로그램과 함께 안전하게 사용할 수 있는 timeout
옵션이 있습니다.--foreground
나누지 마세요, 왜냐하면 그러면 그들의 아이들은 죽지 않을 것이기 때문입니다.
가능한 해결책은
ㅏ)프로그램의 stdin/stdout/stderr를 다른 곳에서 리디렉션하십시오. /dev/tty
명시적으로 열지 말고 제어 터미널만 그대로 두시기 바랍니다.
비)그들이 a)로 확신할 수 없다면 그들에게 실행할 수 있는 의사 터미널을 제공하십시오 . 이 터미널은 stdin에서 읽기를 시도하기 때문에 script(1)
a) handler를 적용해야 합니다. script(1)
신호 SIGTTIN
), 이를 원시 데이터 tcseattr()
(신호를 얻음 SIGTTOU
)로 변환하려고 시도합니다.
% cat <<'EOT' > sample.sh; chmod +x sample.sh
#!/bin/sh
t=$(stty -g -F /dev/tty)
sleep 1000 &
echo BEFORE; stty -F /dev/tty "$t"; echo AFTER
EOT
% sh -c 'timeout 2 ./sample.sh'
BEFORE
# hangs for 2 seconds and exits without writing 'AFTER'
%
% sh -c 'timeout 2 script /dev/null </dev/null -qc ./sample.sh'
BEFORE
AFTER
# and it exits immediately
% pgrep sleep
# nothing, the child was killed too
이는 script(1)
표준화되지 않았으며 Linux 이외의 시스템에서는 해당 구문이 다르므로 이 예를 적용해야 합니다.
씨)systemd가 있는 경우 systemd-run -t --user
which ( 와 달리 timeout
)를 사용하면 이 명령으로 생성된 하위 프로세스가 프로세스 그룹이나 세션을 탈출하려고 시도하더라도 이를 잡아서 종료할 수 있습니다.