"fork" command?

Mark Waddingham mark at livecode.com
Fri Jan 8 04:47:59 EST 2016


On 2016-01-07 20:03, Richard Gaskin wrote:
> I'm just far enough into Robert Love's "Linux System Programming" that
> I think the solution to FastCGI may be much simpler than I'd
> previously thought.

I think you need to read a bit more about fork ;)

> I think we need a new command that launches a specified process but in
> a way that uses a call to "fork" to pass file descriptors (which
> include sockets and other I/O info) to the child process.

The 'fork()' system call is a very low-level primitive which is the 
basis of executing other processes on UNIX based systems. You have to 
use it very carefully - indeed, pretty much any use of fork will almost 
immediately be followed by a call to some variant of 'exec', which 
basically runs a different executable in the current process, replacing 
the original one.

> In many ways it would work very similarly to the existing "open
> process", but allow params to give the child process access to things
> like socket connections, pipes, files, etc. the parent process has
> access to at the time the child process is launched.

Well 'gives access to' is slightly misleading here as it suggests that 
you can use them all, which isn't really true. When you fork the kernel:

1) clones the task structure (the thing the kernel uses to represent a 
process)

2) marks all memory pages as copy on write

3) increases the reference count on every thing attached to a file 
descriptor

This means that when the child process starts running its memory image 
is identical to the parent, and it has *exactly the same* file 
descriptors as the parent.

Critically, any state which the original process has related to 
connections to things (e.g. databases or display servers) is the same in 
parent and child, which means they have the same 'state' on the other 
end of the connection. This means that any usage of them in either after 
the fork will cause things to break, probably horrendously as the two 
processes run asynchronously.

This means that you need to engineer things so that the child gets 
appropriate fd's and the parent keeps appropriate fd's. The typical 
thing you do here is faff around with pipes. For example, here is the 
critical bit of code from the engine's open process command:

         int tochild[2];
         int toparent[2];
         int4 index = MCnprocesses;
         if (pipe(tochild) == 0)
         {
             if (pipe(toparent) == 0)
             {
                 MCU_realloc((char **)&MCprocesses, MCnprocesses,
                             MCnprocesses + 1, sizeof(Streamnode));
                 MCprocesses[MCnprocesses].name = strclone("shell");
                 MCprocesses[MCnprocesses].mode = OM_NEITHER;
                 MCprocesses[MCnprocesses].ohandle = NULL;
                 MCprocesses[MCnprocesses].ihandle = NULL;
                 if ((MCprocesses[MCnprocesses++].pid = fork()) == 0)
                 {
                     close(tochild[1]);
                     close(0);
                     dup(tochild[0]);
                     close(tochild[0]);
                     close(toparent[0]);
                     close(1);
                     dup(toparent[1]);
                     close(2);
                     dup(toparent[1]);
                     close(toparent[1]);
                     execl(MCshellcmd, MCshellcmd, "-s", NULL);
                     _exit(-1);
                 }
                 MCS_checkprocesses();
                 close(tochild[0]);
                 char *str = path2utf(ep.getsvalue().clone());
                 write(tochild[1], str, strlen(str));
                 delete str;
                 write(tochild[1], "\n", 1);
                 close(tochild[1]);
                 close(toparent[1]);
                 MCS_nodelay(toparent[0]);
                 if (MCprocesses[index].pid == -1)
                 {
                     if (MCprocesses[index].pid > 0)
                         MCS_kill(MCprocesses[index].pid, SIGKILL);
                     MCprocesses[index].pid = 0;
                     MCeerror->add(EE_SHELL_BADCOMMAND, 0, 0, 
ep.getsvalue());
                     return IO_ERROR;
                 }
             }
             else
             {
                 close(tochild[0]);
                 close(tochild[1]);
                 MCeerror->add(EE_SHELL_BADCOMMAND, 0, 0, 
ep.getsvalue());
                 return IO_ERROR;
             }
         }


> But for those of you more familiar with Linux system programming, do I
> misunderstand the difficulty involved?

Yes - fork() isn't what you are looking for. It isn't magical - it does 
*precisely* what it says it does which is insufficient for what you are 
proposing (and isn't what it is really used for anywhere).

> Forking seems so common in other tools, and not having it appears to
> be the one detail standing between where we are now and having not
> just FastCGI, but also being able to build truly excellent application
> servers on par with Node.js and other similar systems.

I'd say forking (except the purpose of implementing the equivalent of 
shell or open process) is actually very rare. Any language which offers 
bindings to system libraries and such will likely have os.fork() or some 
such for completeness - however this doesn't mean it is actually used 
very much (nor should it - there are few patterns around fork which are 
safe and generally useful - shell and open process pretty much cover 99% 
of them).

> LiveCode is a great language, and if we had the ability to fork we
> should be able to build a wide range of powerful, scalable, efficient
> systems, breaking far beyond the limitations of CGI we're limited to
> now.

Using LiveCode in the contexts you are talking about doesn't require 
fork().

LiveCode can almost be used as a Node.js type server already - start a 
-ui standalone engine running which 'accepts connections' and dispatches 
requests for appropriate protocols out to whatever you need (the missing 
bit, as I've mentioned before is that some 'long' running things such as 
db access block, rather than do things asynchronously). Like other 
Node.js style setups, I'd imagine you'd need some sort of 
'load-balancing' magic to farm the Node.js-like processes which farm the 
requests - I'm sure others know more about this than me.

Adding FastCGI support to LiveCode server would also be a similar 
solution (where the blocking nature of some aspects of LiveCode wouldn't 
matter so much). When you run this through something such as mod_fcgi, 
you get the 'farming' of processes for free, so in LiveCode you only 
have to worry about servicing the requests, rather than how processes 
get started up and shutdown and used.

Basically, managing collections of processes to fulfil CGI-like requests 
(which is what fork() - because it is what you use to do open process - 
is used for in these contexts) is already a solved problem - there is no 
need for it to be solved again.

> If all we need is a new command to wrap the Linux "fork" call, after I
> finish Love's book I may brush up on my C skills and give it a go.

No need to 'brush up' on your C skills. Something along the lines of the 
following in LiveCode Builder should work:

library module org.livecode.fork

foreign handler _Fork() returns CInt binds to "fork"

public handle Fork()
   return _Fork()
end handler

end module

Although, as mentioned above, it won't get you very far for the reasons 
outlined above.

Warmest Regards,

Mark.

-- 
Mark Waddingham ~ mark at livecode.com ~ http://www.livecode.com/
LiveCode: Everyone can create apps




More information about the use-livecode mailing list