So far I had time only to go quickly through the documentation and a bit in
the code so here are quick remarks:
1. It is not clear at all what happen when child object is destroyed.
2. The way child process termination is implemented for each platform
should be documented.
3. On most platforms there is a way to send a "quit" command to the process
and a way to kill it by force ("kill -9").
Clarifying what is happening in the default cases and having a way to
terminate one way or the other (or a combination of
both, that is requesting termination but force-kill after a time if it
didn't die)
On windows there is an issue with this because the "termination request"
message can only be received if
you didn't build a console application but a windows application (with a
WinMain) but that only means
that the message could be ignored by the process if it's console
application (there are ways to have both WinMain and
a console window, it's just a bit more clumsy to setup).
In our (Softbank Robotics EU) use of child process tracking we need both
termination request and forced.
One could argue that the termination request do not need to be
implemented by this library but it would be very useful
to have a cross platform function doing this for simple cases.
Joël Lamotte
OK let me gather our open-source and closed source code in one example and
I'll send it to you privately.
Our solution is not perfect but it seems to work well.
Joël Lamotte
That's too harsh as a default. Default signal handling in Posix systems means that sending SIGTERM first would signal a graceful exit. That doesn't mean the process will exit successfully or that it won't ignore the signal, so after waiting for a limited time (user specified with a default), you would send SIGKILL.
(You could actually send SIGTERM, SIGINT, and SIGQUIT, one after the other to increase the chance that the process recognizes the need to exit.)
On Windows, you can send WM_CLOSE. The process may respond within the allotted time and it may not (it certainly won't if it's an ordinary console app). TerminateProcess() is the final step if the process handle isn't signaled within the timeout period.
Thus, terminating a process behaves similarly on both platforms: try a nice signal, wait, then terminate it forcefully if need be.
___
Rob
(Sent from my portable computation engine)
_______________________________________________
Am 07.06.2016 um 10:37 schrieb Rob Stewart:
> On June 6, 2016 6:49:44 AM EDT, "Klaim - Joël Lamotte" <mjk...@gmail.com> wrote:
>> On 6 June 2016 at 12:00, Klemens Morgenstern
>> <klemens.m...@gmx.net>
>> wrote:
>>
>>> 2. The way child process termination is implemented for each platform
>>>> should be documented.
>>>>
>>> Makes sense. It's TerminateProcess on windows and "kill -9" on posix.
>>> (also added)
> That's too harsh as a default. Default signal handling in Posix systems means that sending SIGTERM first would signal a graceful exit. That doesn't mean the process will exit successfully or that it won't ignore the signal, so after waiting for a limited time (user specified with a default), you would send SIGKILL.
>
> (You could actually send SIGTERM, SIGINT, and SIGQUIT, one after the other to increase the chance that the process recognizes the need to exit.)
>
> On Windows, you can send WM_CLOSE. The process may respond within the allotted time and it may not (it certainly won't if it's an ordinary console app). TerminateProcess() is the final step if the process handle isn't signaled within the timeout period.
>
> Thus, terminating a process behaves similarly on both platforms: try a nice signal, wait, then terminate it forcefully if need be.
I actually thought the same thing, but it is an issue of security.
Consider this:
ipstream is;
child c("prog", std_in < is);
is << generate_input() << endl;
Not if generate_input throws, I need to terminate the child, elsewise I
get a deadlock (since "prog" waits for input). If you do not want this,
you can detach it or join it. In that it is similar to std::thread,
though it doesn't terminate the current process. Now using a timeout
would be possible, but I really don't like to set an arbitrary value here.
Regarding the WM_CLOSE version: I currently don't think that is a really
portable solution, since you signal the HWND not the Process, which
means that console programs will cause problems here. Joël Lamotte
recommended a similar solution and will send me some examples, so a
child::request_exit() function might be added. Now if that happens, I
still will not change the behaviour in child, but you might be able to
do this then:
soft_exit se (child("thingy"), milliseconds(100));
It really depends on how this can be achieved on windows; I didn't look
into that, because it's not part of the Process handling in the WinAPI,
but maybe there's a workaround.
I have also written a C++ process (and signals) library supporting
windows/OSX/Linux/Android (for an employer though so I can't propose it for
boost). It actually suprised me how many platform differences existed that
needed to be worked around in writing this library compared to others I
have written.
I also did something similar to Klemens if what I undertood is correct, I
did a fallback scenario of send TERM if not closed within time period send
a KILL. I didn't really like it though and wonder if the std::thread
destructor behavior is better to std::terminate if the child is still
running but not detached and treat it more like a bug to destroy an active
process instance than something to rely on to close it.
Not having a SIGTERM equivilant for windows processes bothered me as
SIGKILL is just too nasty as a default as mentioned. So I looked into this
quite thoroughly. For GUI processes you can easily send WM_CLOSE message to
it to simulate something akin to a SIGTERM. I think I used a WM_CLOSE from
memory. I know there was a choice between QUIT and CLOSE, I think CLOSE was
more suitable but I cant recall the exact reason now and may be wrong.
For console processes I only found bad options. I found something that
worked quite well but ended up dropping it in the end as it was not
required and was super hacky IMO. I only ever wanted to SIGTERM my own
processes in the end so just used a non generic solution of a named pipe to
listen for signals and raise() to local process.
The GenerateConsoleCtrlEvent() comes close to doing what we want but its
got the worst behaviour in how to choose who gets the signal delivered and
is not sufficient (even when creating processes to detach/attach to other
consoles to generate the events). In the end I followed the basic idea
proposed at:
http://www.latenighthacking.com/projects/2003/sendSignal/
With extra ideas proposed in comment (20110401) and a few extra changes of
my own. I got this working well in all scenarios I could think of to test
on win32 and 64 up to Win7 (I think I went down to XP but cant recall for
sure).
The idea is basically to use the mechanism that GenerateConsoleCtrlEvent()
uses to deliver the signal, but you choose the process where to deliver it
yourself instead of using the stupid rules that GenerateConsoleCtrlEvent()
follows.
The idea is to create a new "helper" process, have it register a signal
handler, raise a signal to itself and in the handler walk back the stack to
find the entry point where the signal was generated. It turns out that the
function where the signal was generated (I think it was
kernel32!CtrlRoutine) this function has the correct prototype for
CeateRemoteThread and is in the kernel32.dll so is in the same location in
all processes on the system and is how GenerateConsoleCtrlEvent() is
implemented.
So you basically then find the process you care about, create a remote
thread in it to call the specified function and presto you end up with a
SIGTERM that can be handled by a normal signal() handler in a console
process.
I hope my research was useful, but this approach was not really acceptable
to me and I dont think would be to the boost community. Maybe another idea
might arise from it though, in which case I would love to hear about it.
On 7 June 2016 at 18:59, Klemens Morgenstern <klemens.m...@gmx.net>
wrote:
I see your point. You could provide a default timeout for that case and include a function permitting the user to override that default.
>Regarding the WM_CLOSE version: I currently don't think that is a
>really
>portable solution, since you signal the HWND not the Process, which
>means that console programs will cause problems here.
I mentioned that. Sending signals on posix systems may not terminate a process either. That's why you fall back on SIGKILL. On Windows, you try the WM_CLOSE approach and fall back on TerminateProcess().
>Joël Lamotte
>recommended a similar solution and will send me some examples, so a
>child::request_exit() function might be added.
The remote thread technique sounds interesting.
>Now if that happens, I
>still will not change the behaviour in child, but you might be able to
>do this then:
>
>soft_exit se (child("thingy"), milliseconds(100));
Why not c.terminate(milliseconds(100)) and c.kill()?
Sry, but no. I don't see the point of timeouts, especially default ones.
In this case it is quite clear, that there's no point in continuing, so
why put in timeout?
>
>> Regarding the WM_CLOSE version: I currently don't think that is a
>> really
>> portable solution, since you signal the HWND not the Process, which
>> means that console programs will cause problems here.
>
> I mentioned that. Sending signals on posix systems may not terminate a process either. That's why you fall back on SIGKILL. On Windows, you try the WM_CLOSE approach and fall back on TerminateProcess().
Difference is: every posix program can catch SIGTERM, not every windows
program will get WM_CLOSE - console applications will never receive
that. It depends on the compile options.
Now for this to be part of the process core library (i.e. included in
boost/process.hpp) it would need to work like this both platforms:
//father
child c(...);
c.request_exit();
//child
this_process::on_exit_request(std::function<bool()> func);
That's not possible, so it won't be in the library. It might be added to
the platform-extensions though, i.e. you could have something like the
following functions if you include boost/process/posix.hpp or
boost/process/windows.hpp (needs to be distinguished by #ifdef)
posix::send_terminate(child &);
windows::send_wm_close(child&);
windows::send_console_kill(child&);
I have no problem having a platform-specific function there, but if I
have a function in the multi-platform part of boost.process it has to
behave the same everywhere.
>
>> Joël Lamotte
>> recommended a similar solution and will send me some examples, so a
>> child::request_exit() function might be added.
>
> The remote thread technique sounds interesting.
>
>> Now if that happens, I
>> still will not change the behaviour in child, but you might be able to
>> do this then:
>>
>> soft_exit se (child("thingy"), milliseconds(100));
>
> Why not c.terminate(milliseconds(100)) and c.kill()?
>
Yeah would be possible, too. Though as written above, there won't be a
terminate-request member-function of child. But what you could do is this.
some_magic_terminate_request(c); //your SIGTERM impl.
if (!c.wait_for(milliseconds(100))
c.terminate();
Your approach gives the child no chance to shut down gracefully. It cannot close database connections, remove temporary files, clean up content in shared memory, whatever.
>> Sending signals on posix systems may not terminate
>a process either. That's why you fall back on SIGKILL. On Windows, you
>try the WM_CLOSE approach and fall back on TerminateProcess().
>
>Difference is: every posix program can catch SIGTERM, not every windows
>program will get WM_CLOSE - console applications will never receive
>that. It depends on the compile options.
Some console apps can be written to process such messages, but most won't, of course. What's more, if an app doesn't handle the message, you're no worse off than when you just call TerminateProcess().
>Now for this to be part of the process core library (i.e. included in
>boost/process.hpp) it would need to work like this both platforms:
>
>//father
>child c(...);
>c.request_exit();
>
>//child
>this_process::on_exit_request(std::function<bool()> func);
>
>That's not possible, so it won't be in the library. It might be added
>to
>the platform-extensions though, i.e. you could have something like the
>following functions if you include boost/process/posix.hpp or
>boost/process/windows.hpp (needs to be distinguished by #ifdef)
>
>posix::send_terminate(child &);
>windows::send_wm_close(child&);
>windows::send_console_kill(child&);
>
>I have no problem having a platform-specific function there, but if I
>have a function in the multi-platform part of boost.process it has to
>behave the same everywhere.
I see that you only want to send the nice termination signal if the child can install a handler in a common way. I was missing that side of the equation. The Windows issue is whether it's possible to determine, at compile time or runtime, whether an association has a GUI message loop or not. If so, you'd have to install a message loop handler for WM_CLOSE, and if not, you'd have to use SetConsoleCtrlHandler() to install a callback.
I don't know much about Windows message handler loops, but you could install a message hook if there isn't a better way to integrate into the app's own message loop.
>>> Joël Lamotte
>>> recommended a similar solution and will send me some examples, so a
>>> child::request_exit() function might be added.
>>
>> The remote thread technique sounds interesting.
>>
>>> Now if that happens, I
>>> still will not change the behaviour in child, but you might be able
>to
>>> do this then:
>>>
>>> soft_exit se (child("thingy"), milliseconds(100));
>>
>> Why not c.terminate(milliseconds(100)) and c.kill()?
>
>Yeah would be possible, too. Though as written above, there won't be a
>terminate-request member-function of child. But what you could do is
>this.
>
>some_magic_terminate_request(c); //your SIGTERM impl.
>
>if (!c.wait_for(milliseconds(100))
> c.terminate();
I'm confused why you think some_magic_terminate_request() should be a free function, while terminate() is a member function.
A timeout doesn't make sense if you have no standard way to signal the
child to exit. If you implement your version of that, you can use
chlid::wait_for and then terminate. The child class is meant to be
joined before destructing; the terminate on destruction is for exceptions.
That, AND it ought to be the default on the system for exit requests.
> I don't know much about Windows message handler loops, but you could install a message hook if there isn't a better way to integrate into the app's own message loop.
>
It doesn't work really, especially since SetConsoleCtrlHandler does only
allow two values (Ctrl+C & Ctrl+Break), which are both not clearly
termination requests, but more of a terminate command I think. Also to
do that, I would need to put all those child processes on a new process
group, which would mean, that Ctrl+C will NOT be transmitted to them.
That's also a dealbreaker.
>>>> Joël Lamotte
>>>> recommended a similar solution and will send me some examples, so a
>>>> child::request_exit() function might be added.
>>>
>>> The remote thread technique sounds interesting.
>>>
>>>> Now if that happens, I
>>>> still will not change the behaviour in child, but you might be able
>> to
>>>> do this then:
>>>>
>>>> soft_exit se (child("thingy"), milliseconds(100));
>>>
>>> Why not c.terminate(milliseconds(100)) and c.kill()?
>>
>> Yeah would be possible, too. Though as written above, there won't be a
>> terminate-request member-function of child. But what you could do is
>> this.
>>
>> some_magic_terminate_request(c); //your SIGTERM impl.
>>
>> if (!c.wait_for(milliseconds(100))
>> c.terminate();
>
> I'm confused why you think some_magic_terminate_request() should be a free function, while terminate() is a member function.
>
Because I would be fine to have it as an extension in the posix- or
windows-namespace, but not in the core-library. Hence it won't be a
method of child.