> me. How exactly is (fork) working? The reference example:
> (unless (fork) (do 5 (println 'OK) (wait 1000)) (bye))
> seems to imply that the current expression, ie. (unless ... ) is being
> rerun in the child (becoming the contents of *Fork) whilst execution
> continues to the (bye) in the parent. The (fork) call will return NIL
> in the child so the (do ... ) expression is being run there. It also
> seems to imply that the (bye) expression waits for the child to finish
> before shutting down?
This description is not correct in some points.
The PicoLisp 'fork' maps to the underlying fork() system call. When
fork() is called, an new process is created, and both processes (the
parent and the child) continue to execute from that point. At that
moment, both processes are identical, the only difference is that in the
parent process fork() returns the child's process ID, while in the child
0 is returned.
The PicoLisp (fork) function returns a number (the child's PID) in the
parent process, and NIL in the child process. So in the above example
is executed in the current process (which will be the "parent"
thereafter), and (fork) returns the PID of the child. This is non-NIL,
so the body of the 'unless' call is skipped by the parent, and it will
continue execution with the next expression (not shown here).
In the child process, (fork) returns NIL, and the body of the 'unless'
call is executed. It consists of the two expressions
(do 5 (println 'OK) (wait 1000))
This is the code the child process sees. It prints five times 'OK' and
then exits (terminating the child process).
Note that the (bye) is probably important, because otherwise the child
would continue with the next (not shown) expression which was already
executed by the parent 5 seconds before.
Thus, (unless (fork) (do-something) (bye)) is a typical pattern to have
(do-something) done in a child process. In that regard, the formulation
"(unless ..) is being rerun in the child" is a bit misleading, as the
'unless' is begins execution only in the parent, but after its condition
(the (fork)) is evaluated, we suddenly have two processes which execute
different branches in the code (the next statement in the parent, and
the body of 'unless' in the child). That's the reason for the name of
that system call, "fork".
Then the formulation "(becoming the contents of *Fork)" is not correct.
The global variable '*Fork' is not changed in that process, it may be
set under program control _before_ the call to (fork) to contain
expressions which should be executed in all child processes. Perhaps you
meant '*Pid'? This is changed to a new value in the child process.
Also, "whilst execution continues to the (bye) in the parent" is not
right, as the parent never sees the (bye). It is only there for the
child to terminate after it did its job.
> Anyway, having a wait call like that as is the case in my code could
> result in various race conditions, according to Alex he had to make
> (wait) "smart" in order to avoid common cases, I'm sure he can
> elaborate further on the details... In any case it works in my case
Yes, though I would not say that there are race conditions.
'wait' is actually the PicoLisp "Event Handler". It can be called
explicitly, and is called implicitly by other functions (see the
reference of '*Run').
This event handler takes care of several things, listening on file
descriptors and watching timeout values. This includes all expressions
in '*Run' (typically installed and de-installed by 'task'), but also
internal events resulting from interprocess communication: The parent
relays 'tell' messages from a child to all other children, accepts
'boss' commands (via 'hear'), detects when child processes terminate to
clean up their resources, and is needed to synchronize the database
between all child processes.
So typically 'wait' processes a list of events. Now you may well call
another 'wait' in a task, and this in fact happens frequently during
database synchronization between child processes, or when GUI functions
execute. However, 'wait' must ensure that while processing a list of
events, a recursively called 'wait' does not re-execute the same event a
second time. For that, the inner 'wait' takes care not to look at the
events in the outer 'wait', and this may result that outer events will
never get executed if the inner 'wait' is run in a loop in some task.
Anyway, this may get rather confusing, and isn't the way 'task's are
intended. I mean, instead that a task establishes its own timing loop
using nested 'wait's, one central capability of tasks _is_ being a
timer. That's why I recommended not to write such a loop in the task,
but execute the checking for that child process periodically with.
> but in order to avoid using (wait) Alex offered me the following
> (de updatePresentFeeds (Feeds)
> (task -1000 0
> Pid NIL
> Cycles 0
> Feeds Feeds
> ((nand Pid (kill Pid 0))
> (if (pop 'Feeds)
> Pid (or (fork) (updatePresentFeed @))
> Cycles 30 )
> (task -1000) ) )
> ((=0 (dec 'Cycles))
> (and (kill Pid) (off Pid)) ) ) ) )
> The main thing here is to initiate a task that is being run every
> second, when all feeds have been imported we terminate through the
> (task -1000) call. The first condition will be run initially (Pid is
Correct. This will start a task which runs once a second, and has three
local variables Pid, Cycles and Feeds (a closure).
> The problem: It won't run at all, not even once and I have no idea
> why, it all looks good to me.
Strange. I cannot see at the moment what might be the problem.
To test it locally, I tried the following, using a stub function for
(de updatePresentFeed (F)
(msg F '<feed> *Pid)
(de updatePresentFeeds (Feeds)
(task -1000 0
((nand Pid (kill Pid 0))
(if (pop 'Feeds)
Pid (or (fork) (updatePresentFeed @))
Cycles 30 )
(task -1000) ) )
((=0 (dec 'Cycles))
(and (kill Pid) (off Pid)) ) ) ) )
(updatePresentFeeds (1 2 3 4))
This seems to work, printing the numbers 1 .. 4 (the "feeds") and the
PIDs of the individual child processes:
What exactly goes wrong?