    Array.from(io.processes.values())
        .filter(proc => proc.exitCode == null)
        .map(proc => [proc.id, proc.pid])
    );
    return { data };
  },

  // For testing.
  waitForNoProcesses() {
    return Promise.all(
      Array.from(io.processes.values(), proc => proc.awaitFinished())
    );
  },

  // It is the caller's responsability to make sure dup() is called on the FDs
  // returned here.
  getFds(processId) {
    // fd is a unix.Fd aka CDataFinalizer that wraps the actual integer. We can
    // retrieve its value via .toString(), if it has not been closed yet.
    let process = io.getProcess(processId);
    let pipes = process.pipes.map(p => parseInt(p.fd.toString(), 10));
    return {
      data: [pipes[0], pipes[1], pipes[2]],
    };
  },
};

onmessage = event => {
  io.messageCount--;

  let { msg, msgId, args } = event.data;

  new Promise(resolve => {
    resolve(requests[msg](...args));
  })
    .then(result => {
      let response = {
        msg: "success",
        msgId,
        data: result.data,
      };

      self.postMessage(response, result.transfer || []);
    })
    .catch(error => {
      self.postMessage({
        msg: "failure",
        msgId,
        error,
        // We sometimes attach an extra property to the error, which is not
        // copied when the error passes through structuredClone.
        errorCode: error.errorCode,
      });
    })
    .catch(error => {
      console.error(error);

      self.postMessage({
        msg: "failure",
        msgId,
        error: {},
      });
    });
};
PK