Factor out PID file creation and add tests
This commit factors out the PID file creation from bootstrap and adds tests for error conditions etc. We also can't rely on DELETE_ON_CLOSE since it might not even write the file depending on the OS and JVM implementation. This impl uses a shutdown hook to best-effort remove the pid file if it was written. Closes #8771
This commit is contained in:
parent
ab0e3a6db2
commit
f4052fd936
|
@ -22,6 +22,7 @@ package org.elasticsearch.bootstrap;
|
|||
import com.google.common.base.Charsets;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.PidFile;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.inject.CreationException;
|
||||
import org.elasticsearch.common.inject.spi.Message;
|
||||
|
@ -58,7 +59,6 @@ public class Bootstrap {
|
|||
|
||||
private static volatile Thread keepAliveThread;
|
||||
private static volatile CountDownLatch keepAliveLatch;
|
||||
|
||||
private static Bootstrap bootstrap;
|
||||
|
||||
private void setup(boolean addShutdownHook, Tuple<Settings, Environment> tuple) throws Exception {
|
||||
|
@ -151,12 +151,7 @@ public class Bootstrap {
|
|||
|
||||
if (pidFile != null) {
|
||||
try {
|
||||
Path fPidFile = Paths.get(pidFile);
|
||||
Files.createDirectories(fPidFile.getParent());
|
||||
OutputStream outputStream = Files.newOutputStream(fPidFile, StandardOpenOption.DELETE_ON_CLOSE);
|
||||
outputStream.write(Long.toString(JvmInfo.jvmInfo().pid()).getBytes(Charsets.UTF_8));
|
||||
outputStream.flush(); // make those bytes visible...
|
||||
// don't close this stream we will delete on JVM exit
|
||||
PidFile.create(Paths.get(pidFile), true);
|
||||
} catch (Exception e) {
|
||||
String errorMessage = buildErrorMessage("pid", e);
|
||||
System.err.println(errorMessage);
|
||||
|
@ -164,7 +159,6 @@ public class Bootstrap {
|
|||
System.exit(3);
|
||||
}
|
||||
}
|
||||
|
||||
boolean foreground = System.getProperty("es.foreground", System.getProperty("es-foreground")) != null;
|
||||
// handle the wrapper system property, if its a service, don't run as a service
|
||||
if (System.getProperty("wrapper.service", "XXX").equalsIgnoreCase("true")) {
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.common;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchIllegalArgumentException;
|
||||
import org.elasticsearch.monitor.jvm.JvmInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
/**
|
||||
* Process ID file abstraction that writes the current pid into a file and optionally
|
||||
* removes it on system exit.
|
||||
*/
|
||||
public final class PidFile {
|
||||
|
||||
private final long pid;
|
||||
private final Path path;
|
||||
private final boolean deleteOnExit;
|
||||
|
||||
private PidFile(Path path, boolean deleteOnExit, long pid) throws IOException {
|
||||
this.path = path;
|
||||
this.deleteOnExit = deleteOnExit;
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new PidFile and writes the current process ID into the provided path
|
||||
*
|
||||
* @param path the path to the pid file. The file is newly created or truncated if it already exists
|
||||
* @param deleteOnExit if <code>true</code> the pid file is deleted with best effort on system exit
|
||||
* @throws IOException if an IOException occurs
|
||||
*/
|
||||
public static PidFile create(Path path, boolean deleteOnExit) throws IOException {
|
||||
return create(path, deleteOnExit, JvmInfo.jvmInfo().pid());
|
||||
}
|
||||
|
||||
static PidFile create(Path path, boolean deleteOnExit, long pid) throws IOException {
|
||||
Path parent = path.getParent();
|
||||
if (parent != null) {
|
||||
if (Files.exists(parent) && Files.isDirectory(parent) == false) {
|
||||
throw new ElasticsearchIllegalArgumentException(parent + " exists but is not a directory");
|
||||
}
|
||||
if (Files.exists(parent) == false) {
|
||||
// only do this if it doesn't exists we get a better exception further down
|
||||
// if there are security issues etc. this also doesn't work if the parent exists
|
||||
// and is a soft-link like on many linux systems /var/run can be a link and that should
|
||||
// not prevent us from writing the PID
|
||||
Files.createDirectories(parent);
|
||||
}
|
||||
}
|
||||
if (Files.exists(path) && Files.isRegularFile(path) == false) {
|
||||
throw new ElasticsearchIllegalArgumentException(path + " exists but is not a regular file");
|
||||
}
|
||||
|
||||
try(OutputStream stream = Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
stream.write(Long.toString(pid).getBytes(Charsets.UTF_8));
|
||||
}
|
||||
|
||||
if (deleteOnExit) {
|
||||
addShutdownHook(path);
|
||||
}
|
||||
return new PidFile(path, deleteOnExit, pid);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current process id
|
||||
*/
|
||||
public long getPid() {
|
||||
return pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the process id file path
|
||||
*/
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> iff the process id file is deleted on system exit. Otherwise <code>false</code>.
|
||||
*/
|
||||
public boolean isDeleteOnExit() {
|
||||
return deleteOnExit;
|
||||
}
|
||||
|
||||
private static void addShutdownHook(final Path path) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Files.deleteIfExists(path);
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("Failed to delete pid file " + path, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.common;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import org.elasticsearch.ElasticsearchIllegalArgumentException;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
/**
|
||||
* UnitTest for {@link org.elasticsearch.common.PidFile}
|
||||
*/
|
||||
public class PidFileTests extends ElasticsearchTestCase {
|
||||
|
||||
@Test(expected = ElasticsearchIllegalArgumentException.class)
|
||||
public void testParentIsFile() throws IOException {
|
||||
Path dir = newTempDir().toPath();
|
||||
Path parent = dir.resolve("foo");
|
||||
try(BufferedWriter stream = Files.newBufferedWriter(parent, Charsets.UTF_8, StandardOpenOption.CREATE_NEW)) {
|
||||
stream.write("foo");
|
||||
}
|
||||
|
||||
PidFile.create(parent.resolve("bar.pid"), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPidFile() throws IOException {
|
||||
Path dir = newTempDir().toPath();
|
||||
Path parent = dir.resolve("foo");
|
||||
if (randomBoolean()) {
|
||||
Files.createDirectories(parent);
|
||||
if (randomBoolean()) {
|
||||
try {
|
||||
Path link = dir.resolve("link_to_real_path");
|
||||
Files.createSymbolicLink(link, parent);
|
||||
parent = link;
|
||||
} catch (UnsupportedOperationException ex) {
|
||||
// fine - no links on this system
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Path pidFile = parent.resolve("foo.pid");
|
||||
long pid = randomLong();
|
||||
if (randomBoolean() && Files.exists(parent)) {
|
||||
try (BufferedWriter stream = Files.newBufferedWriter(pidFile, Charsets.UTF_8, StandardOpenOption.CREATE_NEW)) {
|
||||
stream.write("foo");
|
||||
}
|
||||
}
|
||||
|
||||
final PidFile inst = PidFile.create(pidFile, false, pid);
|
||||
assertEquals(pidFile, inst.getPath());
|
||||
assertEquals(pid, inst.getPid());
|
||||
assertFalse(inst.isDeleteOnExit());
|
||||
assertTrue(Files.exists(pidFile));
|
||||
assertEquals(pid, Long.parseLong(new String(Files.readAllBytes(pidFile), Charsets.UTF_8)));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue