diff --git a/src/main/java/org/jenkinsci/plugins/workflow/log/FileLogStorage.java b/src/main/java/org/jenkinsci/plugins/workflow/log/FileLogStorage.java index a4d7ae9a..0c1b750a 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/log/FileLogStorage.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/log/FileLogStorage.java @@ -26,6 +26,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import hudson.Functions; import hudson.console.AnnotatedLargeText; import hudson.console.ConsoleAnnotationOutputStream; import hudson.model.BuildListener; @@ -94,6 +95,14 @@ private FileLogStorage(File log) { private synchronized void open() throws IOException { if (os == null) { os = new FileOutputStream(log, true); + // TODO mandatory file locks break log reading on Windows (see FileLogStorageTest.smokes etc.) + // And LargeText offers no control over how FileSession works, so cannot share the channel. + // Could instead lock the index file, then use openStorages to ensure that readers reuse the channel. + if (!Functions.isWindows()) { + LOGGER.fine(() -> "locking " + log + "…"); + os.getChannel().lock(); + LOGGER.fine(() -> "…locked " + log); + } osStartPosition = log.length(); cos = new CountingOutputStream(os); bos = LogStorage.wrapWithAutoFlushingBuffer(cos); @@ -189,6 +198,9 @@ private final class IndexOutputStream extends OutputStream { openStorages.remove(log); try { bos.close(); + if (!Functions.isWindows()) { + LOGGER.fine(() -> "closed " + log + " which should have released its lock"); + } } finally { indexOs.close(); } diff --git a/src/test/java/org/jenkinsci/plugins/workflow/log/FileLogStorageTest.java b/src/test/java/org/jenkinsci/plugins/workflow/log/FileLogStorageTest.java index fb35e8fe..b96011eb 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/log/FileLogStorageTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/log/FileLogStorageTest.java @@ -28,13 +28,17 @@ import hudson.model.TaskListener; import java.io.File; +import java.util.logging.Level; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.jvnet.hudson.test.LoggerRule; public class FileLogStorageTest extends LogStorageTestBase { + @ClassRule public static LoggerRule fineLogging = new LoggerRule(); @Rule public TemporaryFolder tmp = new TemporaryFolder(); private File log; @@ -56,6 +60,7 @@ public class FileLogStorageTest extends LogStorageTestBase { } @Test public void interruptionDoesNotCloseStream() throws Exception { + fineLogging.record(FileLogStorage.class, Level.FINE); LogStorage ls = createStorage(); TaskListener overall = ls.overallListener(); overall.getLogger().println("overall 1");