Chuyển tiếp SIGTERM cho trẻ em ở Bash


86

Tôi có một đoạn script Bash, trông giống như thế này:

#!/bin/bash
echo "Doing some initial work....";
/bin/start/main/server --nodaemon

Bây giờ, nếu bash shell chạy tập lệnh nhận được tín hiệu SIGTERM, thì nó cũng sẽ gửi SIGTERM đến máy chủ đang chạy (chặn, vì vậy không thể bẫy được). Điều đó có thể không?

Câu trả lời:


91

Thử:

#!/bin/bash 

_term() { 
  echo "Caught SIGTERM signal!" 
  kill -TERM "$child" 2>/dev/null
}

trap _term SIGTERM

echo "Doing some initial work...";
/bin/start/main/server --nodaemon &

child=$! 
wait "$child"

Thông thường, bashsẽ bỏ qua mọi tín hiệu trong khi một tiến trình con đang thực thi. Bắt đầu từ các máy chủ với &ý chí nền nó vào hệ thống điều khiển công việc của vỏ, với $!tổ chức PID của máy chủ (được sử dụng với waitkill). waitSau đó, việc gọi sẽ chờ công việc với PID (máy chủ) được chỉ định kết thúc hoặc cho bất kỳ tín hiệu nào được kích hoạt .

Khi shell nhận được SIGTERM(hoặc máy chủ thoát độc lập), waitcuộc gọi sẽ trở lại (thoát với mã thoát của máy chủ hoặc với số tín hiệu + 128 trong trường hợp nhận được tín hiệu). Sau đó, nếu shell nhận được SIGTERM, nó sẽ gọi _termhàm được chỉ định là trình xử lý bẫy SIGTERM trước khi thoát (trong đó chúng tôi thực hiện bất kỳ việc dọn dẹp nào và truyền tín hiệu thủ công đến quy trình máy chủ bằng cách sử dụng kill).


Có vẻ tốt! Tôi sẽ thử nó và trả lời khi tôi kiểm tra nó.
Lorenz

7
Nhưng exec thay thế shell bằng chương trình đã cho , tôi không rõ tại sao waitcuộc gọi tiếp theo lại cần thiết?
iruvar

5
Tôi nghĩ rằng điểm 1_CR là hợp lệ. Hoặc bạn chỉ đơn giản sử dụng exec /bin/start/main/server --nodaemon(trong trường hợp quy trình shell được thay thế bằng quy trình máy chủ và bạn không cần truyền bất kỳ tín hiệu nào) hoặc bạn sử dụng /bin/start/main/server --nodaemon &, nhưng sau đó execkhông thực sự có ý nghĩa.
Andreas Veithen

2
Nếu bạn muốn tập lệnh shell của bạn kết thúc chỉ sau khi con bị chấm dứt, thì trong _term()hàm bạn nên thực hiện wait "$child"lại. Điều này có thể cần thiết nếu bạn có một số quy trình giám sát khác đang chờ tập lệnh shell chết trước khi khởi động lại hoặc nếu bạn cũng bị mắc kẹt EXITđể dọn dẹp và chỉ chạy nó sau khi quá trình con kết thúc.
LeoRochael

1
@AlexanderMills Đọc các câu trả lời khác. Hoặc bạn đang tìm kiếm exec, hoặc bạn muốn thiết lập bẫy .
Stuart P. Bentley

78

Bash không chuyển tiếp các tín hiệu như SIGTERM cho các quy trình mà nó hiện đang chờ. Nếu bạn muốn kết thúc tập lệnh của mình bằng cách tách biệt với máy chủ của mình (cho phép nó xử lý tín hiệu và mọi thứ khác, như thể bạn đã khởi động máy chủ trực tiếp), bạn nên sử dụng exec, nó sẽ thay thế trình bao bằng quy trình được mở :

#!/bin/bash
echo "Doing some initial work....";
exec /bin/start/main/server --nodaemon

Nếu bạn cần phải giữ cho lớp vỏ bao quanh đối với một số lý do (ví dụ. Bạn cần làm một số ngẫu nhiên sau khi máy chủ kết thúc), bạn nên sử dụng một sự kết hợp của trap, waitkill. Xem câu trả lời của SensorSmith .


Đây là câu trả lời chính xác! Vì vậy, ngắn gọn hơn nhiều và giải quyết chính xác yêu cầu ban đầu của OP
BrDaHa

20

Andreas Veithen chỉ ra rằng nếu bạn không cần phải quay lại từ cuộc gọi (như trong ví dụ của OP) chỉ cần gọi qua execlệnh là đủ ( câu trả lời của @Stuart P. Bentley ). trap 'kill $CHILDPID' TERMMặt khác, "truyền thống" (câu trả lời của @ cuonglm) là một sự khởi đầu, nhưng waitcuộc gọi thực sự trở lại sau khi trình xử lý bẫy chạy vẫn có thể trước khi tiến trình con thực sự thoát. Vì vậy, một cuộc gọi "thêm" waitđược khuyến khích ( câu trả lời của @ user1463361 ).

Mặc dù đây là một cải tiến nhưng nó vẫn có một điều kiện cuộc đua, điều đó có nghĩa là quá trình có thể không bao giờ thoát ra (trừ khi người báo hiệu thử lại gửi tín hiệu TERM). Cửa sổ của lỗ hổng là giữa việc đăng ký trình xử lý bẫy và ghi lại PID trẻ em.

Sau đây loại bỏ lỗ hổng đó (được đóng gói trong các chức năng để tái sử dụng).

prep_term()
{
    unset term_child_pid
    unset term_kill_needed
    trap 'handle_term' TERM INT
}

handle_term()
{
    if [ "${term_child_pid}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null
    else
        term_kill_needed="yes"
    fi
}

wait_term()
{
    term_child_pid=$!
    if [ "${term_kill_needed}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null 
    fi
    wait ${term_child_pid}
    trap - TERM INT
    wait ${term_child_pid}
}

# EXAMPLE USAGE
prep_term
/bin/something &
wait_term

2
Tuyệt vời công việc - Tôi đã cập nhật vào liên kết trong câu trả lời của tôi chỉ ở đây (trên đỉnh này là một giải pháp toàn diện hơn, tôi vẫn còn một chút khó chịu rằng StackExchange UI không ghi có tôi trong câu trả lời của cuonglm cho sửa chữa kịch bản để thực sự làm những gì nó được yêu cầuviết khá nhiều tất cả các văn bản giải thích sau khi OP thậm chí không hiểu đã thực hiện một vài chỉnh sửa nhỏ).
Stuart P. Bentley

2
@ StuartP.Bentley, cảm ơn. Tôi đã rất ngạc nhiên khi lắp ráp hai câu trả lời (không được chấp nhận) này và một tài liệu tham khảo bên ngoài, và sau đó tôi phải chạy xuống điều kiện cuộc đua. Tôi sẽ nâng cấp các tài liệu tham khảo của mình lên các liên kết như những gì tôi có thể cung cấp thêm.
SensorSmith

3

Cung cấp giải pháp không hoạt động cho tôi vì quá trình đã bị hủy trước khi lệnh chờ thực sự kết thúc. Tôi thấy bài viết đó http://veithen.github.io/2014/11/16/sigterm-propagation.html , đoạn trích cuối hoạt động tốt trong trường hợp ứng dụng của tôi bắt đầu trong OpenShift với trình chạy sh tùy chỉnh. Kịch bản sh là bắt buộc vì tôi cần có khả năng để có được các kết xuất luồng, điều này là không thể trong trường hợp PID của quy trình Java là 1.

trap 'kill -TERM $PID' TERM INT
$JAVA_EXECUTABLE $JAVA_ARGS &
PID=$!
wait $PID
trap - TERM INT
wait $PID
EXIT_STATUS=$?
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.