Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions src/main/java/com/aparapi/Execution.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Copyright (c) 2016 - 2018 Syncleus, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.aparapi;

/**
* Handle returned by {@link Kernel#executeAsync(Range)} and related methods.
*/
public final class Execution {
private final Kernel kernel;
private final Thread thread;
private volatile Throwable failure;

Execution(Kernel _kernel, Runnable _task) {
kernel = _kernel;
thread = new Thread(new Runnable() {
@Override
public void run() {
try {
_task.run();
} catch (Throwable t) {
failure = t;
}
}
}, "Aparapi async execution: " + _kernel.getClass().getName());
thread.start();
}

/**
* Wait until the asynchronous execution has completed.
*/
public void waitUntilFinished() {
boolean interrupted = false;
while (thread.isAlive()) {
try {
thread.join();
} catch (InterruptedException e) {
interrupted = true;
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
rethrowFailure();
}

/**
* @return true when the asynchronous execution thread has completed.
*/
public boolean isFinished() {
return !thread.isAlive();
}

/**
* @return The kernel instance used for this execution.
*/
public Kernel getKernel() {
return kernel;
}

/**
* @return The failure thrown by asynchronous execution, or null when execution completed successfully or is still running.
*/
public Throwable getFailure() {
return failure;
}

private void rethrowFailure() {
if (failure instanceof RuntimeException) {
throw (RuntimeException) failure;
}
if (failure instanceof Error) {
throw (Error) failure;
}
if (failure != null) {
throw new RuntimeException(failure);
}
}
}
76 changes: 76 additions & 0 deletions src/main/java/com/aparapi/Kernel.java
Original file line number Diff line number Diff line change
Expand Up @@ -2804,6 +2804,19 @@ public synchronized Kernel execute(Range _range) {
return (execute(_range, 1));
}

/**
* Start asynchronous execution of <code>_range</code> kernels.
* <p>
* This method starts a background Java thread that invokes {@link #execute(Range)} and returns an {@link Execution}
* handle that can be used to wait for completion.
*
* @param _range The range of kernels that we would like to initiate.
* @return An execution handle that can be used to wait for completion.
*/
public Execution executeAsync(final Range _range) {
return (executeAsync(_range, 1));
}

@Override
@SuppressWarnings("deprecation")
public String toString() {
Expand Down Expand Up @@ -2839,6 +2852,19 @@ public synchronized Kernel execute(int _range) {
return (execute(createRange(_range), 1));
}

/**
* Start asynchronous execution of <code>_range</code> kernels.
* <p>
* Since adding the new <code>Range class</code> this method offers backward compatibility and merely defers to
* <code>executeAsync(Range.create(_range), 1)</code>.
*
* @param _range The number of Kernels that we would like to initiate.
* @return An execution handle that can be used to wait for completion.
*/
public Execution executeAsync(final int _range) {
return (executeAsync(createRange(_range), 1));
}

@SuppressWarnings("deprecation")
protected Range createRange(int _range) {
if (executionMode.equals(EXECUTION_MODE.AUTO)) {
Expand All @@ -2864,6 +2890,17 @@ public synchronized Kernel execute(Range _range, int _passes) {
return (execute("run", _range, _passes));
}

/**
* Start asynchronous execution of <code>_passes</code> iterations of <code>_range</code> kernels.
*
* @param _range The range of kernels that we would like to initiate.
* @param _passes The number of passes to make.
* @return An execution handle that can be used to wait for completion.
*/
public Execution executeAsync(final Range _range, final int _passes) {
return (executeAsync("run", _range, _passes));
}

/**
* Start execution of <code>_passes</code> iterations over the <code>_range</code> of kernels.
* <p>
Expand All @@ -2879,6 +2916,17 @@ public synchronized Kernel execute(int _range, int _passes) {
return (execute(createRange(_range), _passes));
}

/**
* Start asynchronous execution of <code>_passes</code> iterations over the given range.
*
* @param _range The number of Kernels that we would like to initiate.
* @param _passes The number of passes to make.
* @return An execution handle that can be used to wait for completion.
*/
public Execution executeAsync(final int _range, final int _passes) {
return (executeAsync(createRange(_range), _passes));
}

/**
* Start execution of <code>globalSize</code> kernels for the given entrypoint.
* <p>
Expand All @@ -2893,6 +2941,17 @@ public synchronized Kernel execute(String _entrypoint, Range _range) {
return (execute(_entrypoint, _range, 1));
}

/**
* Start asynchronous execution of <code>globalSize</code> kernels for the given entrypoint.
*
* @param _entrypoint is the name of the method we wish to use as the entrypoint to the kernel.
* @param _range The range of kernels that we would like to initiate.
* @return An execution handle that can be used to wait for completion.
*/
public Execution executeAsync(final String _entrypoint, final Range _range) {
return (executeAsync(_entrypoint, _range, 1));
}

/**
* Start execution of <code>globalSize</code> kernels for the given entrypoint.
* <p>
Expand All @@ -2907,6 +2966,23 @@ public synchronized Kernel execute(String _entrypoint, Range _range, int _passes
return prepareKernelRunner().execute(_entrypoint, _range, _passes);
}

/**
* Start asynchronous execution of <code>globalSize</code> kernels for the given entrypoint.
*
* @param _entrypoint is the name of the method we wish to use as the entrypoint to the kernel.
* @param _range The range of kernels that we would like to initiate.
* @param _passes The number of passes to make.
* @return An execution handle that can be used to wait for completion.
*/
public Execution executeAsync(final String _entrypoint, final Range _range, final int _passes) {
return new Execution(this, new Runnable() {
@Override
public void run() {
execute(_entrypoint, _range, _passes);
}
});
}

/**
* Force pre-compilation of the kernel for a given device, without executing it.
*
Expand Down
93 changes: 93 additions & 0 deletions src/test/java/com/aparapi/runtime/AsyncExecutionTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Copyright (c) 2016 - 2018 Syncleus, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.aparapi.runtime;

import com.aparapi.Execution;
import com.aparapi.Kernel;
import com.aparapi.Range;
import org.junit.Test;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class AsyncExecutionTest {
@Test
@SuppressWarnings("deprecation")
public void asyncExecuteRangeWaitsForCompletion() {
final int[] values = new int[8];
Kernel kernel = new Kernel() {
@Override
public void run() {
int id = getGlobalId();
values[id] = id * 2;
}
};
kernel.setExecutionMode(Kernel.EXECUTION_MODE.SEQ);

Execution execution = kernel.executeAsync(Range.create(values.length));
assertSame(kernel, execution.getKernel());

execution.waitUntilFinished();

assertTrue(execution.isFinished());
assertArrayEquals(new int[] { 0, 2, 4, 6, 8, 10, 12, 14 }, values);
}

@Test
@SuppressWarnings("deprecation")
public void asyncExecuteIntWithPassesCompletesAllPasses() {
final int[] passes = new int[4];
Kernel kernel = new Kernel() {
@Override
public void run() {
passes[getGlobalId()]++;
}
};
kernel.setExecutionMode(Kernel.EXECUTION_MODE.SEQ);

Execution execution = kernel.executeAsync(passes.length, 3);
execution.waitUntilFinished();

assertArrayEquals(new int[] { 3, 3, 3, 3 }, passes);
}

@Test
@SuppressWarnings("deprecation")
public void asyncExecutionRethrowsFailureWhenWaitedOn() {
Kernel kernel = new Kernel() {
@Override
public void run() {
throw new IllegalStateException("boom");
}
};
kernel.setExecutionMode(Kernel.EXECUTION_MODE.SEQ);

Execution execution = kernel.executeAsync(1);
try {
execution.waitUntilFinished();
fail("Expected async execution failure to be rethrown");
} catch (IllegalStateException e) {
assertEquals("boom", e.getMessage());
}

assertTrue(execution.isFinished());
assertNotNull(execution.getFailure());
}
}