Hi Peter,

On 3/10/2015 2:57 PM, Peter Levart wrote:
Hi Roger,

Thanks for bearing with me...
And me, as I find the route among the language, type system, compatibility issues,
and os intricacies.

On 03/10/2015 06:27 PM, Roger Riggs wrote:
I think current ProcessHandle is a mix of specification and partial implementation that does not help in any way to hypothetical external implementations. It effectively disables any consistent alternative implementations. Take for example the following method in ProcessHandle:

    public final ProcessHandle parent() {
        return ProcessHandleImpl.parent(getPid());
    }
ProcessHandle is not intended to be *externally* extended; as an interface or abstract class

But Process is, and Process inherits methods from ProcessHandle which means that you are limiting external implementations of Process forever to status-quo functionality. Why not let the implementor of external Process implementation decide whether it needs or wants to implement this new functionality or not. Are you afraid that implementations will emerge, but will not be of enough quality because of impedance missmatch between the API and the target platform? Or are you afraid that it will be harder to support new additions to this API if there are external implementations created?
I'm reluctant because we're having to speculate about the details of what might be with no one to speak directly to any particular target OS issues and not much evidence that it is needed.



it can not make *any* guarantees about the implementation.
No caller can depend on any of the behavior based on the compile time type. With the changes proposed, the callers using ProcessHandle must be prepared to handle UOE in addition to dealing with the normal absent or missing information from the OS.

So let's say the caller is on a platform that is not supported by JDK internal ProcessHandle implementation. What guarantees does ProcessHandle implementation give him? It think he's in better position if he must cope with differences than he is if he can't use the API at all.
Perhaps

UOE from getPid() is just a form of absent information. It could be -1.
This choice goes to the design of the caller; is it structured to handle exceptions
or willing to check the validity of the values.
Many have complained that code to handle exceptions makes less code unreadable
and should be avoided.


We should not open up another hole like Process without fully understanding the use cases and the requirements on extensibility. There may be a middle ground
that leaves the door open but not to start with.

The hole is already open. Some external implementation of Process can already attempt to implement all the ProcessHandle instance API merely by subclassing the Process. Package-private constructor on ProcessHandle will not stop that. Final methods will though.
The argument is frequently made about making the surface area of the API larger
without taking into account who pays the cost and who gets the benefit.


There's no way an alternative implementation of ProcessHandle could be created that returned alternative implementations using method parent(). What if alternative implementation doesn't support getPid()?
I have not considered supporting alternate implementations a goal but not breaking existing ones is essential.

Well, I don't see anything existing is broken if ProcessHandle doesn't impose final methods.
True for existing API; but then the salient discussion is about the semantics of the new functions and how they can be expected to adhere to the contract if extensibility is supported.



I think ProcessHandle should be mostly a specification and only have abstract methods. Only a method that does not depend on implementation could be concrete. I think it could be an interface. A package-private constructor does not help as there is a Process class that has a public constructor already, so anyone can create arbitrary instances of arbitrary subclass of Process which is-a ProcessHandle...
Most of the behavior of ProcessHandle (as is Process) *is* implementation specific due to the OS dependencies. When every function bottoms out in OS behavior, the layers above are dependent too.

So this is a question of: "Is ProcessHandle API suitable for other implementations or is it just for supported UNIX/Windows platforms", right?

Because technically it could be exposed as externally implementable with no backwards compatibility problems when 1st introduced. There might be problems later when extending it.
That's the risk. Can it be eliminated or avoided... Is it a necessary unknown?



I now see Process.getPid was previously added and it throws USO by default. So we might be stuck due to the tricky nature of this area.

Unfortunately. Default methods (in publicly extendable abstract classes and interfaces) can not provide new functionality. They can just re-pack existing functionality. getPid() is new functionality and so are all new methods of ProcessHandle that Process inherits from. So we can not just simply delegate to our implementation which might be incompatible (unsupported platform).
Yes, but to support source level compatibility; some implementation is needed; though it cannot provide the full functionality.

There's no source-level compatibility issue with ProcessHandle as it is new class or interface. Just with Process. And even with my changes, the source-level compatibility is maintained. Can you spot anything that is not source-compatible or does not behave like it used to? It actually behaves exactly like it behaves with your latest implementation when default Process.getPid() throws UOE. The only difference is that ProcessHandle is made extensible.
Just the aforementioned risk to maintenance cost



Any sub-type of Process that does not override getPid will essentially result in that USO being propagated to many ProcessHandle methods that depend on the PID (parent, children, allChildren, info, compareTo). That effectively renders ProcessHandle almost useless for sub-types outside of our control that that not been updated.

So here's some changes I propose to the tip of JDK-8046092-branch that hopefully fix these issues:

http://cr.openjdk.java.net/~plevart/jdk9-sandbox/JDK-8046092-branch/webrev.02/

The changes are:
- Process.getPid() is back to throwing UOE if unsupported
- ProcessHandle is all-abstract interface (only allChildren() is a default method, since it is just a utility independent of implementation)
I considered 'abstract' in the PH API to be a distraction unless the API was intended to be extensible.

Well in that case, yes. But why is it ProcessHandle then split between ProcessHandle and ProcessHandleImpl? Isn't that a distraction too?
It kept the bulk of the implementation details out of the API and made them available to the ProcessImpls. I'm still working on the organization of the implementation; there should be a clean split between ProcessImpl's that spawn processes and ProcessHandleImpl for the other functions.


- ProcessHandle.compareTo() specifies that only same-implementations of ProcessHandle(s) are mutually comparable otherwise ClassCastException must be thrown
Yes, avoids the issue with UOE.
- ProcessHandleImpl.onExit() and ProcessImpl.onProcessExit() propagate error from waitForProcessExit0 native method as exceptional completion of CompletableFuture. exitValue is set to -1 on UNIX in this case (instead of 0). What's the exitStatus on Windows in this case? This area needs more work, I think.
The exceptional case was removed because the waitForExit does not throw an exception. The only exception from Process.waitFor is InterruptedException and since there might
be spurious exceptions it should be retried.

When waitForProcessExit0 returns the process is gone; and the CF can be completed.

So there's no error condition from waitForProcessExit0() then. Any return *is* exitValue(). I haven't checked the native implementations and just relied on comments in Java code.
There are a variety of OS level possible error conditions but either the process still exists in which case it should still wait; or it is done and the exitValue (or not-available value)
is returned.


- Some tweaks to javadocs in a few places


Is it common to sub-type Process? If so then perhaps Process should not extend ProcessHandle and instead there should be a way to view a Process as a ProcessHandle, whose default implementation is:

   return ProcessHandle.of(getPid()); // throws USO if getPid does

(java.lang.ProcessImpl could implement Processhandle thereby the implementation is "return this".)

Thus once you have a ProcessHandle instance (from whatever source) it's possible to operate on it without fear of a USO.


A possible default implementation for Process.onProcessExit could be:

return CF.supplyAsync(() -> { this.waitFor(); return p}, cachedThreadPool);

It could be, but this would encourage external implementations to not override it and this implementation is not very efficient in terms of thread stack sizes. We could use a default implementation that is similar to Windows' ProcessImpl - only use waitFor() instead of ProcessHandleImpl.waitForProcessExit0() native method, but on UNIX waitFor() *is* implemented on top of this implementation, so I think the best is to force the implementor to do it optimally from the ground-up. Default methods should really be reserved for something very trivial that is not only implementation independent, but also equally efficient regardless of underlying implementation.
Without external implementations, the code moved up from ProcessImpl and ProcessHandleImpl classes
to the abstract class to avoid duplication in the subclasses.

ProcesshandleImpl and Windows ProcessImpl can have same code, but UNIX ProcessImpl must additionally synchronize using waitFor() since on UNIX, exitValue() result is published by the continuation of completion() which can execute concurrently with asynchronous continuation of same completion() that completes user-visible CompletableFuture. There's no harm if synchronization using waitFor() is used in ProcesshandleImpl and Windows ProcessImpl too, but that's unneeded overhead.
yes, on Unix, it provides synchronization between the CF completions.
On Windows, it is redundant since by the time that code is executed, the process is already known to be done.

Thanks, Roger


Reply via email to