RFR 9: 8077350 Process API Updates Implementation Review

Roger Riggs Roger.Riggs at Oracle.com
Tue May 19 15:48:14 UTC 2015


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
>




More information about the core-libs-dev mailing list