Thread: refactoring fork() and EXEC_BACKEND

refactoring fork() and EXEC_BACKEND

From
Neil Conway
Date:
While going through the usual motions needed to fork a child process of 
the postmaster, it occurred to me that there's a fair bit of duplicated 
code involved. There are also #ifdef for various situations (BeOS, 
LINUX_PROFILE, and EXEC_BACKEND), which makes the code yet more ugly. I 
think we could make this a lot cleaner.

I'd like to define an API like so:

pid_t fork_process(int proc_type);
pid_t fork_backend(Port *port);

If the process needs to add a lot of private information to the argv in 
the case of EXEC_BACKEND, they could invoke a third variant:

#ifdef EXEC_BACKEND
pid_t forkexec_process(int proc_type, int argc, char **argv);
#endif

(Or possibly using varargs, if that is cleaner for most call-sites). 
Hopefully most call sites could just use fork_process().

These functions would then take care of all the necessary 
platform-specific judo:

- flush stdout, stderr
- invoke BeOS hooks as necessary
- save and restore profiling timer, if necessary
- if EXEC_BACKEND, use proc_type to lay out the argv for the new process 
and then invoke internal_forkexec()
- otherwise, just invoke fork()
- return result to client

So, most call sites would be quite nice:

pid_t result = fork_process(PROC_TYPE_FOO);
if (result == -1) { /* fork failed, in parent */ }
else if (result == 0) { /* in child */ }
else { /* in parent, `result' is pid of child */ }

I'd also like to move the implementation of fork_process() and friends, 
as well as internal_forkexec(), into a separate file -- I'd rather not 
clutter up postmaster.c with it.

Comments?

-Neil


Re: refactoring fork() and EXEC_BACKEND

From
"Magnus Hagander"
Date:
>While going through the usual motions needed to fork a child
>process of
>the postmaster, it occurred to me that there's a fair bit of
>duplicated
>code involved. There are also #ifdef for various situations (BeOS,
>LINUX_PROFILE, and EXEC_BACKEND), which makes the code yet
>more ugly. I
>think we could make this a lot cleaner.
>
>I'd like to define an API like so:

This is a lot like what I was planning to work towards with the
refactoring of the forkexec code I promised to do for 8.1. Glad to hear
you think in the same direction.


>pid_t fork_process(int proc_type);
>pid_t fork_backend(Port *port);
>
>If the process needs to add a lot of private information to
>the argv in
>the case of EXEC_BACKEND, they could invoke a third variant:
>
>#ifdef EXEC_BACKEND
>pid_t forkexec_process(int proc_type, int argc, char **argv);
>#endif
>
>(Or possibly using varargs, if that is cleaner for most call-sites).
>Hopefully most call sites could just use fork_process().

I was actually thinking of not passing these on the commandline at all,
in order to avoid possible quoting issues etc (recall all the problems
with the stupid commandline processing on win32). Instead moving it into
a struct that is appended to the end of the backend variable file/shared
memory.


<snip>

>So, most call sites would be quite nice:
>
>pid_t result = fork_process(PROC_TYPE_FOO);
>if (result == -1) { /* fork failed, in parent */ }
>else if (result == 0) { /* in child */ }
>else { /* in parent, `result' is pid of child */ }

You're not going to be able to get the "in child" there for an execed
process, are you? it has to be called somewhere in the new process, and
thus it would have to be a function, wouldn't it?


>I'd also like to move the implementation of fork_process() and
>friends,
>as well as internal_forkexec(), into a separate file -- I'd rather not
>clutter up postmaster.c with it.

That was also what I was thinking. Let me know if you want to split the
load somewhere :-)


//Magnus


Re: refactoring fork() and EXEC_BACKEND

From
Neil Conway
Date:
Magnus Hagander wrote:
> This is a lot like what I was planning to work towards with the
> refactoring of the forkexec code I promised to do for 8.1.

Cool. BTW, have we accepted that EXEC_BACKEND is the way we're going to 
workaround the lack of fork() on Win32 for the foreseeable future? I 
mean, it _works_, but it's slow, ugly, and complicates the code. If it's 
the only workable option for Win32 support, then fair enough -- I just 
don't know enough of the Win32 API to know if there's a better 
alternative out there (short of using threads, which is of course not 
really plausible).

> I was actually thinking of not passing these on the commandline at all,
> in order to avoid possible quoting issues etc (recall all the problems
> with the stupid commandline processing on win32). Instead moving it into
> a struct that is appended to the end of the backend variable file/shared
> memory.

Sounds good to me. Finding a cleaner way to pass data to the child 
process than writing it out to a file would also be nice, if possible. 
Again, I'm not sure what options there are on Win32...

> That was also what I was thinking. Let me know if you want to split the
> load somewhere :-)

Given that you're planning to work on this, I've scaled back my 
ambitions. I'll send a patch to -patches that just cleans up fork() and 
doesn't change the EXEC_BACKEND case. So fork_process() will:

- flush stderr/stdout
- save and restore the profiling timer if LINUX_PROFILE is defined
- handle BeOS

which means it should not be very invasive. Of course, there is plenty 
of room for improvement -- if you're interested in taking a look, please 
do...

-Neil


Re: refactoring fork() and EXEC_BACKEND

From
"Magnus Hagander"
Date:
>> This is a lot like what I was planning to work towards with the
>> refactoring of the forkexec code I promised to do for 8.1.
>
>Cool. BTW, have we accepted that EXEC_BACKEND is the way we're
>going to
>workaround the lack of fork() on Win32 for the foreseeable future? I
>mean, it _works_, but it's slow, ugly, and complicates the
>code. If it's
>the only workable option for Win32 support, then fair enough -- I just
>don't know enough of the Win32 API to know if there's a better
>alternative out there (short of using threads, which is of course not
>really plausible).

I don't beleive there is any other way than using threads. The only
"objects" you can create are processes and threads, and I don't know
there to be any other way to create a process than CreateProcess().


>> I was actually thinking of not passing these on the
>commandline at all,
>> in order to avoid possible quoting issues etc (recall all
>the problems
>> with the stupid commandline processing on win32). Instead
>moving it into
>> a struct that is appended to the end of the backend variable
>file/shared
>> memory.
>
>Sounds good to me. Finding a cleaner way to pass data to the child
>process than writing it out to a file would also be nice, if possible.
>Again, I'm not sure what options there are on Win32...

Win32 already passes it using shared memory. It was when I asked to get
that patch in during beta (or possibly even RC) that I promised to work
on the cleanup stuff for 8.1. For unix/exec_backend it still writes to a
file, but since that is never expected to be used in production where
performance is an issue...

I think that is a fairly clean way of doing it. You could pass it
through a pipe or something, but I don't see that it would be a cleaner
approach. You're still going to have a single place collecting all the
data, which is where most of the uglyness comes from.


>> That was also what I was thinking. Let me know if you want
>to split the
>> load somewhere :-)
>
>Given that you're planning to work on this, I've scaled back my
>ambitions. I'll send a patch to -patches that just cleans up
>fork() and
>doesn't change the EXEC_BACKEND case. So fork_process() will:
>
>- flush stderr/stdout
>- save and restore the profiling timer if LINUX_PROFILE is defined
>- handle BeOS
>
>which means it should not be very invasive. Of course, there is plenty
>of room for improvement -- if you're interested in taking a
>look, please
>do...

Ok. I'll look at it once your stuff is done.

//Magnus