Proposal for refactoring process launching

Apologies in advance for the long read, tried to write this up as clearly as possible. At a minimum I’d like to complete the HostProcess abstraction and the LaunchProcess signature change, but I think the part that follows (which I’ve listed as optional) is a net gain for the codebase and improves the maintainability and clarity of all of this code.

This will discuss a plan for refactoring process management and launching code in the Host layer into a set of reusable abstractions. This is part of a continued effort to refactor Host.cpp in a way that lends itself to better re-usability, better platform-separation (code specific to platform X is isolated to a file specific to platform X), and a stronger barrier between generic code and platform specific code. One of the biggest difficulties in my efforts to port LLDB to Windows so far has been locating and fixing places in LLDB where an operation was assumed to be generic (either logically or through the use of a particular API) when it actually was not. By re-thinking the Host layer from the bottom up with Windows in mind, we can arrive at a set of abstractions that is truly generic enough to be used across all platforms, while still making it possible to use platform specific features from other platform specific code.

The HostProcess abstraction

It’s probably easiest to start with the core abstraction itself, and then discuss the other supporting changes needed to arrive at that. At the core of this refactor is the |HostProcess| abstraction. This is a replacement for what is currently an lldb::process_t, and serves as an abstraction that allows one to manipulate a process running on the system. In order to create a strong barrier between generic code and platform specific code, |HostProcess| will use a facade pattern similar to that used by |HostThread|, whereby |HostProcess| provides only methods which are generic enough to work on every platform, and wraps a |HostNativeProcessBase| for which there might be many derived implementations, each implementing the generic options for the particular platform, as well as potentially offering any relevant platform specific operations so that other platform specific code can take full advantage of the platform.

A generic interface for process will provide at a minimum the following methods:

  • Terminate

  • GetMainModule

  • GetProcessId

  • IsRunning

Additionally, there may be use for methods to inspect the process while running, such as methods like the following:

  • ReadProcessMemory

  • WriteProcessMemory

Additional platform-specific methods will be exposed through the corresponding subclasses of |HostNativeProcessBase|. The most obvious example of this is a Signal method that will be exposed through |HostProcessPosix|, although the proposed design provides the flexibility for this set of platform-specific methods to be arbitrary.

Launching Processes

Currently the ability to launch a process is exposed through a single method Host::LaunchProcess which takes a |ProcessLaunchInfo| argument. Upon completion, LaunchProcess writes the pid of the newly created process into the |ProcessLaunchInfo|. Because of this, the ProcessLaunchInfo structure is required to be passed in as a non-const reference so it can be modified. To address this, the launch method will be modified to return a HostProcess. The PID will not be written back into the |ProcessLaunchInfo| (and if possible the pid field will ideally be removed from |ProcessLaunchInfo| entirely), allowing it to be passed const.

Optional code re-org related to process launching

The process launching code is relatively confusing and difficult to maintain. Because the code is not modular and not written to be re-usable at a granular level, some hackery exists to make the correct methods be found by the linker on various platforms. The following sections describes a possible code re-organization that makes everything more modular and re-usable, and makes the code easier to understand and maintain as a result.

The basis of this code re-organization is the creation of a |ProcessLauncher| interface will be created. Instead of using Host::LaunchProcess, anyone wishing to launch a process will instantiate some subclass of |ProcessLauncher|, and then invoke the Launch method on this interface, which will return a HostProcess. For those just wishing to get the default behavior on a particular platform (i.e. get the behavior equivalent to calling Host::LaunchProcess now), |DefaultProcessLauncher| will be typedef’ed to the most sensible process launcher on each platform. This allows existing code to be written by simply instantiating a |DefaultProcessLauncher| and using it. The interface of |ProcessLauncher| will provide the following methods:

  • LaunchProcess

  • AddExitCallback

The second of these two methods, AddExitCallback, replaces and improves upon the existing mechanism in |ProcessLaunchInfo| which allows the user to specify an exit callback. The improvement comes from the fact that multiple exit callbacks can be specified.

This approach yields a number of benefits. For starters, it allows us to abstract out the notion of the launching strategy into a reusable piece of code so that platforms which have multiple launching strategies have the ability to make use of specific strategies in other platform specific code, or so that remote process launches can be treated the same as local process launches. As an example, on MacOSX there are three different launching strategies: Launch with XPC services, launch with Applescript, and launch with posix_spawn. Each of these could be independent implementations of |ProcessLauncher|, with the default implementation on MacOSX being a fourth implementation that chooses one of the first 3 implementations to delegate to at runtime. As a result, the 3 implementations can be used independently of each other should the need arise.

A second benefit is that it opens up the possibility of treating remote process launch transparently with local process launch – just have a |RemoteProcessLauncher| class.

There are additional benefits in the form of overall code health, platform separation, and just generally improving the modularity of the codebase, although those are perhaps less tangible.

Monitoring for process exit

It is important to know when a process exits, for 2 reasons. First, on some platforms the process needs to be reaped. Second, asynchronous exit callbacks need to be invoked. The implementation of the monitoring is platform dependent, and a strategy for each platform can be summarized as follows:

  • Windows
  1. Create a background thread, use WaitForSingleObject

  2. [Better, alternative implementation] Create one “static” background thread for all processes, use WaitForMultipleObjects() to monitor many processes at once, like select().

  • MacOSX
  1. Processes spawned with posix_spawn - Use MacOSX “dispatch” library to queue monitoring code on a system thread.

  2. Processes spawned with XPC - Use MacOSX “dispatch” library to queue monitoring code on a system thread.

  3. Processes spawned with Applescript - No monitoring necessary

  • Non-MacOSX posix systems
  1. Processes spawned with posix_spawn - Create a background thread, use waitpid in the background thread.

To handle this, a |ProcessExitMonitor| abstraction will be created, with implementations for each of the above strategies. Individual implementations of |ProcessLauncher| will provide a method CreateProcessMonitor() which returns an instance to the correct monitor type, and the resulting |ProcessExitMonitor| will be stored in the |HostProcess|.

It’s possible to see some of the benefit in code re-organization from the above breakdown of implementations. All posix-based systems can sometimes use a posix_spawn based strategy for launching processes, but MacOSX might use other strategies depending on the situation. Even when MacOSX uses posix_spawn though, it still uses a different exit monitoring implementation. This set of modular abstractions handles this nicely. |ProcessLauncherPosixSpawn| goes in host/posix, and takes a |ProcessExitMonitor| to its constructor. If MacOSX decides at runtime to launch using posix_spawn, it passes a |DispatchExitMonitor|, which lives in host/macosx to the constructor. All code always lives at the most generic level where it makes sense for everyone, and there’s no code pollution or pre-processor hackery to get certain functions to be visible.

Since the |HostProcess| can go out of scope while the process is still running on the system, it is important that the lifetime of the |ProcessExitMonitor| be tied to the lifetime of the process running on the system, and not to the lifetime of the |HostProcess| object inside of LLDB. This can be achieved by having |HostProcess| store an std::weak_ptr, and having the background thread own the |ProcessExitMonitor| through an std::shared_ptr. This way, even if LLDB decides it no longer needs the |HostProcess|, if the process is still running in the background it will get reaped and callbacks fired correctly.

Getting it done

In an effort to keep the CLs manageable, this work will be split into multiple changes, in the order described below:

  1. Create the HostProcess abstraction, implement it for all platforms. Do not update any code to use HostProcess yet.

  2. Modify the signature of Host::LaunchProcess to return a HostProcess, and remove the pid from ProcessLaunchInfo. Update calling code accordingly.

  3. Create the various implementations of ProcessExitMonitor, update Host::LaunchProcess to use the appropriate ProcessExitMonitor instead of calling StartMonitoringChildProcess.

  4. Create the various implementations of ProcessLauncher, update calling code to use them instead of Host::LaunchProcess, delete Host::LaunchProcess.

Comments:

- When running processes one might want to get signals for the process that are delivered while the child process is running. There can be any amount of signals over the child process' lifetime. This must be included in the design. It might not work for windows, but it will need to be worked into the design.

- You can change the internal API, but you can't change the public API. If you make changes to any of the launch info classes internally, you will need to keep the SBLaunchInfo API the same. I know you like cleaning things up, but we have clients that use this API and we don't want it broken. You will need to carefully check for any other things that get modified in the launch info class and make sure any such cases are fixed.

- We should make ProcessLauncher a plug-in and be able to request them by name. Then we can add things to the launch info to be able to specify the process launcher plug-in name ("posix_spawn", "fork", "apple-xpc", "default" which can map to the default launcher for a host, etc).

More comments below

Apologies in advance for the long read, tried to write this up as clearly as possible. At a minimum I'd like to complete the HostProcess abstraction and the LaunchProcess signature change, but I think the part that follows (which I've listed as optional) is a net gain for the codebase and improves the maintainability and clarity of all of this code.

This will discuss a plan for refactoring process management and launching code in the Host layer into a set of reusable abstractions. This is part of a continued effort to refactor Host.cpp in a way that lends itself to better re-usability, better platform-separation (code specific to platform X is isolated to a file specific to platform X), and a stronger barrier between generic code and platform specific code. One of the biggest difficulties in my efforts to port LLDB to Windows so far has been locating and fixing places in LLDB where an operation was assumed to be generic (either logically or through the use of a particular API) when it actually was not. By re-thinking the Host layer from the bottom up with Windows in mind, we can arrive at a set of abstractions that is truly generic enough to be used across all platforms, while still making it possible to use platform specific features from other platform specific code.

The HostProcess abstraction

It's probably easiest to start with the core abstraction itself, and then discuss the other supporting changes needed to arrive at that. At the core of this refactor is the |HostProcess| abstraction. This is a replacement for what is currently an lldb::process_t, and serves as an abstraction that allows one to manipulate a process running on the system. In order to create a strong barrier between generic code and platform specific code, |HostProcess| will use a facade pattern similar to that used by |HostThread|, whereby |HostProcess| provides only methods which are generic enough to work on every platform, and wraps a |HostNativeProcessBase| for which there might be many derived implementations, each implementing the generic options for the particular platform, as well as potentially offering any relevant platform specific operations so that other platform specific code can take full advantage of the platform.

A generic interface for process will provide at a minimum the following methods:

  • Terminate
  • GetMainModule
  • GetProcessId

"GetProcessID" instead of "GetProcessId".

  • IsRunning

Additionally, there may be use for methods to inspect the process while running, such as methods like the following:

  • ReadProcessMemory
  • WriteProcessMemory

Why have "Process" in the name? It is on a process class.

You might want suspend/resume as well.

Additional platform-specific methods will be exposed through the corresponding subclasses of |HostNativeProcessBase|. The most obvious example of this is a Signal method that will be exposed through |HostProcessPosix|, although the proposed design provides the flexibility for this set of platform-specific methods to be arbitrary.

Launching Processes
Currently the ability to launch a process is exposed through a single method Host::LaunchProcess which takes a |ProcessLaunchInfo| argument. Upon completion, LaunchProcess writes the pid of the newly created process into the |ProcessLaunchInfo|. Because of this, the ProcessLaunchInfo structure is required to be passed in as a non-const reference so it can be modified. To address this, the launch method will be modified to return a HostProcess. The PID will not be written back into the |ProcessLaunchInfo| (and if possible the pid field will ideally be removed from |ProcessLaunchInfo| entirely), allowing it to be passed const.

We should make this a plug-in type for reasons mentioned at the beginning. You also need to keep the public API unchanged for SBLaunchInfo. You might need to make a small wrapper class for this to wrap the SBLaunchInfo and the resulting HostProcess into a single class to be able to wrap this. You should also add the ability to set a process launcher plug-in by name both internally and in the SB layer. It is ok to add functions to a public API, but not to change it. You can add new API to the SB layer ( like adding a new SBHostProcess and having "SBHostProcess SBLaunchInfo::GetHostProcess()", but you need to maintain the old API as well).

Optional code re-org related to process launching
The process launching code is relatively confusing and difficult to maintain. Because the code is not modular and not written to be re-usable at a granular level, some hackery exists to make the correct methods be found by the linker on various platforms. The following sections describes a possible code re-organization that makes everything more modular and re-usable, and makes the code easier to understand and maintain as a result.

The basis of this code re-organization is the creation of a |ProcessLauncher| interface will be created. Instead of using Host::LaunchProcess, anyone wishing to launch a process will instantiate some subclass of |ProcessLauncher|, and then invoke the Launch method on this interface, which will return a HostProcess. For those just wishing to get the default behavior on a particular platform (i.e. get the behavior equivalent to calling Host::LaunchProcess now), |DefaultProcessLauncher| will be typedef'ed to the most sensible process launcher on each platform. This allows existing code to be written by simply instantiating a |DefaultProcessLauncher| and using it. The interface of |ProcessLauncher| will provide the following methods:

  • LaunchProcess
  • AddExitCallback

The second of these two methods, AddExitCallback, replaces and improves upon the existing mechanism in |ProcessLaunchInfo| which allows the user to specify an exit callback. The improvement comes from the fact that multiple exit callbacks can be specified.

That is fine. Not really needed by any case I can think of but a good change.

This approach yields a number of benefits. For starters, it allows us to abstract out the notion of the launching strategy into a reusable piece of code so that platforms which have multiple launching strategies have the ability to make use of specific strategies in other platform specific code, or so that remote process launches can be treated the same as local process launches. As an example, on MacOSX there are three different launching strategies: Launch with XPC services, launch with Applescript, and launch with posix_spawn. Each of these could be independent implementations of |ProcessLauncher|, with the default implementation on MacOSX being a fourth implementation that chooses one of the first 3 implementations to delegate to at runtime. As a result, the 3 implementations can be used independently of each other should the need arise.

A second benefit is that it opens up the possibility of treating remote process launch transparently with local process launch -- just have a |RemoteProcessLauncher| class.

Uh... Then don't call your class HostProcess. I really would rather not have remote launching involved in this at all. Remote launching should result in a lldb::pid_t and it won't use the native process layer for anything, so I would avoid including remote process launching in this as it mucks up what info might need to be in the launch info (remote host/port, password, etc). So no remote process support please.

There are additional benefits in the form of overall code health, platform separation, and just generally improving the modularity of the codebase, although those are perhaps less tangible.

Monitoring for process exit
It is important to know when a process exits, for 2 reasons. First, on some platforms the process needs to be reaped. Second, asynchronous exit callbacks need to be invoked. The implementation of the monitoring is platform dependent, and a strategy for each platform can be summarized as follows:

  • Windows
    • Create a background thread, use WaitForSingleObject
    • [Better, alternative implementation] Create one "static" background thread for all processes, use WaitForMultipleObjects() to monitor many processes at once, like select().
  • MacOSX
    • Processes spawned with posix_spawn - Use MacOSX "dispatch" library to queue monitoring code on a system thread.
    • Processes spawned with XPC - Use MacOSX "dispatch" library to queue monitoring code on a system thread.
    • Processes spawned with Applescript - No monitoring necessary
  • Non-MacOSX posix systems
    • Processes spawned with posix_spawn - Create a background thread, use waitpid in the background thread.

We also need to be able to deliver signals during child process execution.

To handle this, a |ProcessExitMonitor| abstraction will be created, with implementations for each of the above strategies. Individual implementations of |ProcessLauncher| will provide a method CreateProcessMonitor() which returns an instance to the correct monitor type, and the resulting |ProcessExitMonitor| will be stored in the |HostProcess|.

That is fine as long as NULL can be returned in cases where a process can't be monitored (AppleScript).

It’s possible to see some of the benefit in code re-organization from the above breakdown of implementations. All posix-based systems can sometimes use a posix_spawn based strategy for launching processes, but MacOSX might use other strategies depending on the situation. Even when MacOSX uses posix_spawn though, it still uses a different exit monitoring implementation. This set of modular abstractions handles this nicely. |ProcessLauncherPosixSpawn| goes in host/posix, and takes a |ProcessExitMonitor| to its constructor. If MacOSX decides at runtime to launch using posix_spawn, it passes a |DispatchExitMonitor|, which lives in host/macosx to the constructor. All code always lives at the most generic level where it makes sense for everyone, and there’s no code pollution or pre-processor hackery to get certain functions to be visible.

We might want the rename the ProcessExitMonitor to ProcessStatusMonitor since signals can be delivered from the child process that don't cause it to exit (SIGSTOP, etc).

Since the |HostProcess| can go out of scope while the process is still running on the system, it is important that the lifetime of the |ProcessExitMonitor| be tied to the lifetime of the process running on the system,

and not to the lifetime of the |HostProcess| object inside of LLDB. This can be achieved by having |HostProcess| store an std::weak_ptr<ProcessExitMonitor>, and having the background thread own the |ProcessExitMonitor| through an std::shared_ptr<ProcessExitMonitor>. This way, even if LLDB decides it no longer needs the |HostProcess|, if the process is still running in the background it will get reaped and callbacks fired correctly.

Getting it done
In an effort to keep the CLs manageable, this work will be split into multiple changes, in the order described below:
  • Create the HostProcess abstraction, implement it for all platforms. Do not update any code to use HostProcess yet.
  • Modify the signature of Host::LaunchProcess to return a HostProcess, and remove the pid from ProcessLaunchInfo. Update calling code accordingly.

Add step here:
  Create a SBHostProcess.h/.cpp/.i file and plumb it through the public API. Modify SBLaunchInfo to be able to return a SBHostProcess, yet still maintain its current API. I actually just looked and it doesn't look like we have a SBLaunchInfo::GetProcessID() so there might not be anything to do. Just make sure not to remove any API calls from the public API, and feel free to add what you want.

  • Create the various implementations of ProcessExitMonitor, update Host::LaunchProcess to use the appropriate ProcessExitMonitor instead of calling StartMonitoringChildProcess.
  • Create the various implementations of ProcessLauncher, update calling code to use them instead of Host::LaunchProcess, delete Host::LaunchProcess.

Let me know what you think about the new ProcessLauncher plug-in type and all my other comments. Overall sounds like a good plan.

Greg

Comments:

- When running processes one might want to get signals for the process
that are delivered while the child process is running. There can be any
amount of signals over the child process' lifetime. This must be included
in the design. It might not work for windows, but it will need to be worked
into the design.

Can you clarify? The goal was to propose this in such a way that it's
functionally equivalent to the current code. In the existing code, what is
the means by which this currently happens? There is the ability to send a
signal *to* a process, and that is covered in this design by providing a
Signal() method on HostNativeProcessPosix. Anyone calling that method is
invoking platform specific functionality, so they would have either already
be inside platform specific code, or write something like
HostProcess->GetNativeProcess()->Signal() wrapped in an appropriate #ifdef.

- You can change the internal API, but you can't change the public API. If
you make changes to any of the launch info classes internally, you will
need to keep the SBLaunchInfo API the same. I know you like cleaning things
up, but we have clients that use this API and we don't want it broken. You
will need to carefully check for any other things that get modified in the
launch info class and make sure any such cases are fixed.

This shouldn't involve a change to the public API.

- We should make ProcessLauncher a plug-in and be able to request them by
name. Then we can add things to the launch info to be able to specify the
process launcher plug-in name ("posix_spawn", "fork", "apple-xpc",
"default" which can map to the default launcher for a host, etc).

Good idea, probably can be done as a last step. All of the actual logic
would still live in Host, since it's OS-specific, and the plugin code could
just be a lightweight shim to select the correct one.

>
> This approach yields a number of benefits. For starters, it allows us
to abstract out the notion of the launching strategy into a reusable piece
of code so that platforms which have multiple launching strategies have the
ability to make use of specific strategies in other platform specific code,
or so that remote process launches can be treated the same as local process
launches. As an example, on MacOSX there are three different launching
strategies: Launch with XPC services, launch with Applescript, and launch
with posix_spawn. Each of these could be independent implementations of
>ProcessLauncher>, with the default implementation on MacOSX being a fourth
implementation that chooses one of the first 3 implementations to delegate
to at runtime. As a result, the 3 implementations can be used
independently of each other should the need arise.
>
> A second benefit is that it opens up the possibility of treating remote
process launch transparently with local process launch -- just have a
>RemoteProcessLauncher> class.

Uh... Then don't call your class HostProcess. I really would rather not
have remote launching involved in this at all. Remote launching should
result in a lldb::pid_t and it won't use the native process layer for
anything, so I would avoid including remote process launching in this as it
mucks up what info might need to be in the launch info (remote host/port,
password, etc). So no remote process support please.

Fair enough, I mentioned that just as something that may or may not be
useful in the future, but the design lends itself nicely to. I don't want
to choose names based on something that might not happen though, so I think
it still makes sense to go with HostProcess, since for now that's what it
actually is, and gives some naming consistency between this and HostThread.

>
> There are additional benefits in the form of overall code health,
platform separation, and just generally improving the modularity of the
codebase, although those are perhaps less tangible.
>
> Monitoring for process exit
> It is important to know when a process exits, for 2 reasons. First, on
some platforms the process needs to be reaped. Second, asynchronous exit
callbacks need to be invoked. The implementation of the monitoring is
platform dependent, and a strategy for each platform can be summarized as
follows:
>
> • Windows
> • Create a background thread, use WaitForSingleObject
> • [Better, alternative implementation] Create one "static"
background thread for all processes, use WaitForMultipleObjects() to
monitor many processes at once, like select().
> • MacOSX
> • Processes spawned with posix_spawn - Use MacOSX
"dispatch" library to queue monitoring code on a system thread.
> • Processes spawned with XPC - Use MacOSX "dispatch"
library to queue monitoring code on a system thread.
> • Processes spawned with Applescript - No monitoring
necessary
> • Non-MacOSX posix systems
> • Processes spawned with posix_spawn - Create a background
thread, use waitpid in the background thread.

We also need to be able to deliver signals during child process execution.

Mentioned earlier, but this is already covered. HostProcess is a wrapper
around a HostNativeProcess, and provides a method just like HostThread
does, which returns the native process. Since signal is not a generic
operation, it wouldn't live on HostProcess, but on HostNativeProcessPosix.
Anyone wishing to deliver a signal to a child process would write
host_process.GetNativeProcess()->Signal(SIGTERM) or whatever. If they
write this from generic code it would need to be inside of an #ifdef. If
they write it from platform-specific code it will just work.

Heads up, I’m actually going to put this on hold for the foreseeable future. It’s more difficult than I anticipated, which although that just makes the code re-organization more desirable in my mind, it also makes the risk pretty high. I would rather just let the changes I’ve done bake for a while, and then maybe come back to this in a few months, hopefully making smaller changes along the way that make this easier in the future.

Thanks for the heads up, Zachary!

Comments:

- When running processes one might want to get signals for the process that are delivered while the child process is running. There can be any amount of signals over the child process' lifetime. This must be included in the design. It might not work for windows, but it will need to be worked into the design.

Can you clarify? The goal was to propose this in such a way that it's functionally equivalent to the current code. In the existing code, what is the means by which this currently happens?

    static HostThread StartMonitoringChildProcess(MonitorChildProcessCallback callback, void *callback_baton, lldb::pid_t pid,
                                                  bool monitor_signals);

Call the above method with "monitor_signals" set to true. When a child process of your child process dies you will get a a SIGCHILD signal, and if something happens to the child's terminal you might get a SIGTTIN or SIGTTOU or SIGWINCH. Also you might get a SIGPIPE if some socket connection in the child dies or becomes unreadable.

There is the ability to send a signal to a process, and that is covered in this design by providing a Signal() method on HostNativeProcessPosix.

No, we won't need to send the signal, we need to get signals that the child doesn't handle.

Anyone calling that method is invoking platform specific functionality, so they would have either already be inside platform specific code, or write something like HostProcess->GetNativeProcess()->Signal() wrapped in an appropriate #ifdef.

See above.

- You can change the internal API, but you can't change the public API. If you make changes to any of the launch info classes internally, you will need to keep the SBLaunchInfo API the same. I know you like cleaning things up, but we have clients that use this API and we don't want it broken. You will need to carefully check for any other things that get modified in the launch info class and make sure any such cases are fixed.
This shouldn't involve a change to the public API.

- We should make ProcessLauncher a plug-in and be able to request them by name. Then we can add things to the launch info to be able to specify the process launcher plug-in name ("posix_spawn", "fork", "apple-xpc", "default" which can map to the default launcher for a host, etc).
Good idea, probably can be done as a last step. All of the actual logic would still live in Host, since it's OS-specific, and the plugin code could just be a lightweight shim to select the correct one.

>
> This approach yields a number of benefits. For starters, it allows us to abstract out the notion of the launching strategy into a reusable piece of code so that platforms which have multiple launching strategies have the ability to make use of specific strategies in other platform specific code, or so that remote process launches can be treated the same as local process launches. As an example, on MacOSX there are three different launching strategies: Launch with XPC services, launch with Applescript, and launch with posix_spawn. Each of these could be independent implementations of |ProcessLauncher|, with the default implementation on MacOSX being a fourth implementation that chooses one of the first 3 implementations to delegate to at runtime. As a result, the 3 implementations can be used independently of each other should the need arise.
>
> A second benefit is that it opens up the possibility of treating remote process launch transparently with local process launch -- just have a |RemoteProcessLauncher| class.

Uh... Then don't call your class HostProcess. I really would rather not have remote launching involved in this at all. Remote launching should result in a lldb::pid_t and it won't use the native process layer for anything, so I would avoid including remote process launching in this as it mucks up what info might need to be in the launch info (remote host/port, password, etc). So no remote process support please.

Fair enough, I mentioned that just as something that may or may not be useful in the future, but the design lends itself nicely to. I don't want to choose names based on something that might not happen though, so I think it still makes sense to go with HostProcess, since for now that's what it actually is, and gives some naming consistency between this and HostThread.

that is fine, just don't try and mix it with remote processes and then all of our types lldb::process_t and lldb::thread_t, etc are only for local processes.

>
> There are additional benefits in the form of overall code health, platform separation, and just generally improving the modularity of the codebase, although those are perhaps less tangible.
>
> Monitoring for process exit
> It is important to know when a process exits, for 2 reasons. First, on some platforms the process needs to be reaped. Second, asynchronous exit callbacks need to be invoked. The implementation of the monitoring is platform dependent, and a strategy for each platform can be summarized as follows:
>
> • Windows
> • Create a background thread, use WaitForSingleObject
> • [Better, alternative implementation] Create one "static" background thread for all processes, use WaitForMultipleObjects() to monitor many processes at once, like select().
> • MacOSX
> • Processes spawned with posix_spawn - Use MacOSX "dispatch" library to queue monitoring code on a system thread.
> • Processes spawned with XPC - Use MacOSX "dispatch" library to queue monitoring code on a system thread.
> • Processes spawned with Applescript - No monitoring necessary
> • Non-MacOSX posix systems
> • Processes spawned with posix_spawn - Create a background thread, use waitpid in the background thread.

We also need to be able to deliver signals during child process execution.
Mentioned earlier, but this is already covered. HostProcess is a wrapper around a HostNativeProcess, and provides a method just like HostThread does, which returns the native process. Since signal is not a generic operation, it wouldn't live on HostProcess, but on HostNativeProcessPosix. Anyone wishing to deliver a signal to a child process would write host_process.GetNativeProcess()->Signal(SIGTERM) or whatever. If they write this from generic code it would need to be inside of an #ifdef. If they write it from platform-specific code it will just work.

Again, the signal delivery is the other way around (child to parent when the child doesn't handle or suppress a signal).

Greg