blob: 380edbb2000bd0b7659c465f93d11e9fa1f9286e [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License
#include <glog/logging.h>
#include <sys/types.h>
#ifndef __WINDOWS__
#include <sys/wait.h>
#endif
#include <process/delay.hpp>
#include <process/future.hpp>
#include <process/id.hpp>
#include <process/once.hpp>
#include <process/owned.hpp>
#include <process/reap.hpp>
#include <stout/check.hpp>
#include <stout/foreach.hpp>
#include <stout/multihashmap.hpp>
#include <stout/none.hpp>
#include <stout/option.hpp>
#include <stout/os.hpp>
#include <stout/result.hpp>
#include <stout/try.hpp>
namespace process {
// TODO(bmahler): This can be optimized to use a thread per pid, where
// each thread makes a blocking call to waitpid. This eliminates the
// unfortunate poll delay.
//
// Simple bounded linear model for computing the poll interval.
// Values were chosen such that at (50 pids, 100 ms) the CPU usage is
// less than approx. 0.5% of a single core, and at (500 pids, 1000 ms)
// less than approx. 1.0% of single core. Tested on Linux 3.10 with
// Intel Xeon E5620 and OSX 10.9 with Intel i7 4980HQ.
//
// 1000ms _____
// /
// (interval) /
// /
// 100ms -----/
// 50 500
//
// (# pids)
//
const size_t LOW_PID_COUNT = 50;
Duration MIN_REAP_INTERVAL() { return Milliseconds(100); }
const size_t HIGH_PID_COUNT = 500;
Duration MAX_REAP_INTERVAL() { return Seconds(1); }
namespace internal {
ReaperProcess::ReaperProcess() : ProcessBase(ID::generate("__reaper__")) {}
Future<Option<int>> ReaperProcess::reap(pid_t pid)
{
// Check to see if this pid exists.
if (os::exists(pid)) {
Owned<Promise<Option<int>>> promise(new Promise<Option<int>>());
promises.put(pid, promise);
return promise->future();
} else {
return None();
}
}
void ReaperProcess::initialize()
{
wait();
}
void ReaperProcess::wait()
{
// There are two cases to consider for each pid when it terminates:
// 1) The process is our child. In this case, we will reap the process and
// notify with the exit status.
// 2) The process was not our child. In this case, it will be reaped by
// someone else (its parent or init, if reparented) so we cannot know
// the exit status and we must notify with None().
//
// NOTE: A child can only be reaped by us, the parent. If a child exits
// between waitpid and the (!exists) conditional it will still exist as a
// zombie; it will be reaped by us on the next loop.
foreach (pid_t pid, promises.keys()) {
int status;
Result<pid_t> child_pid = os::waitpid(pid, &status, WNOHANG);
if (child_pid.isSome()) {
// We have reaped a child.
notify(pid, status);
} else if (!os::exists(pid)) {
// The process no longer exists and has been reaped by someone else.
notify(pid, None());
}
}
delay(interval(), self(), &ReaperProcess::wait); // Reap forever!
}
void ReaperProcess::notify(pid_t pid, Result<int> status)
{
foreach (const Owned<Promise<Option<int>>>& promise, promises.get(pid)) {
if (status.isError()) {
promise->fail(status.error());
} else if (status.isNone()) {
promise->set(Option<int>::none());
} else {
promise->set(Option<int>(status.get()));
}
}
promises.remove(pid);
}
const Duration ReaperProcess::interval()
{
size_t count = promises.size();
if (count <= LOW_PID_COUNT) {
return MIN_REAP_INTERVAL();
} else if (count >= HIGH_PID_COUNT) {
return MAX_REAP_INTERVAL();
}
// Linear interpolation between min and max reap intervals.
double fraction =
((double) (count - LOW_PID_COUNT) / (HIGH_PID_COUNT - LOW_PID_COUNT));
return (MIN_REAP_INTERVAL() +
(MAX_REAP_INTERVAL() - MIN_REAP_INTERVAL()) * fraction);
}
} // namespace internal {
Future<Option<int>> reap(pid_t pid)
{
// The reaper process is instantiated in `process::initialize`.
process::initialize();
return dispatch(
internal::reaper,
&internal::ReaperProcess::reap,
pid);
}
} // namespace process {