• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.layoutlib.bridge.remote.server;
18 
19 import com.android.layout.remote.api.RemoteBridge;
20 import com.android.tools.layoutlib.annotations.NotNull;
21 
22 import java.io.BufferedReader;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.lang.management.ManagementFactory;
27 import java.lang.management.RuntimeMXBean;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.rmi.NoSuchObjectException;
31 import java.rmi.RemoteException;
32 import java.rmi.registry.LocateRegistry;
33 import java.rmi.registry.Registry;
34 import java.rmi.server.UnicastRemoteObject;
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.concurrent.ArrayBlockingQueue;
38 import java.util.concurrent.BlockingQueue;
39 import java.util.concurrent.TimeUnit;
40 import java.util.function.Consumer;
41 import java.util.stream.Collectors;
42 
43 /**
44  * Main server class. The main method will start an RMI server for the {@link RemoteBridgeImpl}
45  * class.
46  */
47 public class ServerMain {
48     private static final String RUNNING_SERVER_STR = "Server is running on port ";
49     public static int REGISTRY_BASE_PORT = 9000;
50 
51     private final int mPort;
52     private final Registry mRegistry;
53 
ServerMain(int port, @NotNull Registry registry)54     private ServerMain(int port, @NotNull Registry registry) {
55         mPort = port;
56         mRegistry = registry;
57     }
58 
getPort()59     public int getPort() {
60         return mPort;
61     }
62 
stop()63     public void stop() {
64         try {
65             UnicastRemoteObject.unexportObject(mRegistry, true);
66         } catch (NoSuchObjectException ignored) {
67         }
68     }
69 
createOutputProcessor(String outputProcessorName, InputStream inputStream, Consumer<String> consumer)70     private static Thread createOutputProcessor(String outputProcessorName,
71             InputStream inputStream,
72             Consumer<String> consumer) {
73         BufferedReader inputReader = new BufferedReader(new InputStreamReader(inputStream));
74         Thread thread = new Thread(() -> inputReader.lines().forEach(consumer));
75         thread.setName(outputProcessorName);
76         thread.start();
77         return thread;
78     }
79 
80     /**
81      * This will start a new JVM and connect to the new JVM RMI registry.
82      * <p/>
83      * The server will start looking for ports available for the {@link Registry} until a free one
84      * is found. The port number will be returned.
85      * If no ports are available, a {@link RemoteException} will be thrown.
86      * @param basePort port number to start looking for available ports
87      * @param limit number of ports to check. The last port to be checked will be (basePort + limit)
88      */
forkAndStartServer(int basePort, int limit)89     public static ServerMain forkAndStartServer(int basePort, int limit)
90             throws IOException, InterruptedException {
91         // We start a new VM by copying all the parameter that we received in the current VM.
92         // We only remove the agentlib parameter since that could cause a port collision and avoid
93         // the new VM from starting.
94         RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
95         List<String> arguments = runtimeMxBean.getInputArguments().stream()
96                 .filter(arg -> !arg.contains("-agentlib")) // Filter agentlib to avoid conflicts
97                 .collect(Collectors.toList());
98 
99         Path javaPath = Paths.get(System.getProperty("java.home"), "bin", "java");
100         String thisClassName = ServerMain.class.getName()
101                 .replace('.','/');
102 
103         List<String> cmd = new ArrayList<>();
104         cmd.add(javaPath.toString());
105 
106         // Inherited arguments
107         cmd.addAll(arguments);
108 
109         // Classpath
110         cmd.add("-cp");
111         cmd.add(System.getProperty("java.class.path"));
112 
113         // Class name and path
114         cmd.add(thisClassName);
115 
116         // ServerMain parameters [basePort. limit]
117         cmd.add(Integer.toString(basePort));
118         cmd.add(Integer.toString(limit));
119 
120         Process process = new ProcessBuilder()
121                 .command(cmd)
122                 .start();
123 
124         BlockingQueue<String> outputQueue = new ArrayBlockingQueue<>(10);
125         Thread outputThread = createOutputProcessor("output", process.getInputStream(),
126                 outputQueue::offer);
127         Thread errorThread = createOutputProcessor("error", process.getErrorStream(),
128                 System.err::println);
129 
130         Runnable killServer = () -> {
131             process.destroyForcibly();
132             outputThread.interrupt();
133             errorThread.interrupt();
134             try {
135                 outputThread.join();
136             } catch (InterruptedException ignore) {
137             }
138 
139             try {
140                 errorThread.join();
141             } catch (InterruptedException ignore) {
142             }
143         };
144 
145         // Try to read the "Running on port" line in 10 lines. If it's not there just fail.
146         for (int i = 0; i < 10; i++) {
147             String line = outputQueue.poll(5, TimeUnit.SECONDS);
148 
149             if (line != null && line.startsWith(RUNNING_SERVER_STR)) {
150                 int runningPort = Integer.parseInt(line.substring(RUNNING_SERVER_STR.length()));
151                 System.out.println("Running on port " + runningPort);
152 
153                 // We already know where the server is running so we just need to get the registry
154                 // and return our own instance of ServerMain
155                 Registry registry = LocateRegistry.getRegistry(runningPort);
156                 return new ServerMain(runningPort, registry) {
157                     @Override
158                     public void stop() {
159                         killServer.run();
160                     }
161                 };
162             }
163         }
164 
165         killServer.run();
166         throw new IOException("Unable to find start string");
167     }
168 
169     /**
170      * The server will start looking for ports available for the {@link Registry} until a free one
171      * is found. The port number will be returned.
172      * If no ports are available, a {@link RemoteException} will be thrown.
173      * @param basePort port number to start looking for available ports
174      * @param limit number of ports to check. The last port to be checked will be (basePort + limit)
175      */
176     private static ServerMain startServer(int basePort, int limit) throws RemoteException {
177         RemoteBridgeImpl remoteBridge = new RemoteBridgeImpl();
178         RemoteBridge stub = (RemoteBridge) UnicastRemoteObject.exportObject(remoteBridge, 0);
179 
180         RemoteException lastException = null;
181         for (int port = basePort; port <= basePort + limit; port++) {
182             try {
183                 Registry registry = LocateRegistry.createRegistry(port);
184                 registry.rebind(RemoteBridge.class.getName(), stub);
185                 return new ServerMain(port, registry);
186             } catch (RemoteException e) {
187                 lastException = e;
188             }
189         }
190 
191         if (lastException == null) {
192             lastException = new RemoteException("Unable to start server");
193         }
194 
195         throw lastException;
196     }
197 
198     /**
199      * Starts an RMI server that runs in the current JVM. Only for debugging.
200      */
201     public static ServerMain startLocalJvmServer() throws RemoteException {
202         System.err.println("Starting server in the local JVM");
203         return startServer(REGISTRY_BASE_PORT, 10);
204     }
205 
206     public static void main(String[] args) throws RemoteException {
207         int basePort = args.length > 0 ? Integer.parseInt(args[0]) : REGISTRY_BASE_PORT;
208         int limit = args.length > 1 ? Integer.parseInt(args[1]) : 10;
209 
210         ServerMain server = startServer(basePort, limit);
211         System.out.println(RUNNING_SERVER_STR + server.getPort());
212     }
213 }
214