From 7790dbcabfcfe3aeb8db54cf13673036e0548d34 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 9 Sep 2013 13:24:58 -0400 Subject: [PATCH] [FIXED JENKINS-19515] Try to avoid truncation of fingerprint storage files. 1. Use AtomicFileWriter on the hypothesis that an exception during save() caused truncation. 2. Improve logging from the cleanup thread since that may be related to the root cause. 3. Regardless of the cause, recover more gracefully by deleting any truncated file rather than throwing the error up. --- changelog.html | 3 +++ .../main/java/hudson/model/Fingerprint.java | 22 +++++++++++++------ .../model/FingerprintCleanupThread.java | 2 ++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/changelog.html b/changelog.html index a4af17ce93..2540ce9d85 100644 --- a/changelog.html +++ b/changelog.html @@ -67,6 +67,9 @@ Upcoming changes
  • Since 1.518, fingerprint serialization broke when job or file names contained XML special characters like ampersands. (issue 18337) +
  • + Robustness against truncated fingerprint files. + (issue 19515)
  • JavaScript error in the checkUrl computation shouldn't break the job configuration page. (issue 19457) diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java index 59a759460e..0e91bf7cf2 100644 --- a/core/src/main/java/hudson/model/Fingerprint.java +++ b/core/src/main/java/hudson/model/Fingerprint.java @@ -38,11 +38,13 @@ import hudson.BulkChange; import hudson.Extension; import hudson.model.listeners.ItemListener; import hudson.model.listeners.SaveableListener; +import hudson.util.AtomicFileWriter; import hudson.util.HexBinaryConverter; import hudson.util.Iterators; import hudson.util.PersistedList; import hudson.util.RunList; import hudson.util.XStream2; +import java.io.EOFException; import jenkins.model.FingerprintFacet; import jenkins.model.Jenkins; import jenkins.model.TransientFingerprintFacetFactory; @@ -995,8 +997,12 @@ public class Fingerprint implements ModelObject, Saveable { } } - if (modified) + if (modified) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Saving trimmed {0}", getFingerprintFile(md5sum)); + } save(); + } return modified; } @@ -1101,8 +1107,9 @@ public class Fingerprint implements ModelObject, Saveable { if (facets.isEmpty()) { file.getParentFile().mkdirs(); // JENKINS-16301: fast path for the common case. - PrintWriter w = new PrintWriter(file, "UTF-8"); + AtomicFileWriter afw = new AtomicFileWriter(file); try { + PrintWriter w = new PrintWriter(afw); w.println(""); w.println(""); w.print(" "); @@ -1139,8 +1146,9 @@ public class Fingerprint implements ModelObject, Saveable { w.println(" "); w.print(""); w.flush(); + afw.commit(); } finally { - w.close(); + afw.abort(); } } else { // Slower fallback that can persist facets. @@ -1230,7 +1238,7 @@ public class Fingerprint implements ModelObject, Saveable { file.delete(); return null; } - String parseError = messageOfXmlPullParserException(e); + String parseError = messageOfParseException(e); if (parseError != null) { logger.log(Level.WARNING, "Malformed XML in {0}: {1}", new Object[] {configFile, parseError}); file.delete(); @@ -1240,13 +1248,13 @@ public class Fingerprint implements ModelObject, Saveable { throw e; } } - private static String messageOfXmlPullParserException(Throwable t) { - if (t instanceof XmlPullParserException) { + private static String messageOfParseException(Throwable t) { + if (t instanceof XmlPullParserException || t instanceof EOFException) { return t.getMessage(); } Throwable t2 = t.getCause(); if (t2 != null) { - return messageOfXmlPullParserException(t2); + return messageOfParseException(t2); } else { return null; } diff --git a/core/src/main/java/hudson/model/FingerprintCleanupThread.java b/core/src/main/java/hudson/model/FingerprintCleanupThread.java index f8be385ba0..c04830911d 100644 --- a/core/src/main/java/hudson/model/FingerprintCleanupThread.java +++ b/core/src/main/java/hudson/model/FingerprintCleanupThread.java @@ -101,11 +101,13 @@ public final class FingerprintCleanupThread extends AsyncPeriodicWork { try { Fingerprint fp = Fingerprint.load(fingerprintFile); if (fp == null || !fp.isAlive()) { + logger.fine("deleting obsolete " + fingerprintFile); fingerprintFile.delete(); return true; } else { // get the fingerprint in the official map so have the changes visible to Jenkins // otherwise the mutation made in FingerprintMap can override our trimming. + logger.finer("possibly trimming " + fingerprintFile); fp = Jenkins.getInstance()._getFingerprint(fp.getHashString()); return fp.trim(); } -- GitLab