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
pull/3297/merge
Tomasz Przybyła 10 years ago committed by Phillip Webb
parent c13ff96b78
commit becced5f0b

@ -18,6 +18,9 @@ package org.springframework.boot.actuate.system;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
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
* 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")
* 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 Dave Syer
* @author Phillip Webb
* @author Tomasz Przybyla
* @since 1.2.0
*/
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[] 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);
@ -108,7 +134,12 @@ public class ApplicationPidFileWriter implements
writePidFile(event);
}
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 {
File pidFile = this.file;
String override = SystemProperties.get(SYSTEM_PROPERTY_VARIABLES);
String override = getProperty(event, FILE_PROPERTIES);
if (override != null) {
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);
pidFile.deleteOnExit();
}
private Environment getEnvironment(SpringApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
return ((ApplicationEnvironmentPreparedEvent) event).getEnvironment();
}
if (event instanceof ApplicationPreparedEvent) {
return ((ApplicationPreparedEvent) event).getApplicationContext()
.getEnvironment();
private boolean failOnWriteError(SpringApplicationEvent event) {
String value = getProperty(event, FAIL_ON_WRITE_ERROR_PROPERTIES);
return (value == null ? false : Boolean.parseBoolean(value));
}
private String getProperty(SpringApplicationEvent event, List<Property> candidates) {
for (Property candidate : candidates) {
String value = candidate.getValue(event);
if (value != null) {
return value;
}
}
return null;
}
@ -160,4 +185,69 @@ public class ApplicationPidFileWriter implements
static void reset() {
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;
/**
* Access to system properties.
*
* @author Phillip Webb
*/
class SystemProperties {

@ -87,9 +87,22 @@
"type": "java.lang.String",
"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",
"type": "java.lang.String",
"deprecated:" true,
"description": "Location of the PID file to write (if ApplicationPidFileWriter is used).",
"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");
* 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.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.boot.context.event.SpringApplicationEvent;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;
@ -46,6 +48,7 @@ import static org.mockito.Mockito.mock;
* @author Jakub Kubrynski
* @author Dave Syer
* @author Phillip Webb
* @author Tomasz Przybyla
*/
public class ApplicationPidFileWriterTests {
@ -56,10 +59,14 @@ public class ApplicationPidFileWriterTests {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public ExpectedException exception = ExpectedException.none();
@Before
@After
public void resetListener() {
System.clearProperty("PIDFILE");
System.clearProperty("PID_FAIL_ON_WRITE_ERROR");
ApplicationPidFileWriter.reset();
}
@ -85,14 +92,8 @@ public class ApplicationPidFileWriterTests {
@Test
public void overridePidFileWithSpring() throws Exception {
File file = this.temporaryFolder.newFile();
ConfigurableEnvironment environment = new StandardEnvironment();
MockPropertySource propertySource = new MockPropertySource();
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);
SpringApplicationEvent event = createPreparedEvent("spring.pidfile",
file.getAbsolutePath());
ApplicationPidFileWriter listener = new ApplicationPidFileWriter();
listener.onApplicationEvent(event);
assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString()));
@ -101,12 +102,8 @@ public class ApplicationPidFileWriterTests {
@Test
public void differentEventTypes() throws Exception {
File file = this.temporaryFolder.newFile();
ConfigurableEnvironment environment = new StandardEnvironment();
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.pidfile", file.getAbsolutePath());
environment.getPropertySources().addLast(propertySource);
ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent(
new SpringApplication(), new String[] {}, environment);
SpringApplicationEvent event = createEnvironmentPreparedEvent("spring.pidfile",
file.getAbsolutePath());
ApplicationPidFileWriter listener = new ApplicationPidFileWriter();
listener.onApplicationEvent(event);
assertThat(FileCopyUtils.copyToString(new FileReader(file)), isEmptyString());
@ -125,4 +122,64 @@ public class ApplicationPidFileWriterTests {
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)
# 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.autoconfig.id=autoconfig

Loading…
Cancel
Save