source

wait3(waitpid alias)는 ECHILD로 설정된 오류가 없는 -1을 반환합니다.

ittop 2023. 10. 24. 21:33
반응형

wait3(waitpid alias)는 ECHILD로 설정된 오류가 없는 -1을 반환합니다.

상황은 이 Redis 이슈입니다.저희가.wait3()디스크에 새 AOF 버전을 생성할 때까지 AOF 다시 쓰기 하위를 기다리는 호출.아이가 끝나면 부모에게 다음을 통해 알립니다.wait3()기존 AOF를 새 AOF로 대체하기 위해서입니다.

그러나 위 문제와 관련하여 사용자가 버그에 대해 알려주었습니다.나는 언제를 명확하게 기록하기 위해 Redis 3.0의 구현을 조금 수정했습니다.wait3()이 예기치 않은 상태 때문에 충돌 대신 -1을 반환했습니다.그래서 이런 일이 발생합니다.

  1. wait3()대기 중인 아이들이 있을 때 호출됩니다
  2. SIGCHLD로 설정해야 합니다.SIG_DFL, Redis에는 이 신호를 설정하는 코드가 전혀 없으므로 기본 동작입니다.
  3. 첫번째 AOF 재작성이 발생하면,wait3()예상대로 성공적으로 작동합니다.
  4. 두 번째 AOF 재작성(두 번째 자식 생성)부터 시작합니다.wait3()는 -1을 반환하기 시작합니다.

AFAIK 현재 우리가 부르는 코드에서는 불가능합니다.wait3()보류 중인 자식이 없는 동안, AOF 자식이 생성될 때부터 우리는server.aof_child_pidpid 값으로, 그리고 우리는 성공한 후에야 그것을 재설정했습니다.wait3()러.

그렇게wait3()-1과 함께 실패할 이유가 없어야 합니다.ECHILD, 하지만, 그래서 좀비 아이는 아마도 어떤 예기치 않은 이유로 만들어지지 않았을 것입니다.

가설 1: 특정 홀수 조건 동안 리눅스가 메모리 압력 때문에 좀비 자식을 버릴 가능성이 있습니까?좀비는 메타데이터만 첨부되어 있지만 누가 알겠습니까?

전화를 드리는 것을 참고합니다.wait3()와 함께WNOHANG. 그런 점을 감안하면SIGCHLD로 설정됩니다.SIG_DFL기본적으로, 실패 및 반환으로 이어져야 하는 유일한 조건 -1 및ECHLD좀비가 정보를 보고할 수 없어야 합니다.

가설 2: 발생할 수 있는 다른 일이지만 발생할 경우 설명할 수 없는 것은 첫째 아이가 죽은 후에SIGCHLD핸들러가 다음으로 설정됩니다.SIG_IGN,발생시키는wait3()과 -1및 -1을 합니다.ECHLD.

가설 3: 좀비 아이들을 외부에서 제거할 수 있는 방법은?아마도 이 사용자는 백그라운드에서 좀비 프로세스를 제거하여 정보를 더 이상 사용할 수 없도록 하는 일종의 스크립트를 가지고 있을 것입니다.wait3()? 내가 아는 한, 부모가 좀비를 기다리지 않으면 절대로 좀비를 제거할 수 없습니다.waitpid(또는 신호 처리) 및 만약SIGCHLD무시되는 것은 아니지만 아마도 리눅스 특유의 방법이 있을 것입니다.

가설 4: 실제로 Redis 코드에 버그가 있어서 성공적입니다.wait3()처음에는 상태를 올바르게 재설정하지 않고 나중에는 전화를 걸죠wait3()몇 번이고 하지만 좀비가 없어져서 -1로 돌아옵니다.코드를 분석해보면 불가능해 보이지만, 제가 틀렸을 수도 있습니다.

또 다른 중요한 것은 우리는 과거에 이것을 관찰한 적이 없다는 것입니다.이 특정 Linux 시스템에서만 발생합니다.

업데이트: Yossi Gottlieb은 다음과 같이 제안했습니다.SIGCHLD어떠한 이유로 인해 Redis 프로세스의 다른 스레드에서 수신됩니다(정상적으로 발생하지 않음, 이 시스템에서만).우리는 이미 가면을 썼습니다.SIGALRM인에bio.c실, 어쩌면 우리는 마스크를 시도해 볼 수 있을지도 모릅니다.SIGCHLDI/O 쓰레드에서도 사용할 수 있습니다.

부록: Rediscode에서 선택한 부분

wait3()이 호출되는 위치:

/* Check if a background saving or AOF rewrite in progress terminated. */
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {
    int statloc;
    pid_t pid;

    if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
        int exitcode = WEXITSTATUS(statloc);
        int bysignal = 0;

        if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);

        if (pid == -1) {
            redisLog(LOG_WARNING,"wait3() returned an error: %s. "
                "rdb_child_pid = %d, aof_child_pid = %d",
                strerror(errno),
                (int) server.rdb_child_pid,
                (int) server.aof_child_pid);
        } else if (pid == server.rdb_child_pid) {
            backgroundSaveDoneHandler(exitcode,bysignal);
        } else if (pid == server.aof_child_pid) {
            backgroundRewriteDoneHandler(exitcode,bysignal);
        } else {
            redisLog(REDIS_WARNING,
                "Warning, detected child with unmatched pid: %ld",
                (long)pid);
        }
        updateDictResizePolicy();
    }
} else {

선택한 부분backgroundRewriteDoneHandler:

void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
    if (!bysignal && exitcode == 0) {
        int newfd, oldfd;
        char tmpfile[256];
        long long now = ustime();
        mstime_t latency;

        redisLog(REDIS_NOTICE,
            "Background AOF rewrite terminated with success");

        ... more code to handle the rewrite, never calls return ...

    } else if (!bysignal && exitcode != 0) {
        server.aof_lastbgrewrite_status = REDIS_ERR;

        redisLog(REDIS_WARNING,
            "Background AOF rewrite terminated with error");
    } else {
        server.aof_lastbgrewrite_status = REDIS_ERR;

        redisLog(REDIS_WARNING,
            "Background AOF rewrite terminated by signal %d", bysignal);
    }

cleanup:
    aofClosePipes();
    aofRewriteBufferReset();
    aofRemoveTempFile(server.aof_child_pid);
    server.aof_child_pid = -1;
    server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start;
    server.aof_rewrite_time_start = -1;
    /* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */
    if (server.aof_state == REDIS_AOF_WAIT_REWRITE)
        server.aof_rewrite_scheduled = 1;
}

보시다시피 모든 코드 경로가 실행되어야 합니다.cleanup재설정한 코드server.aof_child_pid1 이동합니다.

문제가 발생하는 동안 Redis에서 기록한 오류

21353:C 11월 29일 04:00:29957 * AOF rewrite: 카피 온 라이트(copy-on-write)에서 사용되는 8MB의 메모리

27848:M 29 11월 04:00:30.133 ^@ wait3()에서 오류를 반환했습니다.자식 프로세스가 없습니다.rdb_child_pid = -1, aof_child_pid = 21353

당신이 볼 수 있듯이.aof_child_pid-1이 아닙니다.

TLDR: 현재 지정되지 않은 동작에 의존하고 있습니다.signal (2); sigaction(carefully) 대신에

SIGCHLD이상합니다.사용 설명서 페이지에서 다음에 대해sigaction;

POSIX.1-1990에 대한 작업 설정이 허용되지 않음SIGCHLD.SIG_IGN. POSIX.1-2001은 이러한 가능성을 허용하여 무시합니다.SIGCHLD좀비의 생성을 방지하기 위해 사용될 수 있습니다(참조).wait에도 불구하고 및 V (2 그럼에도 불구하고, 과거 BSD 및 시스템 V의 무시 행위SIGCHLD차이가 있기 때문에, 종료된 아이들이 좀비가 되지 않도록 보장하는 유일하게 완전하게 휴대할 수 있는 방법은 잡는 것입니다.SIGCHLD신호를 보내고 수행합니다.wait(2) 또는 유사한.

그리고 여기서 한가지.wait(2)의 수동 페이지:

POSIX.1-2001은 다음과 같이 규정하고 있습니다.SIGCHLD로 설정됩니다.SIG_IGN아니면SA_NOCLDWAIT플래그가 설정되어 있습니다.SIGCHLD조) )sigaction(2)), 그러면 종료되는 아이들은 좀비가 되지 않고 전화를 합니다.wait()아니면waitpid()모든 자식이 종료될 때까지 차단한 다음 오류를 다음으로 설정하여 실패합니다.ECHILD. (원래 POSIX 표준은 설정 동작을 남겼습니다.SIGCHLD.SIG_IGN불특정의주의할 점은 다음과 같은 기본적인 처분이SIGCHLD는 "ignore"이며, 명시적으로 다음과 같이 처분을 설정합니다.SIG_IGN결과적으로 좀비 프로세스 아동에 대한 다양한 대우를 초래합니다.)리눅스 2.6은 이 규격에 부합합니다.그러나 리눅스 2.4(이전 버전)는 다음과 같이 하지 않습니다.wait()아니면waitpid()통화중입니다.SIGCHLD무시되고 있고, 통화는 마치 그가 자신의 전화를 건 것처럼 행동합니다.SIGCHLD무시되지 않았습니다. 즉, 다음 자식이 종료된 후 해당 자식의 프로세스 ID와 상태를 반환할 때까지 호출이 차단됩니다.

그 효과는 신호의 처리가 다음과 같이 작동하는 경우에 발생합니다.SIG_IGN가 설정되어 있으면 (리눅스 2.6+ 아래에서) 사용자가 보고 있는 동작을 볼 수 있습니다.wait()돌아올 것입니다-1그리고.ECHLD아이가 자동으로 거둬들일 것이기 때문입니다.

두 번째로, 신호 처리는pthreads(당신이 여기서 쓰고 있는 것 같아요) 그건 어려운 걸로 악명이 높습니다.이것이 작동하는 방식은 (여러분도 아시다시피) 프로세스 지향 신호가 마스킹되지 않은 프로세스 내의 임의의 스레드로 전송되는 것입니다.그러나 스레드에는 자체 신호 마스크가 있지만 프로세스 전반에 걸친 액션 핸들러가 있습니다.

이 두 가지를 종합해보면, 제가 전에 마주쳤던 문제를 우연히 마주친 것 같습니다.문제가 있었습니다.SIGCHLD작업 처리signal()(이는 pthreads 이전에 감가상각되었기 때문에 충분히 공정합니다)로 이동함으로써 수정되었습니다.sigaction나사산 신호 마스크를 주의 깊게 설정합니다.그때 내 결론은 C 도서관이 에뮬레이션을 하고 있다는 것이었습니다.sigaction) 제가 말씀드리던 것은signal(), 하지만 그들에게 걸려 넘어지고 있었습니다.pthreads.

현재 지정되지 않은 동작에 의존하고 있습니다.수동 페이지에서signal(2):

의 .signal()다중 스레드 프로세스에서는 지정되지 않습니다.

다음은 여러분께 권장하는 것이 좋습니다.

  1. 이동 위치sigaction()그리고.pthread_sigmask(). 사용자가 신경 쓰는 모든 신호의 처리를 명시적으로 설정합니다(현재 기본값이라고 생각하더라도).SIG_IGN아니면SIG_DFL 심할 이 작업을 수행하는 동안 신호를 차단합니다(주의가 지나칠 수 있지만 어디선가 예제를 복사했습니다).

제가 하고 있는 일은 이렇습니다.

sigset_t set;
struct sigaction sa;

/* block all signals */
sigfillset (&set);
pthread_sigmask (SIG_BLOCK, &set, NULL);

/* Set up the structure to specify the new action. */
memset (&sa, 0, sizeof (struct sigaction));
sa.sa_handler = handlesignal;        /* signal handler for INT, TERM, HUP, USR1, USR2 */
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGINT, &sa, NULL);
sigaction (SIGTERM, &sa, NULL);
sigaction (SIGHUP, &sa, NULL);
sigaction (SIGUSR1, &sa, NULL);
sigaction (SIGUSR2, &sa, NULL);

sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);     /* I don't care about SIGPIPE */

sa.sa_handler = SIG_DFL;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGCHLD, &sa, NULL);     /* I want SIGCHLD to be handled by SIG_DFL */

pthread_sigmask (SIG_UNBLOCK, &set, NULL);
  1. 가능한 경우 모든 신호 처리기 및 마스크 등을 먼저 설정합니다.pthread작전.가능한 경우 신호 처리기 및 마스크를 변경하지 마십시오(이전 및 이후에 이 작업을 수행해야 할 수 있습니다).fork()부름).

  2. 신호 처리기가 필요한 경우SIGCHLD(믿기 보다는)SIG_DFL), 가능한 경우 스레드에서 수신하게 하고 자체 파이프 방식 또는 유사한 방식을 사용하여 메인 프로그램에 알립니다.

  3. 특정 신호를 처리하지 않는 스레드가 있어야 하는 경우 다음으로 제한합니다.pthread_sigmask보다 관련된 분야에서sig*

  4. 혹시라도 내가 마주친 다음 문제에 정면으로 부딪힐 경우를 대비해서, 당신이 그 다음에fork()'d, 부모 프로세스에서 물려받을 수 있는 모든 것에 의존하지 않고 처음부터 (자녀의) 신호 처리를 다시 설정합니다.pthread와 혼합된 신호보다 더 나쁜 것이 있다면 pthread와 혼합된 신호입니다.fork().

참고로 (1) 변화가 작동하는 이유를 완전히 설명할 수는 없지만, 저에게 매우 유사한 문제로 보이는 것을 수정했고 결국 이전에 '특정되지 않은' 것에 의존하고 있었습니다.그것은 당신의 '가설 2'에 가장 가깝지만, 나는 그것이 레거시 신호 함수의 완전하지 않은 에뮬레이션이라고 생각합니다(특히 이전의 희귀한 행동을 에뮬레이션합니다).signal()그것이 그것을 대체하게 한 원인입니다.sigaction()처음부터 - 하지만 이것은 단지 추측일 뿐입니다).

덧붙여서, 제가 제안하는 것은wait4()또는 (사용하지 않는 경우)rusage)waitpid()보다는wait3(), 대기할 특정 PID를 지정할 수 있습니다.아이들을 낳게 하는 다른 무언가가 있다면 (도서관에서 하게 한 적이 있습니다), 잘못된 일을 기다리게 될지도 모릅니다.그렇다고 여기서 그런 일이 벌어지고 있는 것 같지는 않습니다.

언급URL : https://stackoverflow.com/questions/33994543/wait3-waitpid-alias-returns-1-with-errno-set-to-echild-when-it-should-not

반응형