Dear Faré, dear Robert, dear list,

> On 15 Aug 2016, at 15:25, Elias Pipping <pipping.el...@icloud.com> wrote:
> 
>>>> * Also, is #+lispworks7 future-proof? Is there a feature for 7-or-later?
>>> 
>>> I don’t think so, no. It seems that each version has a feature 
>>> corresponding to its own version but not others. We could maybe provide our 
>>> own, though. We cannot know what versioning scheme they’ll use in the 
>>> future but it shouldn’t be too difficult to find out what features 
>>> lispworks has defined in the past. So a
>>> 
>>> (lispworks7-or-greater-p)
>>> 
>>> could probably be implemented as
>>> 
>>> (not (or #+(or lispworks4 lispworks5 lispworks6) t))
>>> 
>>> or something along those lines.
>>> 
>> Yuck. I'd rather query SYSTEM::*MAJOR-VERSION-NUMBER* in a #..
> 
> I didn’t know about that variable. The resulting code would be nicer but it’s 
> still a private symbol which might disappear in the future. Personally, I’d 
> prefer to go with the uglier but more official version.

Indeed, http://www.lispworks.com/documentation/lw70/LW/html/lw-373.htm 
explicitly mentions that

(defvar *feature-added-in-LispWorks-7.0*
  #+(or lispworks4 lispworks5 lispworks6) nil
  #-(or lispworks4 lispworks5 lispworks6) t)

is the proper way to check for LispWorks 7.0 and above. Based on that, I’ve 
added

   (defmacro %if-on-lispworks7+ (action-if action-else)
     #+(and lispworks (not (or lispworks4 lispworks5 lispworks6))) action-if
     #-(and lispworks (not (or lispworks4 lispworks5 lispworks6))) action-else)

in 
https://gitlab.common-lisp.net/epipping/asdf/commit/cbeb4b3c75ad777f668a48d09b874ae0e4cc16ed
 and started using it. The name might not be ideal.
We might also want to replace this with a more general version checking macro 
for LispWorks in the future but I don’t yet see the need for that.
I wasn’t sure where to put it: os.lisp is currently most fitting but still 
wrong and I was reluctant to add a new module so I left it in run-program.lisp 
for now.

>>> (A) The first is with the :overwrite default for :if-output-exists. [...]
>>> So since all [implementations] appear to support the behaviour of 
>>> :supersede (even though not necessarily by that name) and not all of them 
>>> support :overwrite, it might make sense to change the default from 
>>> :overwrite to :supersede and then (like other normaliser functions) have a 
>>> translator that turns :supersede into :overwrite for CLISP. This would 
>>> unfortunately also affect the (exported) run-program function. On the plus 
>>> side, it would only change behaviour that previously could not be relied 
>>> upon anyway.
>>> 
>> Yes, that would be the Right Thing(tm).
>> The entire purpose of UIOP is to provide some portable abstractions
>> with stable semantics abstracting over implementation specifics,
>> rather than functions the meaning of which is actually not portable.
>> See my ASDF3 essay on this topic.
>> 
>>> (B) Process termination
>>> 
>>> Even though it’s possible to kill a process on nearly ever platform somehow 
>>> (not on CLISP, except for processes spawned through ext::launch) there are 
>>> multiple tiers of support for that: From native to external: If you have a 
>>> process in MKCL, you can terminate it through its process object, on unix 
>>> or windows. That’s the ideal situation.
>>> 
>> If it helps with CLISP, maybe ext::launch should be the default on CLISP.
>> I admit I don't care too much about CLISP, that hasn't been actively
>> maintained for years.
>> (It still ships with ASDF 2.33, for John McCarthy's sake.)
> 
> It appears there are some attempts to breathe new life into CLISP, though. 
> See e.g.
> 
>  https://sourceforge.net/u/musteresel/clisp/
> 
>>> The worst-possible situation that can still be handled if needed is when 
>>> all you have is the PID: you end up calling `taskkill` on windows, through 
>>> cmd.exe, or `kill` on unix, through the shell. I only recently started 
>>> thinking about potential issues that this might involve other than 
>>> performance, not only in this extreme case but also in cases that lie 
>>> half-way between the MKCL situation and the worst-possible scenario, e.g. 
>>> SBCL’s.
>>> 
>>> If you obtain the PID of an arbitrary external process and then try to kill 
>>> it through that PID at a later point in time, the process may no longer be 
>>> running. It may not even exist anymore. Worse yet, its PID may have been 
>>> recycled by the system and assigned to a newly spawned process that you end 
>>> up killing by accident!
>>> 
>> Note that on Unix, the race condition between killing a process
>> and the process dying then its PID being recycled is inherent,
>> whatever abstraction MKCL may try to provide that SBCL doesn’t.
> 
> Yes, but we’re not dealing with this general problem as I tried to outline in 
> the paragraph after that (quoting myself):
> 
>  Fortunately, I am not trying to solve this general problem but the simpler 
> problem of killing a process spawned through run-program, which will 
> necessarily be a child process. Such a process will signal its parent with 
> SIGCHLD when it terminates and stick around as a zombie until it is waited 
> for.
> 
> So the issue is solvable here, no?

I still think that killing a child process can be safe if the SIGCHLD handler 
(which would call wait()) acquires the same lock as the process killing 
function. But maybe I’m missing something.

>> The only mitigating behavior would be for the kernel to insert a pause
>> before a PID could be used, and hope that programs never try kill a PID
>> more than that pause duration after having checked the process was alive
>> (and even then, if the process is kill -STOP'ped between the check and the 
>> kill,
>> there's nothing that can prevent the race condition).

From my understanding, the user has control over this pause: Until he or she 
calls wait(), the process will be in zombie state and its PID will be 
unavailable to the OS for recycling, no?

>> Of course, 16-bit PIDs make the situation particularly tight on Unix
>> 
>>> ((B') I’m not sure what the best way to handle this requirement of reaping 
>>> is. %wait-process-result currently calls both functions, so it would be 
>>> possible to require that asynchronously spawned processes are waited on at 
>>> some point, mirroring to some extent the requirement that every C program 
>>> that calls fork() should call wait() at some point. So the name would be 
>>> rather appropriate even.)
>>> 
>> Yes, it is fair to require that asynchronous UIOP:RUN-PROGRAM users
>> should always call wait-process (or whatever you call the API
>> function) and not rely on the implementation doing it for them.

I’ve now documented this. Indeed, there’s documentation for

- the now exported function wait-process-result: 
https://gitlab.common-lisp.net/epipping/asdf/commit/7243fe25bb8bf9164d3bd659f42d146fcb87174b
 - the new, exported terminate-process: 
https://gitlab.common-lisp.net/epipping/asdf/commit/fa289e53700d92ff8f7c2da38b98b0d22832cee6
 - the new, exported process-alive-p: 
https://gitlab.common-lisp.net/epipping/asdf/commit/4a1339e8e8ce83408251c9264a7d8b5fe5393976
 - the new, exported process-close: 
https://gitlab.common-lisp.net/epipping/asdf/commit/0cc3f20e7fcc1ae9250ce241f1ac2741b0e26094

I’m not yet sure what the best way to export the asynchronous process spawning 
functionality is. %run-program is not supposed to replace run-program after 
all. So I either need to extend run-program or add another wrapper that calls 
%run-program in a different fashion. In terms of return values, the latter 
would be more consistent: run-program would return an exit code and the new 
function (`start`?) would return a process-info object.

I’ve also carried out miscellaneous smaller fixes(*). Parameter errors now 
occur early and in the same place: 
https://gitlab.common-lisp.net/epipping/asdf/commit/cf47cd04a509cd923f2368f9840492b26ed764aa

I’ve plugged the test into the existing test suite, so that it can be run e.g. 
via

  make; ./test/run-tests.sh sbcl run-program-unix-tests.script

I’ll still have to mark all the known failures as such. Come to think of it, I 
should probably handle those known issues not just in the test suite but also 
in run-program itself, albeit maybe through another error class (e.g. 
'known-bug rather than 'parameter-error). Or maybe not (does it help to know 
that a feature is not missing but broken on your platform? it doesn’t really…).

(*) Especially after all the rebasing, I think the merge request at

  https://gitlab.common-lisp.net/asdf/asdf/merge_requests/3

continues to be the best way to inspect the changes I’ve made, be it commit-by 
commit or in terms of overall changes.


Elias

Reply via email to