ok, rate limit forking

On Macs the user process limit is low, in the hundreds, and it's a
little annoying to mysteriously find other programs fail to start
because ok is greedily slurping up all the available process capacity.

On Linux the user process limit is up in the ten-thousands, so I don't
expect this to matter there, even on Android.  I haven't seen any
significant speed difference imposing a default ncpus limit.

Change-Id: Id284723808074441710c0436acbd75ab1c6bbbb3
Reviewed-on: https://skia-review.googlesource.com/41840
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Mike Klein <mtklein@chromium.org>
diff --git a/tools/ok_engines.cpp b/tools/ok_engines.cpp
index e2218bf..3304f3e 100644
--- a/tools/ok_engines.cpp
+++ b/tools/ok_engines.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "ok.h"
+#include <stdlib.h>
 
 struct SerialEngine : Engine {
     static std::unique_ptr<Engine> Factory(Options) {
@@ -44,14 +45,26 @@
     #include <unistd.h>
 
     struct ForkEngine : Engine {
-        static std::unique_ptr<Engine> Factory(Options) {
+        int limit;      // How many concurrent subprocesses do we allow to run at max?
+        int alive = 0;  // How many concurrent subprocesses do we have running right now?
+
+        static std::unique_ptr<Engine> Factory(Options options) {
             ForkEngine engine;
+            engine.limit = atoi(options("limit", "0").c_str());
+            if (engine.limit < 1) {
+                engine.limit = std::thread::hardware_concurrency();
+            }
             return move_unique(engine);
         }
 
         bool crashproof() override { return true; }
 
         std::future<Status> spawn(std::function<Status(void)> fn) override {
+            if (alive == limit) {
+                // The caller will wait for a child process to finish then try again.
+                return std::future<Status>();
+            }
+
             switch (fork()) {
                 case  0:
                     // We are the spawned child process.
@@ -66,10 +79,12 @@
                 default:
                     // We succesfully spawned a child process!
                     // This will wait for any spawned process to finish and return its Status.
-                    return std::async(std::launch::deferred, [] {
+                    alive++;
+                    return std::async(std::launch::deferred, [&] {
                         do {
                             int status;
                             if (wait(&status) > 0) {
+                                alive--;
                                 return WIFEXITED(status) ? (Status)WEXITSTATUS(status)
                                                          : Status::Crashed;
                             }
@@ -80,6 +95,6 @@
         }
     };
     static Register _fork("fork",
-                          "Run each task in an independent process with fork().",
+                          "Run each task in an independent process with fork(), limit=ncpus.",
                           ForkEngine::Factory);
 #endif