diff options
author | Jean-Philippe Lesot <jplesot@google.com> | 2015-03-11 10:50:55 +0100 |
---|---|---|
committer | Jean-Philippe Lesot <jplesot@google.com> | 2015-03-11 10:50:55 +0100 |
commit | a6ad00e946f9efa9fcaad7ebff51e125a224e4b3 (patch) | |
tree | adc1860f2496d58b222d8008a6a83405ddafe472 | |
parent | 14f66d21a64021d5492c56aff5055e5e7382dfd4 (diff) | |
download | toolchain_jack-a6ad00e946f9efa9fcaad7ebff51e125a224e4b3.tar.gz toolchain_jack-a6ad00e946f9efa9fcaad7ebff51e125a224e4b3.tar.bz2 toolchain_jack-a6ad00e946f9efa9fcaad7ebff51e125a224e4b3.zip |
Start support of a small server for Jack compilation
Change-Id: I38d410565da4f480c60cdd9405603eebaa2d9938
-rwxr-xr-x | jack/etc/jack | 85 | ||||
-rw-r--r-- | jack/src/com/android/jack/server/Server.java | 383 | ||||
-rw-r--r-- | jack/src/com/android/jack/server/ServerTask.java | 32 | ||||
-rw-r--r-- | sched/src/com/android/sched/util/file/AbstractStreamFile.java | 4 | ||||
-rw-r--r-- | sched/src/com/android/sched/util/file/Directory.java | 2 | ||||
-rw-r--r-- | sched/src/com/android/sched/util/file/FileOrDirectory.java | 38 | ||||
-rw-r--r-- | sched/src/com/android/sched/util/file/OutputStreamFile.java | 23 |
7 files changed, 564 insertions, 3 deletions
diff --git a/jack/etc/jack b/jack/etc/jack new file mode 100755 index 00000000..2409a09b --- /dev/null +++ b/jack/etc/jack @@ -0,0 +1,85 @@ +#! /bin/bash +# +# Copyright (C) 2015 The Android Open Source Project +# +# 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. +# +set -o nounset + +JACK_NB_COMPILE=4 +JACK_TIMEOUT=15 + +TMPDIR="/tmp" + +JACK_PRG="java -cp /usr/local/google/home/jplesot/Android/ub-jack/toolchain/jack/jack/dist/jack.jar com.android.jack.server.Server" +JACK_FIFO="$TMPDIR/jack-$USER.cmd" +JACK_LOCK="$TMPDIR/jack-$USER.lock" +JACK_LOG="$TMPDIR/jack-$USER.log" + +JACK_DIR="$TMPDIR/jack-$USER-$$/" +JACK_OUT="$JACK_DIR/out" +JACK_ERR="$JACK_DIR/err" +JACK_EXIT="$JACK_DIR/exit" +JACK_CLI="$JACK_DIR/cli" +JACK_PWD="$PWD" + +# +# Always create the FIFO +# + +mkfifo -m 0000 $JACK_FIFO 2>/dev/null + +# +# Prepare +# + +# Cleanup +trap "rm -f $JACK_OUT $JACK_ERR $JACK_EXIT $JACK_CLI $JACK_LOCK 2>/dev/null; rmdir $JACK_DIR 2>/dev/null" exit + +# Create fifo for a task +mkdir $JACK_DIR +mkfifo $JACK_OUT +mkfifo $JACK_ERR +mkfifo $JACK_EXIT + +# Redirect output and error +cat <$JACK_OUT >&1 & +PID_OUT=$! +cat <$JACK_ERR >&2 & +PID_ERR=$! + +echo \"$PWD\" $* >$JACK_CLI + +# +# Launch compilation +# + +# Try sending a command +CMD="+ $JACK_OUT $JACK_ERR $JACK_EXIT $JACK_CLI" +( echo $CMD >>$JACK_FIFO ) 2>/dev/null +while [ $? -ne 0 ]; do + # If not possible, launch the server one time only + mkfifo $JACK_LOCK 2>/dev/null + if [ $? -eq 0 ]; then + echo "Launch background server" $JACK_PRG + nohup $JACK_PRG $JACK_NB_COMPILE $JACK_TIMEOUT $JACK_FIFO >$JACK_LOG 2>&1 & + fi + # Slow down, and retry sending the command + sleep 1 + ( echo $CMD >>$JACK_FIFO ) 2>/dev/null +done + +wait $PID_OUT +wait $PID_ERR + +exit `cat $JACK_EXIT` diff --git a/jack/src/com/android/jack/server/Server.java b/jack/src/com/android/jack/server/Server.java new file mode 100644 index 00000000..65bd99a3 --- /dev/null +++ b/jack/src/com/android/jack/server/Server.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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.android.jack.server; + +import com.android.sched.util.config.cli.TokenIterator; +import com.android.sched.util.file.AbstractStreamFile; +import com.android.sched.util.file.CannotSetPermissionException; +import com.android.sched.util.file.FileOrDirectory; +import com.android.sched.util.file.FileOrDirectory.ChangePermission; +import com.android.sched.util.file.FileOrDirectory.Permission; +import com.android.sched.util.file.OutputStreamFile; +import com.android.sched.util.location.FileLocation; +import com.android.sched.util.location.NoLocation; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Random; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** + * XXX + */ +public class Server { + @Nonnull + private static ServerTask service = new ServerTask() { + @Nonnull + private final Random rnd = new Random(); + + @Override + public int run(@Nonnull PrintStream out, @Nonnull PrintStream err, @Nonnull File workingDir, + @Nonnull TokenIterator args) { + String cmd = null; + try { + args.hasNext(); + cmd = args.next(); + } catch (Throwable e) { + e.printStackTrace(); + } + + out.println("Pre-test stdout for '" + workingDir.getPath() + "'"); + err.println("Pre-test stderr for '" + cmd + "'"); + + try { + Thread.sleep(rnd.nextInt(30000)); + } catch (InterruptedException e) { + // Doesn't matter + } + + out.println("Post-test stdout for '" + workingDir.getPath() + "'"); + err.println("Post-test stderr for '" + cmd + "'"); + + return rnd.nextInt(30); + } + }; + + @Nonnull + private static Logger logger = Logger.getLogger(Server.class.getSimpleName()); + + private static final int CMD_IDX_CMD = 0; + private static final int CMD_IDX_OUT = 1; + private static final int CMD_IDX_ERR = 2; + private static final int CMD_IDX_EXIT = 3; + private static final int CMD_IDX_CLI = 4; + private static final int CMD_IDX_END = 5; + + private static final int CLI_IDX_MAX = 0; + private static final int CLI_IDX_TIEMOUT = 1; + private static final int CLI_IDX_FIFO = 2; + private static final int CLI_IDX_END = 3; + + @Nonnull + private static File fifo; + @CheckForNull + private static LineNumberReader in; + + private static int timeout; + + @Nonnull + private static AtomicInteger nbMax = new AtomicInteger(0); + @Nonnull + private static AtomicLong nbCurrent = new AtomicLong(0);; + + public static void main(String[] args) throws InterruptedException { + if (args.length != CLI_IDX_END) { + logger.log(Level.SEVERE, "Usage: <max-compile> <timeout-s> <path-fifo>"); + abort(); + } + + int nbInstance = 0; + try { + nbInstance = Integer.parseInt(args[CLI_IDX_MAX]); + } catch (NumberFormatException e) { + logger.log(Level.SEVERE, "Cannot parse instance count '" + args[CLI_IDX_MAX] + "'"); + abort(); + } + + try { + timeout = Integer.parseInt(args[CLI_IDX_TIEMOUT]) * 1000; + } catch (NumberFormatException e) { + logger.log(Level.SEVERE, "Cannot parse timeout '" + args[CLI_IDX_TIEMOUT] + "'"); + abort(); + } + + fifo = new File(args[CLI_IDX_FIFO]); + if (fifo.canWrite()) { + logger.log(Level.WARNING, "Already running, aborting"); + abort(); + } + + Runtime.getRuntime().addShutdownHook(new Thread(){ + @Override + public void run() { + cancelTimer(); + shutdownFifo(); + } + }); + + try { + AbstractStreamFile.check(fifo, new FileLocation(fifo)); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + abort(); + } + + startFifo(); + try { + in = new LineNumberReader(new FileReader(fifo)); + } catch (FileNotFoundException e) { + throw new AssertionError(e); + } + + logger.log(Level.INFO, "Starting server on '" + fifo.getPath() + "'"); + ExecutorService executor = Executors.newFixedThreadPool(nbInstance); + for (int i = 0; i < nbInstance; i++) { + logger.log(Level.INFO, "Launching task #" + i); + executor.execute(new Task()); + } + + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.HOURS); + + logger.log(Level.INFO, "Shutdown server"); + logger.log(Level.INFO, "# service runs " + nbCurrent.get()); + } + + /** + * STOPSHIP + */ + public static class Task implements Runnable { + @Override + public void run() { + while (true) { + String line; + + try { + line = getLine(); + logger.log(Level.INFO, "Read command '" + line + "'"); + } catch (IOException e) { + logger.log(Level.INFO, "Shutdown task"); + return; + } + + String[] command = line.split(" "); + + if (command[CMD_IDX_CMD].equals("-")) { + cancelTimer(); + shutdownFifo(); + continue; + } + + if (!command[CMD_IDX_CMD].equals("+")) { + logger.log(Level.SEVERE, "Command error '" + line + "'"); + continue; + } + + if (command.length != CMD_IDX_END) { + logger.log(Level.SEVERE, "Command format error '" + line + "'"); + continue; + } + + logger.log(Level.INFO, "Open standard output '" + command[CMD_IDX_OUT] + "'"); + PrintStream out = null; + try { + out = new OutputStreamFile(command[CMD_IDX_OUT]).getPrintStream(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + continue; + } + + logger.log(Level.INFO, "Open standard error '" + command[CMD_IDX_ERR] + "'"); + PrintStream err = null; + try { + err = new OutputStreamFile(command[CMD_IDX_ERR]).getPrintStream(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + continue; + } + + logger.log(Level.INFO, "Parse command line"); + TokenIterator args = new TokenIterator(new NoLocation(), "@" + command[CMD_IDX_CLI]); + args.allowFileReferenceInFile(); + + if (!args.hasNext()) { + logger.log(Level.WARNING, "Cli format error"); + continue; + } + String workingDir; + try { + workingDir = args.next(); + } catch (IOException e) { + logger.log(Level.WARNING, "Cli format error"); + continue; + } + + if (nbMax.getAndIncrement() == 0) { + cancelTimer(); + } + + int code; + try { + logger.log(Level.INFO, "Run service"); + nbCurrent.incrementAndGet(); + code = service.run(out, err, new File(workingDir), args); + } finally { + if (nbMax.decrementAndGet() == 0) { + startTimer(); + } + } + + assert out != null; + out.close(); + assert err != null; + err.close(); + + try { + logger.log(Level.INFO, "Open exit fifo '" + command[CMD_IDX_EXIT] + "'"); + PrintStream exit = new OutputStreamFile(command[CMD_IDX_EXIT]).getPrintStream(); + logger.log(Level.INFO, "Write exit code '" + code + "'"); + exit.println(code); + exit.close(); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + continue; + } + + } + } + } + + @Nonnull + private static Object lockRead = new Object(); + + @Nonnull + public static String getLine() throws IOException { + synchronized (lockRead) { + String str = in.readLine(); + while (str == null) { + try { + in.close(); + } catch (IOException e1) { + // Best effort + } + + in = new LineNumberReader(new FileReader(fifo)); + str = in.readLine(); + } + + return str; + } + } + + private static void startFifo() { + logger.log(Level.FINE, "Start FIFO"); + + try { + FileOrDirectory.setPermissions(fifo, new FileLocation(fifo), + Permission.READ | Permission.WRITE, ChangePermission.OWNER); + } catch (CannotSetPermissionException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + abort(); + } + } + + private static void shutdownFifo() { + OutputStream out = null; + + logger.log(Level.FINE, "Shutdown FIFO"); + + try { + out = new FileOutputStream(fifo); + } catch (FileNotFoundException e) { + // Best effort + } + + try { + FileOrDirectory.unsetPermissions(fifo, new FileLocation(fifo), Permission.READ + | Permission.WRITE, ChangePermission.OWNER); + } catch (CannotSetPermissionException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + abort(); + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // Best effort + } + } + } + + private static void abort() { + System.exit(1); + } + + @CheckForNull + private static Timer timer; + + @Nonnull + private static Object lockTimer = new Object(); + + private static void startTimer() { + synchronized (lockTimer) { + if (timer != null) { + cancelTimer(); + } + + logger.log(Level.INFO, "Start timer"); + + timer = new Timer("jack-server-timeout"); + timer.schedule(new TimerTask() { + @Override + public void run() { + shutdownFifo(); + cancelTimer(); + } + }, timeout); + } + } + + private static void cancelTimer() { + synchronized (lockTimer) { + if (timer != null) { + logger.log(Level.INFO, "Cancel timer"); + + timer.cancel(); + timer.purge(); + timer = null; + } + } + } +} diff --git a/jack/src/com/android/jack/server/ServerTask.java b/jack/src/com/android/jack/server/ServerTask.java new file mode 100644 index 00000000..f7674b5e --- /dev/null +++ b/jack/src/com/android/jack/server/ServerTask.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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.android.jack.server; + +import com.android.sched.util.config.cli.TokenIterator; + +import java.io.File; +import java.io.PrintStream; + +import javax.annotation.Nonnull; + +/** + * STOPSHIP + */ +public interface ServerTask { + int run(@Nonnull PrintStream out, @Nonnull PrintStream err, @Nonnull File workingDir, + @Nonnull TokenIterator args); +}
\ No newline at end of file diff --git a/sched/src/com/android/sched/util/file/AbstractStreamFile.java b/sched/src/com/android/sched/util/file/AbstractStreamFile.java index 2a680d4b..6bd4b942 100644 --- a/sched/src/com/android/sched/util/file/AbstractStreamFile.java +++ b/sched/src/com/android/sched/util/file/AbstractStreamFile.java @@ -127,8 +127,8 @@ public abstract class AbstractStreamFile extends FileOrDirectory { throw new NoSuchFileException(location); } - // Check it is a file - if (!file.isFile()) { + // Check if it is not a Directory + if (file.isDirectory()) { throw new NotFileException(location); } } diff --git a/sched/src/com/android/sched/util/file/Directory.java b/sched/src/com/android/sched/util/file/Directory.java index 55b30dac..2d7f3ae5 100644 --- a/sched/src/com/android/sched/util/file/Directory.java +++ b/sched/src/com/android/sched/util/file/Directory.java @@ -96,7 +96,7 @@ public class Directory extends FileOrDirectory { throw new NoSuchFileException(location); } - // Check directory + // Check if it is a directory if (!file.isDirectory()) { throw new NotDirectoryException(location); } diff --git a/sched/src/com/android/sched/util/file/FileOrDirectory.java b/sched/src/com/android/sched/util/file/FileOrDirectory.java index dde0146b..c7d7dcae 100644 --- a/sched/src/com/android/sched/util/file/FileOrDirectory.java +++ b/sched/src/com/android/sched/util/file/FileOrDirectory.java @@ -125,6 +125,44 @@ public abstract class FileOrDirectory implements HasLocation { } } + + public static void unsetPermissions(@Nonnull File file, @Nonnull Location location, + int permissions, @Nonnull FileOrDirectory.ChangePermission change) + throws CannotSetPermissionException { + if (change != ChangePermission.NOCHANGE) { + // Set access + if ((permissions & Permission.READ) != 0) { + if (file.setReadable(false, change == ChangePermission.OWNER)) { + logger.log(Level.FINE, "Clear readable permission to {0} (''{1}'')", + new Object[] {location.getDescription(), file.getAbsoluteFile()}); + } else { + throw new CannotSetPermissionException(location, Permission.READ, + change); + } + } + + if ((permissions & Permission.WRITE) != 0) { + if (file.setWritable(false, change == ChangePermission.OWNER)) { + logger.log(Level.FINE, "Clear writable permission to {0} (''{1}'')", + new Object[] {location.getDescription(), file.getAbsoluteFile()}); + } else { + throw new CannotSetPermissionException(location, Permission.WRITE, + change); + } + } + + if ((permissions & Permission.EXECUTE) != 0) { + if (file.setExecutable(false, change == ChangePermission.OWNER)) { + logger.log(Level.FINE, "Clear executable permission to {0} (''{1}'')", + new Object[] {location.getDescription(), file.getAbsoluteFile()}); + } else { + throw new CannotSetPermissionException(location, Permission.EXECUTE, + change); + } + } + } + } + public static void checkPermissions(@Nonnull File file, @Nonnull Location location, int permissions) throws WrongPermissionException { if ((permissions & Permission.READ) != 0) { diff --git a/sched/src/com/android/sched/util/file/OutputStreamFile.java b/sched/src/com/android/sched/util/file/OutputStreamFile.java index 055869b1..6956ba13 100644 --- a/sched/src/com/android/sched/util/file/OutputStreamFile.java +++ b/sched/src/com/android/sched/util/file/OutputStreamFile.java @@ -83,6 +83,29 @@ public class OutputStreamFile extends AbstractStreamFile { this.append = false; } + /** + * Creates a new instance of {@link OutputStreamFile} assuming the file must exist, without + * modifying its permissions. It will be overwritten. + */ + public OutputStreamFile(@Nonnull String name) + throws WrongPermissionException, NotFileException { + super(name, null); + + try { + performChecks(Existence.MUST_EXIST, Permission.WRITE, ChangePermission.NOCHANGE); + } catch (NoSuchFileException e) { + throw new AssertionError(e); + } catch (FileAlreadyExistsException e) { + throw new AssertionError(e); + } catch (CannotSetPermissionException e) { + throw new AssertionError(e); + } catch (CannotCreateFileException e) { + throw new AssertionError(e); + } + + this.append = false; + } + @Nonnull private static final Location STANDARD_OUTPUT_LOCATION = new StandardOutputLocation(); @Nonnull |