summaryrefslogtreecommitdiffstats
path: root/chromium/base/process/kill_mac.cc
blob: 5ebca5d00762329f5a163a9c8e2878df748f50f6 (plain)
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
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/process/kill.h"

#include <signal.h>
#include <sys/event.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "base/file_util.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"

namespace base {

namespace {

const int kWaitBeforeKillSeconds = 2;

// Reap |child| process. This call blocks until completion.
void BlockingReap(pid_t child) {
  const pid_t result = HANDLE_EINTR(waitpid(child, NULL, 0));
  if (result == -1) {
    DPLOG(ERROR) << "waitpid(" << child << ", NULL, 0)";
  }
}

// Waits for |timeout| seconds for the given |child| to exit and reap it. If
// the child doesn't exit within the time specified, kills it.
//
// This function takes two approaches: first, it tries to use kqueue to
// observe when the process exits. kevent can monitor a kqueue with a
// timeout, so this method is preferred to wait for a specified period of
// time. Once the kqueue indicates the process has exited, waitpid will reap
// the exited child. If the kqueue doesn't provide an exit event notification,
// before the timeout expires, or if the kqueue fails or misbehaves, the
// process will be mercilessly killed and reaped.
//
// A child process passed to this function may be in one of several states:
// running, terminated and not yet reaped, and (apparently, and unfortunately)
// terminated and already reaped. Normally, a process will at least have been
// asked to exit before this function is called, but this is not required.
// If a process is terminating and unreaped, there may be a window between the
// time that kqueue will no longer recognize it and when it becomes an actual
// zombie that a non-blocking (WNOHANG) waitpid can reap. This condition is
// detected when kqueue indicates that the process is not running and a
// non-blocking waitpid fails to reap the process but indicates that it is
// still running. In this event, a blocking attempt to reap the process
// collects the known-dying child, preventing zombies from congregating.
//
// In the event that the kqueue misbehaves entirely, as it might under a
// EMFILE condition ("too many open files", or out of file descriptors), this
// function will forcibly kill and reap the child without delay. This
// eliminates another potential zombie vector. (If you're out of file
// descriptors, you're probably deep into something else, but that doesn't
// mean that zombies be allowed to kick you while you're down.)
//
// The fact that this function seemingly can be called to wait on a child
// that's not only already terminated but already reaped is a bit of a
// problem: a reaped child's pid can be reclaimed and may refer to a distinct
// process in that case. The fact that this function can seemingly be called
// to wait on a process that's not even a child is also a problem: kqueue will
// work in that case, but waitpid won't, and killing a non-child might not be
// the best approach.
void WaitForChildToDie(pid_t child, int timeout) {
  DCHECK(child > 0);
  DCHECK(timeout > 0);

  // DON'T ADD ANY EARLY RETURNS TO THIS FUNCTION without ensuring that
  // |child| has been reaped. Specifically, even if a kqueue, kevent, or other
  // call fails, this function should fall back to the last resort of trying
  // to kill and reap the process. Not observing this rule will resurrect
  // zombies.

  int result;

  int kq = HANDLE_EINTR(kqueue());
  if (kq == -1) {
    DPLOG(ERROR) << "kqueue()";
  } else {
    file_util::ScopedFD auto_close_kq(&kq);

    struct kevent change = {0};
    EV_SET(&change, child, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
    result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL));

    if (result == -1) {
      if (errno != ESRCH) {
        DPLOG(ERROR) << "kevent (setup " << child << ")";
      } else {
        // At this point, one of the following has occurred:
        // 1. The process has died but has not yet been reaped.
        // 2. The process has died and has already been reaped.
        // 3. The process is in the process of dying. It's no longer
        //    kqueueable, but it may not be waitable yet either. Mark calls
        //    this case the "zombie death race".

        result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG));

        if (result != 0) {
          // A positive result indicates case 1. waitpid succeeded and reaped
          // the child. A result of -1 indicates case 2. The child has already
          // been reaped. In both of these cases, no further action is
          // necessary.
          return;
        }

        // |result| is 0, indicating case 3. The process will be waitable in
        // short order. Fall back out of the kqueue code to kill it (for good
        // measure) and reap it.
      }
    } else {
      // Keep track of the elapsed time to be able to restart kevent if it's
      // interrupted.
      TimeDelta remaining_delta = TimeDelta::FromSeconds(timeout);
      TimeTicks deadline = TimeTicks::Now() + remaining_delta;
      result = -1;
      struct kevent event = {0};
      while (remaining_delta.InMilliseconds() > 0) {
        const struct timespec remaining_timespec = remaining_delta.ToTimeSpec();
        result = kevent(kq, NULL, 0, &event, 1, &remaining_timespec);
        if (result == -1 && errno == EINTR) {
          remaining_delta = deadline - TimeTicks::Now();
          result = 0;
        } else {
          break;
        }
      }

      if (result == -1) {
        DPLOG(ERROR) << "kevent (wait " << child << ")";
      } else if (result > 1) {
        DLOG(ERROR) << "kevent (wait " << child << "): unexpected result "
                    << result;
      } else if (result == 1) {
        if ((event.fflags & NOTE_EXIT) &&
            (event.ident == static_cast<uintptr_t>(child))) {
          // The process is dead or dying. This won't block for long, if at
          // all.
          BlockingReap(child);
          return;
        } else {
          DLOG(ERROR) << "kevent (wait " << child
                      << "): unexpected event: fflags=" << event.fflags
                      << ", ident=" << event.ident;
        }
      }
    }
  }

  // The child is still alive, or is very freshly dead. Be sure by sending it
  // a signal. This is safe even if it's freshly dead, because it will be a
  // zombie (or on the way to zombiedom) and kill will return 0 even if the
  // signal is not delivered to a live process.
  result = kill(child, SIGKILL);
  if (result == -1) {
    DPLOG(ERROR) << "kill(" << child << ", SIGKILL)";
  } else {
    // The child is definitely on the way out now. BlockingReap won't need to
    // wait for long, if at all.
    BlockingReap(child);
  }
}

}  // namespace

void EnsureProcessTerminated(ProcessHandle process) {
  WaitForChildToDie(process, kWaitBeforeKillSeconds);
}

}  // namespace base