Add `spring.pid.fail-on-write-error` support
Update `ApplicationPidFileWriter` to support a 'fail on write error' properties which allows the user to exit the application if the PID file cannot be written. This commit also deprecates `spring.pidfile` in favor of `spring.pid.file` so that the new property can be added without overlap. Fixes gh-2764
This commit is contained in:
parent
c13ff96b78
commit
becced5f0b
|
|
@ -18,6 +18,9 @@ package org.springframework.boot.actuate.system;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
|
|
@ -36,11 +39,21 @@ import org.springframework.util.Assert;
|
||||||
* An {@link ApplicationListener} that saves application PID into file. This application
|
* An {@link ApplicationListener} that saves application PID into file. This application
|
||||||
* listener will be triggered exactly once per JVM, and the file name can be overridden at
|
* listener will be triggered exactly once per JVM, and the file name can be overridden at
|
||||||
* runtime with a System property or environment variable named "PIDFILE" (or "pidfile")
|
* runtime with a System property or environment variable named "PIDFILE" (or "pidfile")
|
||||||
* or using a {@code spring.pidfile} property in the Spring {@link Environment}.
|
* or using a {@code spring.pid.file} property in the Spring {@link Environment}.
|
||||||
|
* <p>
|
||||||
|
* If PID file can not be created no exception is reported. This behavior can be changed
|
||||||
|
* by assigning {@code true} to System property or environment variable named
|
||||||
|
* {@code PID_FAIL_ON_WRITE_ERROR} (or "pid_fail_on_write_error") or to
|
||||||
|
* {@code spring.pid.fail-on-write-error} property in the Spring {@link Environment}.
|
||||||
|
* <p>
|
||||||
|
* Note: access to the Spring {@link Environment} is only possible when the
|
||||||
|
* {@link #setTriggerEventType(Class) triggerEventType} is set to
|
||||||
|
* {@link ApplicationEnvironmentPreparedEvent} or {@link ApplicationPreparedEvent}.
|
||||||
*
|
*
|
||||||
* @author Jakub Kubrynski
|
* @author Jakub Kubrynski
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Tomasz Przybyla
|
||||||
* @since 1.2.0
|
* @since 1.2.0
|
||||||
*/
|
*/
|
||||||
public class ApplicationPidFileWriter implements
|
public class ApplicationPidFileWriter implements
|
||||||
|
|
@ -50,9 +63,22 @@ public class ApplicationPidFileWriter implements
|
||||||
|
|
||||||
private static final String DEFAULT_FILE_NAME = "application.pid";
|
private static final String DEFAULT_FILE_NAME = "application.pid";
|
||||||
|
|
||||||
private static final String[] SYSTEM_PROPERTY_VARIABLES = { "PIDFILE", "pidfile" };
|
private static final List<Property> FILE_PROPERTIES;
|
||||||
|
static {
|
||||||
|
List<Property> properties = new ArrayList<Property>();
|
||||||
|
properties.add(new SpringProperty("spring.pid.", "file"));
|
||||||
|
properties.add(new SpringProperty("spring.", "pidfile"));
|
||||||
|
properties.add(new SystemProperty("PIDFILE"));
|
||||||
|
FILE_PROPERTIES = Collections.unmodifiableList(properties);
|
||||||
|
}
|
||||||
|
|
||||||
private static final String SPRING_PROPERTY = "spring.pidfile";
|
private static final List<Property> FAIL_ON_WRITE_ERROR_PROPERTIES;
|
||||||
|
static {
|
||||||
|
List<Property> properties = new ArrayList<Property>();
|
||||||
|
properties.add(new SpringProperty("spring.pid.", "fail-on-write-error"));
|
||||||
|
properties.add(new SystemProperty("PID_FAIL_ON_WRITE_ERROR"));
|
||||||
|
FAIL_ON_WRITE_ERROR_PROPERTIES = Collections.unmodifiableList(properties);
|
||||||
|
}
|
||||||
|
|
||||||
private static final AtomicBoolean created = new AtomicBoolean(false);
|
private static final AtomicBoolean created = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
|
@ -108,7 +134,12 @@ public class ApplicationPidFileWriter implements
|
||||||
writePidFile(event);
|
writePidFile(event);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
logger.warn(String.format("Cannot create pid file %s", this.file), ex);
|
String message = String
|
||||||
|
.format("Cannot create pid file %s", this.file);
|
||||||
|
if (failOnWriteError(event)) {
|
||||||
|
throw new IllegalStateException(message, ex);
|
||||||
|
}
|
||||||
|
logger.warn(message, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -116,31 +147,25 @@ public class ApplicationPidFileWriter implements
|
||||||
|
|
||||||
private void writePidFile(SpringApplicationEvent event) throws IOException {
|
private void writePidFile(SpringApplicationEvent event) throws IOException {
|
||||||
File pidFile = this.file;
|
File pidFile = this.file;
|
||||||
String override = SystemProperties.get(SYSTEM_PROPERTY_VARIABLES);
|
String override = getProperty(event, FILE_PROPERTIES);
|
||||||
if (override != null) {
|
if (override != null) {
|
||||||
pidFile = new File(override);
|
pidFile = new File(override);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
Environment environment = getEnvironment(event);
|
|
||||||
if (environment != null) {
|
|
||||||
override = new RelaxedPropertyResolver(environment)
|
|
||||||
.getProperty(SPRING_PROPERTY);
|
|
||||||
if (override != null) {
|
|
||||||
pidFile = new File(override);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new ApplicationPid().write(pidFile);
|
new ApplicationPid().write(pidFile);
|
||||||
pidFile.deleteOnExit();
|
pidFile.deleteOnExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Environment getEnvironment(SpringApplicationEvent event) {
|
private boolean failOnWriteError(SpringApplicationEvent event) {
|
||||||
if (event instanceof ApplicationEnvironmentPreparedEvent) {
|
String value = getProperty(event, FAIL_ON_WRITE_ERROR_PROPERTIES);
|
||||||
return ((ApplicationEnvironmentPreparedEvent) event).getEnvironment();
|
return (value == null ? false : Boolean.parseBoolean(value));
|
||||||
}
|
}
|
||||||
if (event instanceof ApplicationPreparedEvent) {
|
|
||||||
return ((ApplicationPreparedEvent) event).getApplicationContext()
|
private String getProperty(SpringApplicationEvent event, List<Property> candidates) {
|
||||||
.getEnvironment();
|
for (Property candidate : candidates) {
|
||||||
|
String value = candidate.getValue(event);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -160,4 +185,69 @@ public class ApplicationPidFileWriter implements
|
||||||
static void reset() {
|
static void reset() {
|
||||||
created.set(false);
|
created.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to a property value.
|
||||||
|
*/
|
||||||
|
private static interface Property {
|
||||||
|
|
||||||
|
String getValue(SpringApplicationEvent event);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Property} obtained from Spring's {@link Environment}.
|
||||||
|
*/
|
||||||
|
private static class SpringProperty implements Property {
|
||||||
|
|
||||||
|
private final String prexfix;
|
||||||
|
|
||||||
|
private final String key;
|
||||||
|
|
||||||
|
public SpringProperty(String prefix, String key) {
|
||||||
|
this.prexfix = prefix;
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue(SpringApplicationEvent event) {
|
||||||
|
Environment environment = getEnvironment(event);
|
||||||
|
if (environment == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new RelaxedPropertyResolver(environment, this.prexfix)
|
||||||
|
.getProperty(this.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Environment getEnvironment(SpringApplicationEvent event) {
|
||||||
|
if (event instanceof ApplicationEnvironmentPreparedEvent) {
|
||||||
|
return ((ApplicationEnvironmentPreparedEvent) event).getEnvironment();
|
||||||
|
}
|
||||||
|
if (event instanceof ApplicationPreparedEvent) {
|
||||||
|
return ((ApplicationPreparedEvent) event).getApplicationContext()
|
||||||
|
.getEnvironment();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Property} obtained from {@link SystemProperties}.
|
||||||
|
*/
|
||||||
|
private static class SystemProperty implements Property {
|
||||||
|
|
||||||
|
private final String[] properties;
|
||||||
|
|
||||||
|
public SystemProperty(String name) {
|
||||||
|
this.properties = new String[] { name.toUpperCase(), name.toLowerCase() };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue(SpringApplicationEvent event) {
|
||||||
|
return SystemProperties.get(this.properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package org.springframework.boot.actuate.system;
|
package org.springframework.boot.actuate.system;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Access to system properties.
|
||||||
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
class SystemProperties {
|
class SystemProperties {
|
||||||
|
|
|
||||||
|
|
@ -87,9 +87,22 @@
|
||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
"description": "Resource reference to a generated git info properties file."
|
"description": "Resource reference to a generated git info properties file."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spring.pid.file",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "Location of the PID file to write (if ApplicationPidFileWriter is used).",
|
||||||
|
"sourceType": "org.springframework.boot.actuate.system.ApplicationPidFileWriter"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"name": "spring.pid.fail-on-write-error",
|
||||||
|
"type": "java.lang.Boolean",
|
||||||
|
"description": "Fail if ApplicationPidFileWriter is used but it cannot write the PID file.",
|
||||||
|
"sourceType": "org.springframework.boot.actuate.system.ApplicationPidFileWriter"
|
||||||
|
}
|
||||||
{
|
{
|
||||||
"name": "spring.pidfile",
|
"name": "spring.pidfile",
|
||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
|
"deprecated:" true,
|
||||||
"description": "Location of the PID file to write (if ApplicationPidFileWriter is used).",
|
"description": "Location of the PID file to write (if ApplicationPidFileWriter is used).",
|
||||||
"sourceType": "org.springframework.boot.actuate.system.ApplicationPidFileWriter"
|
"sourceType": "org.springframework.boot.actuate.system.ApplicationPidFileWriter"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2010-2014 the original author or authors.
|
* Copyright 2010-2015 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -23,11 +23,13 @@ import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
|
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
|
||||||
import org.springframework.boot.context.event.ApplicationPreparedEvent;
|
import org.springframework.boot.context.event.ApplicationPreparedEvent;
|
||||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||||
|
import org.springframework.boot.context.event.SpringApplicationEvent;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.core.env.ConfigurableEnvironment;
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
import org.springframework.core.env.StandardEnvironment;
|
import org.springframework.core.env.StandardEnvironment;
|
||||||
|
|
@ -46,6 +48,7 @@ import static org.mockito.Mockito.mock;
|
||||||
* @author Jakub Kubrynski
|
* @author Jakub Kubrynski
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Tomasz Przybyla
|
||||||
*/
|
*/
|
||||||
public class ApplicationPidFileWriterTests {
|
public class ApplicationPidFileWriterTests {
|
||||||
|
|
||||||
|
|
@ -56,10 +59,14 @@ public class ApplicationPidFileWriterTests {
|
||||||
@Rule
|
@Rule
|
||||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException exception = ExpectedException.none();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@After
|
@After
|
||||||
public void resetListener() {
|
public void resetListener() {
|
||||||
System.clearProperty("PIDFILE");
|
System.clearProperty("PIDFILE");
|
||||||
|
System.clearProperty("PID_FAIL_ON_WRITE_ERROR");
|
||||||
ApplicationPidFileWriter.reset();
|
ApplicationPidFileWriter.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,14 +92,8 @@ public class ApplicationPidFileWriterTests {
|
||||||
@Test
|
@Test
|
||||||
public void overridePidFileWithSpring() throws Exception {
|
public void overridePidFileWithSpring() throws Exception {
|
||||||
File file = this.temporaryFolder.newFile();
|
File file = this.temporaryFolder.newFile();
|
||||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
SpringApplicationEvent event = createPreparedEvent("spring.pidfile",
|
||||||
MockPropertySource propertySource = new MockPropertySource();
|
file.getAbsolutePath());
|
||||||
propertySource.setProperty("spring.pidfile", file.getAbsolutePath());
|
|
||||||
environment.getPropertySources().addLast(propertySource);
|
|
||||||
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
|
|
||||||
given(context.getEnvironment()).willReturn(environment);
|
|
||||||
ApplicationPreparedEvent event = new ApplicationPreparedEvent(
|
|
||||||
new SpringApplication(), new String[] {}, context);
|
|
||||||
ApplicationPidFileWriter listener = new ApplicationPidFileWriter();
|
ApplicationPidFileWriter listener = new ApplicationPidFileWriter();
|
||||||
listener.onApplicationEvent(event);
|
listener.onApplicationEvent(event);
|
||||||
assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString()));
|
assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString()));
|
||||||
|
|
@ -101,12 +102,8 @@ public class ApplicationPidFileWriterTests {
|
||||||
@Test
|
@Test
|
||||||
public void differentEventTypes() throws Exception {
|
public void differentEventTypes() throws Exception {
|
||||||
File file = this.temporaryFolder.newFile();
|
File file = this.temporaryFolder.newFile();
|
||||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
SpringApplicationEvent event = createEnvironmentPreparedEvent("spring.pidfile",
|
||||||
MockPropertySource propertySource = new MockPropertySource();
|
file.getAbsolutePath());
|
||||||
propertySource.setProperty("spring.pidfile", file.getAbsolutePath());
|
|
||||||
environment.getPropertySources().addLast(propertySource);
|
|
||||||
ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(
|
|
||||||
new SpringApplication(), new String[] {}, environment);
|
|
||||||
ApplicationPidFileWriter listener = new ApplicationPidFileWriter();
|
ApplicationPidFileWriter listener = new ApplicationPidFileWriter();
|
||||||
listener.onApplicationEvent(event);
|
listener.onApplicationEvent(event);
|
||||||
assertThat(FileCopyUtils.copyToString(new FileReader(file)), isEmptyString());
|
assertThat(FileCopyUtils.copyToString(new FileReader(file)), isEmptyString());
|
||||||
|
|
@ -125,4 +122,64 @@ public class ApplicationPidFileWriterTests {
|
||||||
assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString()));
|
assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void continueWhenPidFileIsReadOnly() throws Exception {
|
||||||
|
File file = this.temporaryFolder.newFile();
|
||||||
|
file.setReadOnly();
|
||||||
|
ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file);
|
||||||
|
listener.onApplicationEvent(EVENT);
|
||||||
|
assertThat(FileCopyUtils.copyToString(new FileReader(file)), isEmptyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void throwWhenPidFileIsReadOnly() throws Exception {
|
||||||
|
File file = this.temporaryFolder.newFile();
|
||||||
|
file.setReadOnly();
|
||||||
|
System.setProperty("PID_FAIL_ON_WRITE_ERROR", "true");
|
||||||
|
ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file);
|
||||||
|
this.exception.expect(IllegalStateException.class);
|
||||||
|
this.exception.expectMessage("Cannot create pid file");
|
||||||
|
listener.onApplicationEvent(EVENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void throwWhenPidFileIsReadOnlyWithSpring() throws Exception {
|
||||||
|
File file = this.temporaryFolder.newFile();
|
||||||
|
file.setReadOnly();
|
||||||
|
SpringApplicationEvent event = createPreparedEvent(
|
||||||
|
"spring.pid.fail-on-write-error", "true");
|
||||||
|
ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file);
|
||||||
|
this.exception.expect(IllegalStateException.class);
|
||||||
|
this.exception.expectMessage("Cannot create pid file");
|
||||||
|
listener.onApplicationEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SpringApplicationEvent createEnvironmentPreparedEvent(String propName,
|
||||||
|
String propValue) {
|
||||||
|
ConfigurableEnvironment environment = createEnvironment(propName, propValue);
|
||||||
|
return new ApplicationEnvironmentPreparedEvent(new SpringApplication(),
|
||||||
|
new String[] {}, environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SpringApplicationEvent createPreparedEvent(String propName, String propValue) {
|
||||||
|
ConfigurableEnvironment environment = createEnvironment(propName, propValue);
|
||||||
|
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
|
||||||
|
given(context.getEnvironment()).willReturn(environment);
|
||||||
|
return new ApplicationPreparedEvent(new SpringApplication(), new String[] {},
|
||||||
|
context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigurableEnvironment createEnvironment(String propName, String propValue) {
|
||||||
|
MockPropertySource propertySource = mockPropertySource(propName, propValue);
|
||||||
|
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||||
|
environment.getPropertySources().addLast(propertySource);
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockPropertySource mockPropertySource(String name, String value) {
|
||||||
|
MockPropertySource propertySource = new MockPropertySource();
|
||||||
|
propertySource.setProperty(name, value);
|
||||||
|
return propertySource;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -612,7 +612,8 @@ content into your application; rather pick only the properties that you need.
|
||||||
management.security.sessions=stateless # session creating policy to use (always, never, if_required, stateless)
|
management.security.sessions=stateless # session creating policy to use (always, never, if_required, stateless)
|
||||||
|
|
||||||
# PID FILE ({sc-spring-boot-actuator}/system/ApplicationPidFileWriter.{sc-ext}[ApplicationPidFileWriter])
|
# PID FILE ({sc-spring-boot-actuator}/system/ApplicationPidFileWriter.{sc-ext}[ApplicationPidFileWriter])
|
||||||
spring.pidfile= # Location of the PID file to write
|
spring.pid.file= # Location of the PID file to write
|
||||||
|
spring.pid.fail-on-write-error= # Fail if the PID file cannot be written
|
||||||
|
|
||||||
# ENDPOINTS ({sc-spring-boot-actuator}/endpoint/AbstractEndpoint.{sc-ext}[AbstractEndpoint] subclasses)
|
# ENDPOINTS ({sc-spring-boot-actuator}/endpoint/AbstractEndpoint.{sc-ext}[AbstractEndpoint] subclasses)
|
||||||
endpoints.autoconfig.id=autoconfig
|
endpoints.autoconfig.id=autoconfig
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue