/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.sql;
import com.foundationdb.server.service.config.ConfigurationService;
import com.foundationdb.server.service.servicemanager.GuicedServiceManager;
import com.foundationdb.util.GCMonitor;
import com.foundationdb.util.LoggingStream;
import com.foundationdb.util.OsUtils;
import com.google.inject.Inject;
import com.google.inject.ProvisionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.foundationdb.server.error.StartupFailureException;
import com.foundationdb.server.manage.ManageMXBean;
import com.foundationdb.server.manage.ManageMXBeanImpl;
import com.foundationdb.server.service.Service;
import com.foundationdb.server.service.ServiceManager;
import com.foundationdb.server.service.jmx.JmxManageable;
import javax.management.ObjectName;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.Semaphore;
public class Main implements Service, JmxManageable, LayerInfoInterface
{
private static final Logger LOG = LoggerFactory.getLogger(Main.class);
private static final String VERSION_PROPERTY_FILE = "/version/fdbsql_version.properties";
public static final LayerVersionInfo VERSION_INFO;
static {
Properties props = new Properties();
try(InputStream stream = Main.class.getResourceAsStream(VERSION_PROPERTY_FILE)) {
props.load(stream);
} catch (IOException e) {
LOG.warn("Couldn't read version resource file: {}", VERSION_PROPERTY_FILE);
}
VERSION_INFO = new LayerVersionInfo(props);
}
private static final String NAME_PROP = "fdbsql.name";
private static final String GC_INTERVAL_NAME = "fdbsql.gc_monitor.interval";
private static final String GC_THRESHOLD_NAME = "fdbsql.gc_monitor.log_threshold_ms";
private static final String PID_FILE_NAME = System.getProperty("fdbsql.pidfile");
private static final boolean IS_STD_TO_LOG = Boolean.parseBoolean(System.getProperty("fdbsql.std_to_log", "true"));
private static volatile ShutdownMXBeanImpl shutdownBean = null;
private final JmxObjectInfo jmxObjectInfo;
private final ConfigurationService config;
private GCMonitor gcMonitor;
@Inject
public Main(ConfigurationService config) {
this.config = config;
this.jmxObjectInfo = new JmxObjectInfo(
"SQLLAYER",
new ManageMXBeanImpl(),
ManageMXBean.class
);
}
@Override
public void start() {
int interval = Integer.parseInt(config.getProperty(GC_INTERVAL_NAME));
int logThreshold = Integer.parseInt(config.getProperty(GC_THRESHOLD_NAME));
if(interval > 0) {
gcMonitor = new GCMonitor(interval, logThreshold);
gcMonitor.start();
}
}
@Override
public void stop()
{
if(gcMonitor != null) {
gcMonitor.stopRunning();
gcMonitor = null;
}
}
@Override
public void crash() {
stop();
}
@Override
public JmxObjectInfo getJmxObjectInfo() {
return jmxObjectInfo;
}
@Override
public String getServerName()
{
return config.getProperty(NAME_PROP);
}
@Override
public LayerVersionInfo getVersionInfo() {
return VERSION_INFO;
}
public interface ShutdownMXBean {
public void shutdown();
}
private static class ShutdownMXBeanImpl implements ShutdownMXBean {
private static final String BEAN_NAME = "com.foundationdb:type=SHUTDOWN";
private final ServiceManager sm;
public ShutdownMXBeanImpl(ServiceManager sm) {
this.sm = sm;
}
@Override
public void shutdown() {
try {
if(sm != null) {
sm.stopServices();
}
} catch (Exception e) {
LOG.error("Problem stopping services", e);
}
}
}
public static void main(String[] args) throws Exception {
if(IS_STD_TO_LOG) {
System.setErr(new PrintStream(LoggingStream.forError(LOG), true));
System.setOut(new PrintStream(LoggingStream.forInfo(LOG), true));
}
try {
doStartup();
} catch (ProvisionException e) {
if (e.getCause() instanceof StartupFailureException){
LOG.error(e.getCause().getLocalizedMessage());
} else {
LOG.error("Provisioning exception starting system {}", e);
}
System.exit(1);
} catch(Throwable t) {
LOG.error("Problem starting system", t);
System.exit(1);
}
// Services started successfully, write pid to file.
try {
writePid();
} catch(IOException e) {
LOG.warn("Problem writing pid file {}", PID_FILE_NAME, e);
// Do not abort on error as init scripts handle this fine.
}
}
private static void doStartup() throws Exception {
final ServiceManager serviceManager = new GuicedServiceManager();
Main.shutdownBean = new ShutdownMXBeanImpl(serviceManager);
// JVM shutdown hook.
// Register before startServices() so services are still brought down on startup error.
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
shutdownBean.shutdown();
}
}, "ShutdownHook"));
// Bring system up
serviceManager.startServices();
ObjectName name = new ObjectName(ShutdownMXBeanImpl.BEAN_NAME);
ManagementFactory.getPlatformMBeanServer().registerMBean(shutdownBean, name);
}
private static void writePid() throws IOException {
if (PID_FILE_NAME != null) {
File pidFile = new File(PID_FILE_NAME);
pidFile.deleteOnExit();
FileWriter out = new FileWriter(pidFile);
out.write(OsUtils.getProcessID());
out.flush();
out.close();
}
}
/**
* Start from procrun.
* @see <a href="http://commons.apache.org/daemon/procrun.html">Daemon: Procrun</a>
*/
private static final Semaphore PROCRUN_SEMAPHORE = new Semaphore(0);
private static boolean isProcrunJVMMode(String[] args) {
if(args.length != 1) {
String message = "Expected exactly one argument (StartMode)";
LOG.error("{}: {}", message, Arrays.toString(args));
throw new IllegalArgumentException(message);
}
return "jvm".equals(args[0]);
}
@SuppressWarnings("unused") // Called by procrun
public static void procrunStart(String[] args) throws Exception {
boolean jvmMode = isProcrunJVMMode(args);
main(args);
if(jvmMode) {
PROCRUN_SEMAPHORE.acquire();
}
}
@SuppressWarnings("unused") // Called by procrun
public static void procrunStop(String[] args) throws Exception {
boolean jvmMode = isProcrunJVMMode(args);
// Stop server from another thread.
shutdownBean.shutdown();
if(jvmMode) {
PROCRUN_SEMAPHORE.release();
}
}
}