/* * This file is part of Bitsquare. * * Bitsquare is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * Bitsquare is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public * License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. */ package io.bitsquare.seednode; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.bitsquare.app.AppOptionKeys; import io.bitsquare.app.BitsquareEnvironment; import io.bitsquare.app.BitsquareExecutable; import io.bitsquare.common.UserThread; import io.bitsquare.common.util.Profiler; import io.bitsquare.common.util.RestartUtil; import joptsimple.OptionException; import joptsimple.OptionParser; import joptsimple.OptionSet; import org.apache.commons.lang3.exception.ExceptionUtils; import org.bitcoinj.store.BlockStoreException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import static io.bitsquare.app.BitsquareEnvironment.DEFAULT_APP_NAME; import static io.bitsquare.app.BitsquareEnvironment.DEFAULT_USER_DATA_DIR; public class SeedNodeMain extends BitsquareExecutable { private static final Logger log = LoggerFactory.getLogger(SeedNodeMain.class); private static long MAX_MEMORY_MB_DEFAULT = 500; private static final long CHECK_MEMORY_PERIOD_SEC = 5 * 60; private SeedNode seedNode; private volatile boolean stopped; private static long maxMemory = MAX_MEMORY_MB_DEFAULT; public static void main(String[] args) throws Exception { final ThreadFactory threadFactory = new ThreadFactoryBuilder() .setNameFormat("SeedNodeMain") .setDaemon(true) .build(); UserThread.setExecutor(Executors.newSingleThreadExecutor(threadFactory)); // We don't want to do the full argument parsing here as that might easily change in update versions // So we only handle the absolute minimum which is APP_NAME, APP_DATA_DIR_KEY and USER_DATA_DIR BitsquareEnvironment.setDefaultAppName("Bitsquare_seednode"); OptionParser parser = new OptionParser(); parser.allowsUnrecognizedOptions(); parser.accepts(AppOptionKeys.USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR)) .withRequiredArg(); parser.accepts(AppOptionKeys.APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME)) .withRequiredArg(); OptionSet options; try { options = parser.parse(args); } catch (OptionException ex) { System.out.println("error: " + ex.getMessage()); System.out.println(); parser.printHelpOn(System.out); System.exit(EXIT_FAILURE); return; } BitsquareEnvironment bitsquareEnvironment = new BitsquareEnvironment(options); // need to call that before BitsquareAppMain().execute(args) BitsquareExecutable.initAppDir(bitsquareEnvironment.getProperty(AppOptionKeys.APP_DATA_DIR_KEY)); // For some reason the JavaFX launch process results in us losing the thread context class loader: reset it. // In order to work around a bug in JavaFX 8u25 and below, you must include the following code as the first line of your realMain method: Thread.currentThread().setContextClassLoader(SeedNodeMain.class.getClassLoader()); new SeedNodeMain().execute(args); } @Override protected void doExecute(OptionSet options) { final BitsquareEnvironment environment = new BitsquareEnvironment(options); SeedNode.setEnvironment(environment); UserThread.execute(() -> seedNode = new SeedNode()); Thread.UncaughtExceptionHandler handler = (thread, throwable) -> { if (throwable.getCause() != null && throwable.getCause().getCause() != null && throwable.getCause().getCause() instanceof BlockStoreException) { log.error(throwable.getMessage()); } else { log.error("Uncaught Exception from thread " + Thread.currentThread().getName()); log.error("throwableMessage= " + throwable.getMessage()); log.error("throwableClass= " + throwable.getClass()); log.error("Stack trace:\n" + ExceptionUtils.getStackTrace(throwable)); throwable.printStackTrace(); log.error("We shut down the app because an unhandled error occurred"); // We don't use the restart as in case of OutOfMemory errors the restart might fail as well // The run loop will restart the node anyway... System.exit(EXIT_FAILURE); } }; Thread.setDefaultUncaughtExceptionHandler(handler); Thread.currentThread().setUncaughtExceptionHandler(handler); String maxMemoryOption = environment.getProperty(AppOptionKeys.MAX_MEMORY); if (maxMemoryOption != null && !maxMemoryOption.isEmpty()) { try { maxMemory = Integer.parseInt(maxMemoryOption); } catch (Throwable t) { log.error(t.getMessage()); } } UserThread.runPeriodically(() -> { Profiler.printSystemLoad(log); long usedMemoryInMB = Profiler.getUsedMemoryInMB(); if (!stopped) { if (usedMemoryInMB > (maxMemory - 100)) { log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + "We are over our memory warn limit and call the GC. usedMemoryInMB: {}" + "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n", usedMemoryInMB); System.gc(); usedMemoryInMB = Profiler.getUsedMemoryInMB(); Profiler.printSystemLoad(log); } final long finalUsedMemoryInMB = usedMemoryInMB; UserThread.runAfter(() -> { if (finalUsedMemoryInMB > maxMemory) restart(environment); }, 1); } }, CHECK_MEMORY_PERIOD_SEC); while (true) { try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException ignore) { } } } private void restart(BitsquareEnvironment environment) { stopped = true; seedNode.gracefulShutDown(() -> { try { final String[] tokens = environment.getAppDataDir().split("_"); String logPath = "error_" + (tokens.length > 1 ? tokens[tokens.length - 2] : "") + ".log"; RestartUtil.restartApplication(logPath); } catch (IOException e) { log.error(e.toString()); e.printStackTrace(); } finally { log.warn("Shutdown complete"); System.exit(0); } }); } }