001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.boot;
031:
032: import com.caucho.config.ConfigException;
033: import com.caucho.lifecycle.Lifecycle;
034: import com.caucho.log.RotateStream;
035: import com.caucho.server.port.Port;
036: import com.caucho.util.*;
037: import com.caucho.vfs.Path;
038: import com.caucho.vfs.QServerSocket;
039: import com.caucho.vfs.Vfs;
040: import com.caucho.vfs.WriteStream;
041:
042: import java.io.File;
043: import java.io.IOException;
044: import java.io.InputStream;
045: import java.io.OutputStream;
046: import java.lang.reflect.*;
047: import java.net.*;
048: import java.util.ArrayList;
049: import java.util.HashMap;
050: import java.util.Map;
051: import java.util.logging.Level;
052: import java.util.logging.Logger;
053:
054: /**
055: * Encapsulation of the process running resin.
056: */
057: class WatchdogProcess {
058: private static final L10N L = new L10N(WatchdogProcess.class);
059: private static final Logger log = Logger
060: .getLogger(WatchdogProcess.class.getName());
061:
062: private static Boot _jniBoot;
063:
064: private final String _id;
065: private final Watchdog _watchdog;
066: private final Lifecycle _lifecycle = new Lifecycle();
067:
068: private ServerSocket _ss;
069: private Process _process;
070:
071: WatchdogProcess(String id, Watchdog watchdog) {
072: _id = id;
073: _watchdog = watchdog;
074: }
075:
076: public void run() {
077: if (!_lifecycle.toActive())
078: return;
079:
080: WriteStream jvmOut = null;
081:
082: try {
083: _ss = new ServerSocket(0, 5, InetAddress
084: .getByName("127.0.0.1"));
085:
086: int port = _ss.getLocalPort();
087:
088: log.info(this + " starting Resin");
089:
090: jvmOut = createJvmOut();
091:
092: _process = createProcess(port, jvmOut);
093:
094: if (_process != null) {
095: try {
096: runInstance(jvmOut, _ss, _process);
097: } finally {
098: destroy();
099: }
100: }
101: } catch (Exception e) {
102: log.log(Level.INFO, e.toString(), e);
103:
104: try {
105: Thread.sleep(5000);
106: } catch (Exception e1) {
107: }
108: } finally {
109: if (jvmOut != null && !_watchdog.isSingle()) {
110: try {
111: jvmOut.close();
112: } catch (IOException e) {
113: }
114: }
115: }
116: }
117:
118: void stop() {
119: _lifecycle.toDestroy();
120: }
121:
122: void destroy() {
123: if (_process != null) {
124: try {
125: _process.destroy();
126: } catch (Exception e) {
127: log.log(Level.FINE, e.toString(), e);
128: }
129:
130: try {
131: _process.waitFor();
132: _process = null;
133: } catch (Exception e) {
134: log.log(Level.INFO, e.toString(), e);
135: }
136: }
137: }
138:
139: private void runInstance(WriteStream jvmOut, ServerSocket ss,
140: Process process) throws IOException {
141: InputStream stdIs = null;
142: OutputStream stdOs = null;
143: InputStream watchdogIs = null;
144: Socket s = null;
145:
146: try {
147: stdIs = process.getInputStream();
148: stdOs = process.getOutputStream();
149: ss.setSoTimeout(1000);
150:
151: boolean isLive = true;
152: int stdoutTimeoutMax = 10;
153: int stdoutTimeout = stdoutTimeoutMax;
154: byte[] data = new byte[1024];
155: int len;
156:
157: s = connectToChild(ss, stdIs, jvmOut, process, data);
158:
159: if (s == null)
160: log.warning(this + " watchdog socket timed out");
161:
162: if (s != null)
163: watchdogIs = s.getInputStream();
164:
165: runInstance(stdIs, jvmOut, process, data);
166:
167: try {
168: if (watchdogIs != null)
169: watchdogIs.close();
170: } catch (Exception e) {
171: log.log(Level.WARNING, e.toString(), e);
172: }
173:
174: try {
175: if (s != null)
176: s.close();
177: } catch (Exception e) {
178: log.log(Level.WARNING, e.toString(), e);
179: }
180:
181: try {
182: stdOs.close();
183: } catch (Exception e) {
184: log.log(Level.WARNING, e.toString(), e);
185: }
186:
187: log.info(this + " stopping Resin");
188:
189: closeInstance(stdIs, jvmOut, process, data);
190: } finally {
191: if (watchdogIs != null) {
192: try {
193: watchdogIs.close();
194: } catch (IOException e) {
195: }
196: }
197:
198: try {
199: if (s != null)
200: s.close();
201: } catch (Exception e) {
202: log.log(Level.WARNING, e.toString(), e);
203: }
204: }
205: }
206:
207: private WriteStream createJvmOut() throws IOException {
208: if (!_watchdog.isSingle()) {
209: String name;
210: String id = _watchdog.getId();
211: Path jvmPath = _watchdog.getLogPath();
212:
213: try {
214: Path dir = jvmPath.getParent();
215:
216: if (!dir.exists()) {
217: dir.mkdirs();
218:
219: String userName = _watchdog.getUserName();
220: if (userName != null)
221: dir.changeOwner(userName);
222:
223: String groupName = _watchdog.getGroupName();
224: if (groupName != null)
225: dir.changeGroup(groupName);
226: }
227: } catch (Exception e) {
228: log.log(Level.FINE, e.toString(), e);
229: }
230:
231: RotateStream rotateStream = RotateStream.create(jvmPath);
232: rotateStream.init();
233: return rotateStream.getStream();
234: } else
235: return Vfs.openWrite(System.out);
236: }
237:
238: private Socket connectToChild(ServerSocket ss, InputStream stdIs,
239: WriteStream jvmOut, Process process, byte[] data)
240: throws IOException {
241: try {
242: Socket s = null;
243:
244: for (int i = 0; i < 120 && s == null; i++) {
245: try {
246: s = ss.accept();
247: } catch (SocketTimeoutException e) {
248: }
249:
250: while (stdIs.available() > 0) {
251: int len = stdIs.read(data, 0, data.length);
252:
253: if (len < 0)
254: break;
255:
256: jvmOut.write(data, 0, len);
257: jvmOut.flush();
258: }
259:
260: try {
261: int status = process.exitValue();
262:
263: if (s != null)
264: s.close();
265:
266: return null;
267: } catch (IllegalThreadStateException e) {
268: }
269: }
270:
271: return s;
272: } catch (Exception e) {
273: log.log(Level.WARNING, e.toString(), e);
274:
275: return null;
276: } finally {
277: ss.close();
278: }
279: }
280:
281: private void closeInstance(InputStream stdIs, WriteStream jvmOut,
282: Process process, byte[] data) {
283: long endTime = Alarm.getCurrentTime()
284: + _watchdog.getShutdownWaitTime();
285: boolean isLive = true;
286:
287: while (isLive && Alarm.getCurrentTime() < endTime) {
288: try {
289: while (stdIs.available() > 0) {
290: int len = stdIs.read(data, 0, data.length);
291:
292: if (len <= 0) {
293: isLive = false;
294: break;
295: }
296:
297: jvmOut.write(data, 0, len);
298: jvmOut.flush();
299: }
300: } catch (IOException e) {
301: log.log(Level.FINER, e.toString(), e);
302: }
303:
304: try {
305: int status = process.exitValue();
306:
307: isLive = false;
308: } catch (IllegalThreadStateException e) {
309: }
310: }
311:
312: try {
313: stdIs.close();
314: } catch (Exception e) {
315: }
316: }
317:
318: private void runInstance(InputStream stdIs, WriteStream jvmOut,
319: Process process, byte[] data) throws IOException {
320: boolean isLive = true;
321: int stdoutTimeoutMax = 10;
322: int stdoutTimeout = stdoutTimeoutMax;
323:
324: while (isLive && _lifecycle.isActive()) {
325: while (stdIs.available() > 0) {
326: int len = stdIs.read(data, 0, data.length);
327:
328: if (len <= 0)
329: break;
330:
331: stdoutTimeout = stdoutTimeoutMax;
332:
333: jvmOut.write(data, 0, len);
334: jvmOut.flush();
335: }
336:
337: try {
338: process.exitValue();
339:
340: isLive = false;
341: } catch (IllegalThreadStateException e) {
342: }
343:
344: try {
345: synchronized (_lifecycle) {
346: if (stdoutTimeout-- > 0)
347: _lifecycle
348: .wait(100 * (stdoutTimeoutMax - stdoutTimeout));
349: else
350: _lifecycle.wait(100 * stdoutTimeoutMax);
351: }
352: } catch (Exception e) {
353: }
354: }
355: }
356:
357: private Process createProcess(int socketPort, WriteStream out)
358: throws IOException {
359: // watchdog/0210
360: // Path pwd = rootDirectory;
361: Path processPwd = _watchdog.getPwd();
362:
363: Path resinHome = _watchdog.getResinHome();
364: Path resinRoot = _watchdog.getResinRoot();
365:
366: String classPath = WatchdogArgs.calculateClassPath(resinHome);
367:
368: HashMap<String, String> env = new HashMap<String, String>();
369:
370: env.putAll(System.getenv());
371:
372: env.put("CLASSPATH", classPath);
373:
374: if (_watchdog.is64bit()) {
375: appendEnvPath(env, "LD_LIBRARY_PATH", resinHome.lookup(
376: "libexec64").getNativePath());
377: appendEnvPath(env, "DYLD_LIBRARY_PATH", resinHome.lookup(
378: "libexec64").getNativePath());
379: } else {
380: appendEnvPath(env, "LD_LIBRARY_PATH", resinHome.lookup(
381: "libexec").getNativePath());
382: appendEnvPath(env, "DYLD_LIBRARY_PATH", resinHome.lookup(
383: "libexec").getNativePath());
384: }
385:
386: ArrayList<String> list = new ArrayList<String>();
387:
388: list.add(_watchdog.getJavaExe());
389: list
390: .add("-Djava.util.logging.manager=com.caucho.log.LogManagerImpl");
391:
392: // This is needed for JMX to work correctly.
393: list
394: .add("-Djava.system.class.loader=com.caucho.loader.SystemClassLoader");
395: list.add("-Djava.awt.headless=true");
396: list.add("-Dresin.home=" + resinHome.getPath());
397:
398: if (!_watchdog.hasXss())
399: list.add("-Xss1m");
400:
401: if (!_watchdog.hasXmx())
402: list.add("-Xmx256m");
403:
404: if (!list.contains("-d32") && !list.contains("-d64")
405: && _watchdog.is64bit())
406: list.add("-d64");
407:
408: for (String arg : _watchdog.getJvmArgs()) {
409: if (!arg.startsWith("-Djava.class.path"))
410: list.add(arg);
411: }
412:
413: ArrayList<String> resinArgs = new ArrayList<String>();
414: String[] argv = _watchdog.getArgv();
415: for (int i = 0; i < argv.length; i++) {
416: if (argv[i].equals("-conf")) {
417: // resin conf handled below
418: i++;
419: } else if (argv[i].startsWith("-Djava.class.path=")) {
420: // IBM JDK startup issues
421: } else if (argv[i].startsWith("-J")) {
422: list.add(argv[i].substring(2));
423: } else if (argv[i].startsWith("-Djava.class.path")) {
424: } else
425: resinArgs.add(argv[i]);
426: }
427:
428: list.add("com.caucho.server.resin.Resin");
429:
430: if (_watchdog.getResinConf() != null) {
431: list.add("-conf");
432: list.add(_watchdog.getResinConf().getNativePath());
433: }
434:
435: list.add("-socketwait");
436: list.add(String.valueOf(socketPort));
437:
438: list.addAll(resinArgs);
439:
440: if (_watchdog.isVerbose()) {
441: for (int i = 0; i < list.size(); i++) {
442: if (i > 0)
443: out.print(" ");
444:
445: out.print(list.get(i));
446:
447: if (i + 1 < list.size())
448: out.println(" \\");
449: else
450: out.println();
451: }
452:
453: for (Map.Entry<String, String> envEntry : env.entrySet())
454: out.println("" + envEntry.getKey() + ": "
455: + envEntry.getValue());
456: }
457:
458: Boot boot = getJniBoot();
459: if (boot != null) {
460: boot.clearSaveOnExec();
461:
462: ArrayList<QServerSocket> boundSockets = new ArrayList<QServerSocket>();
463:
464: try {
465: if (_watchdog.getUserName() != null) {
466: for (Port port : _watchdog.getPorts()) {
467: QServerSocket ss = port.bindForWatchdog();
468:
469: boundSockets.add(ss);
470:
471: if (ss.setSaveOnExec()) {
472: list.add("-port");
473: list.add(String.valueOf(ss.getSystemFD()));
474: list.add(String.valueOf(port.getAddress()));
475: list.add(String.valueOf(port.getPort()));
476: }
477: }
478: }
479:
480: Process process = boot.exec(list, env, processPwd
481: .getNativePath(), _watchdog.getUserName(),
482: _watchdog.getGroupName());
483:
484: if (process != null)
485: return process;
486: } catch (ConfigException e) {
487: log.warning(e.getMessage());
488: } catch (Throwable e) {
489: log.log(Level.WARNING, e.toString(), e);
490: } finally {
491: for (int i = 0; i < boundSockets.size(); i++) {
492: try {
493: boundSockets.get(i).close();
494: } catch (Throwable e) {
495: }
496: }
497: }
498: }
499:
500: if (_watchdog.getUserName() != null) {
501: if (_watchdog.isSingle())
502: throw new ConfigException(
503: L
504: .l("<user-name> requires Resin Professional and compiled JNI started with 'start'. Resin cannot use <user-name> when started as a foreground process."));
505: else
506: throw new ConfigException(
507: L
508: .l("<user-name> requires Resin Professional and compiled JNI."));
509: }
510:
511: if (_watchdog.getGroupName() != null) {
512: if (_watchdog.isSingle())
513: throw new ConfigException(
514: L
515: .l("<group-name> requires Resin Professional and compiled JNI started with 'start'. Resin cannot use <group-name> when started as a foreground process."));
516: else
517: throw new ConfigException(
518: L
519: .l("<group-name> requires Resin Professional and compiled JNI."));
520: }
521:
522: ProcessBuilder builder = new ProcessBuilder();
523:
524: builder.directory(new File(processPwd.getNativePath()));
525:
526: builder.environment().putAll(env);
527:
528: builder = builder.command(list);
529:
530: builder.redirectErrorStream(true);
531:
532: return builder.start();
533: }
534:
535: private void appendEnvPath(Map<String, String> env, String prop,
536: String value) {
537: String oldValue = env.get(prop);
538:
539: if (oldValue != null && !"".equals(oldValue))
540: value = value + File.pathSeparator + oldValue;
541:
542: env.put(prop, value);
543: }
544:
545: public String toString() {
546: return getClass().getSimpleName() + "[" + _watchdog + "," + _id
547: + "]";
548: }
549:
550: Boot getJniBoot() {
551: if (_jniBoot != null)
552: return _jniBoot.isValid() ? _jniBoot : null;
553:
554: try {
555: ClassLoader loader = Thread.currentThread()
556: .getContextClassLoader();
557:
558: Class cl = Class.forName("com.caucho.boot.JniBoot", false,
559: loader);
560:
561: _jniBoot = (Boot) cl.newInstance();
562: } catch (ClassNotFoundException e) {
563: log.fine(e.toString());
564: } catch (IllegalStateException e) {
565: log.fine(e.toString());
566: } catch (Throwable e) {
567: log.log(Level.FINE, e.toString(), e);
568: }
569:
570: return _jniBoot != null && _jniBoot.isValid() ? _jniBoot : null;
571: }
572: }
|