Hi Peter,

On 5/19/2015 4:34 AM, Peter Levart wrote:
Hi Roger,

On 05/18/2015 11:49 PM, Roger Riggs wrote:
The earlier spec specifically used the common pool but from a specification point of view it allows more flexibility in the implementation not to bind onExit to the commonPool. Its not clear that the attributes of threads used to handle output or managing Processes
are the same as those attributed to the commonPool.

The default sizing of the commonPool is based on the number of processors
and does not grow (except via the ManagedBlockers) mechanism.
The number of threads waiting for processes is unrelated to the number of CPUs. It is simpler to maintain to use a separate pool and not depend on consistent
assumptions between Process handling and the commonPool.

I'm not advocating to use commonPool for waiting on the process exit(s). We have special low-stack-size reaper threads for this purpose and in future there could be just one thread maybe. I'm also not advocating to use commonPool in default Process.onExit() implementation where the thread must wait for process to exit. I'm just questiong the use of commonPool vs. AsyncExecutor in ProcessImpl.onExit() and ProcessHandleImpl.onExit() where there is an async CompletableFuture stage inserted to shield from exposing internal low-stack-size threads.

The code that is executed by commonPool (or AsyncExecutor) by onExit() continuations doesn't wait for process to exit. The number of processes spawned does not matter directly. Only the rate of process exits does. The code executed will be a cleanup-type / post-exit-actions-type of code which seems to be typical code for commonPool. Code dealing with process input/output will typically be executed concurrently with the Process while the process is still running and not by onExit() continuations.
makes sense


On 05/18/2015 11:49 PM, Roger Riggs wrote:
One alternative is for onExit to return a CF subclass that maps synchronous methods to the corresponding async methods. Each call to onExit would return a new proxy that would delegate to a new CF created from the ExitCompletion.handle(...). For all requests that involved calling app code it would delegate to the corresponding
async method.  For example,

        CompletableFuture<ProcessHandle> cf =
                ProcessHandleImpl.completion(getPid(), false)
                    .handle((exitStatus, unusedThrowable) -> this);
        return new CompletionProxy(cf);

The CompletionProxy can ensure that all CFs that call application code are Async. It would override the syncronous methods and redirect to the corresponding Async method.

public <U> CompletableFuture<U> thenApply(Function<? super ProcessHandle, ? extends U> fn) {
            return thenApplyAsync(fn);
        }

The CompletionProxy can also supply the Executor as needed unless supplied by the application

It adds a simple delegation but removed the extra task dispatch except where needed.

Does that work as you would expect?

Thanks, Roger

I thought about that too. Unfortunately, there are also xxxAsync(..., Executor) methods that appear to attach asynchronous continuations. And they do, if passed-in Executor dispatches to separate threads. But users can pass in a "SynchronousExecutor" like the following:

    public class SynchronousExecutor implements Executor {
        @Override
        public void execute(Runnable command) {
            command.run();
        }
    }

...which makes xxxAsync(..., Executor) methods equivalent to synchronous-continuation-attaching methods.
It might be a bit interventionist, but its possible check the concrete type of the executors and allow direct use of those that are built-in; or to replace unknown ones with a built-in. It would be lighter weight than always queuing a task and potentially starting up a new thread.

It would be nice to have an API that guaranteed division of threads between internal-code and user-code threads, but such API would have to be constructed around something different than Executor interface. I think what we have with insertion of an async stage is the best we can do with CompletableFuture API.
ok, the implementation can be updated if something new becomes available.

The only question remaining is whether to use AsyncExecutor in ProcessImpl and ProcessHandleImpl .onExit() methods for executing the inserted async stage (and any synchronous continuation of it), or only in default implementation of Process.onExit() for waiting on process exit. The AsyncExecutor properties are such that it doesn't pre-start any threads and if not used, doesn't represent any system overhead. This is good if it is normally not used and would make it a good candidate for default Process.onExit() implementation which is used only in uncommon custom Process implementations. Using it in ProcessImpl and ProcessHandleImpl .onExit() methods makes it being used after each onExit() call, albeit potentially only briefly, so there's a chance that only one thread will ever be started and will die after every 60s of inactivity. commonPool() is no different from that perspective, but there is a chance, being a common pool, that it is used for other purposes too, so potentially less thread starts and stops can be achieved.
Makes sense
There's also the lack of uniformity if AsyncExecutor is used which can have surprising effects. For example, a user switches from using synchronous onExit() continuation to asynchronous one and as a result the executor changes which can results in different behavior.
That argument would inhibit changing the implementation since there is an implicit dependency
on the implementation of the Executor.

I would be *for* using the AsyncExecutor in Process[Handle]Impl if it could be set as default Executor for any continuation born from returned CompletableFuture transitively. There were some changes announced from jsr166 team that would allow that (subclassing CF so that the methods returned a subclass of CF), so this could perhaps be the way to go...
A factory for the new CF's that are returned from CF would be quite useful if/when that comes to pass.

I've updated the implementation to use the commonPool for ProcessHandleImpl and the ProcessImpls
and retaining the asyncExecutor pool for Process.onExit.

Thanks, Roger


Regards, Peter


Reply via email to