/*
* Copyright (c) 2013-2017 Cinchapi 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.cinchapi.concourse.server.io.process;
import java.io.Serializable;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.apache.commons.lang.StringUtils;
import com.cinchapi.concourse.server.io.FileSystem;
import com.cinchapi.concourse.util.Processes;
import com.cinchapi.concourse.util.Resources;
import com.cinchapi.concourse.util.Serializables;
/**
* An extension of the {@link com.cinchapi.concourse.util.Processes Processes}
* utils class with server specific capabilities.
*
* @author Jeff Nelson
*/
public final class ServerProcesses {
/**
* The template to use for {@link #fork(Forkable) forked} routines run in a
* separate {@link JavaApp}.
*/
private static String FORK_TEMPLATE = FileSystem.read(Resources
.getAbsolutePath("/META-INF/ForkRunner.tpl"));
/**
* Fork the {@code routine} to a separate JVM process and block until the
* result can be returned locally.
*
* <p>
* This method makes it as easy to fork logic to a separate JVM process as
* it is to fork to a separate thread using a {@link Runnable} or
* {@link Callable}.
* </p>
*
* @param routine the {@link Forkable} to run in a separate process
* @param callback the {@link Callback} that handles the result
* @return the result of running the routine
*/
public static <T extends Serializable> T fork(Forkable<T> routine) {
Callback<T> callback = new NoOpCallback<T>();
fork(routine, callback);
return callback.getResult();
}
/**
* Fork the {@code routine} to a separate JVM process and return the result
* locally by passing it to the {@code callback}.
*
* <p>
* This method makes it as easy to fork logic to a separate JVM process as
* it is to fork to a separate thread using a {@link Runnable} or
* {@link Callable}.
* </p>
*
* @param routine the {@link Forkable} routine to run in a separate process
* @param callback the {@link Callback} that handles the result
* @return the result of running the routine
*/
public static <T extends Serializable> void fork(final Forkable<T> routine,
final Callback<T> callback) {
String input = FileSystem.tempFile(); // use to serialize the #routine
// so it can be read by forked
// process
final String output = FileSystem.tempFile(); // used to serialize the
// return value for the
// #routine so it be read
// by this process
String source = FORK_TEMPLATE.replace("INSERT_INPUT_PATH", input)
.replace("INSERT_OUTPUT_PATH", output)
.replace("INSERT_CLASS_NAME", routine.getClass().getName());
// Since the #routine is forked, we offer the external JVM process the
// local classpath
String classpath = StringUtils.join(((URLClassLoader) Thread
.currentThread().getContextClassLoader()).getURLs(),
JavaApp.CLASSPATH_SEPARATOR);
FileChannel inputChannel = FileSystem.getFileChannel(input);
try {
Serializables.write(routine, inputChannel);
final JavaApp app = new JavaApp(classpath, source);
app.run();
new Thread(new Runnable() { // Wait for completion in separate
// thread so as to not block the caller
@Override
public void run() {
Processes.waitForSuccessfulCompletion(app);
ByteBuffer result = FileSystem.readBytes(output);
T ret = Serializables.read(result,
routine.getReturnType());
callback.result(ret);
}
}).start();
}
finally {
FileSystem.closeFileChannel(inputChannel);
}
}
}