-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Description
Version: Play 3.0.6
During shutdown, the application can throw and log an error because the temporary directory used by Play is not writeable even though this directory was never used in the first place.
This is likely to happen in containerized environments with read-only filesystem as a default setup.
Stack trace:
java.nio.file.FileSystemException: /tmp/playtemp8313349136202869755: Read-only file system
at java.base/sun.nio.fs.UnixException.translateToIOException(Unknown Source)
at java.base/sun.nio.fs.UnixException.rethrowAsIOException(Unknown Source)
at java.base/sun.nio.fs.UnixException.rethrowAsIOException(Unknown Source)
at java.base/sun.nio.fs.UnixFileSystemProvider.createDirectory(Unknown Source)
at java.base/java.nio.file.Files.createDirectory(Unknown Source)
at java.base/java.nio.file.TempFileHelper.create(Unknown Source)
at java.base/java.nio.file.TempFileHelper.createTempDirectory(Unknown Source)
at java.base/java.nio.file.Files.createTempDirectory(Unknown Source)
at play.api.libs.Files$DefaultTemporaryFileCreator.playTempFolder$lzycompute(Files.scala:263)
at play.api.libs.Files$DefaultTemporaryFileCreator.play$api$libs$Files$DefaultTemporaryFileCreator$$playTempFolder(Files.scala:260)
at play.api.libs.Files$DefaultTemporaryFileCreator.$anonfun$new$1(Files.scala:320)
at scala.util.Try$.apply(Try.scala:217)
at play.api.inject.DefaultApplicationLifecycle.$anonfun$stop$2(ApplicationLifecycle.scala:128)
at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:470)
at play.api.libs.streams.Execution$trampoline$.execute(Execution.scala:65)
at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:429)
at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:338)
at scala.concurrent.impl.Promise$DefaultPromise.dispatchOrAddCallbacks(Promise.scala:312)
at scala.concurrent.impl.Promise$DefaultPromise.flatMap(Promise.scala:176)
at play.api.inject.DefaultApplicationLifecycle.clearHooks$1(ApplicationLifecycle.scala:127)
at play.api.inject.DefaultApplicationLifecycle.stop(ApplicationLifecycle.scala:139)
at play.api.libs.concurrent.CoordinatedShutdownProvider.$anonfun$get$2(Pekko.scala:305)
at org.apache.pekko.actor.CoordinatedShutdown$tasks$TaskDefinition$$anon$2.liftedTree1$1(CoordinatedShutdown.scala:438)
at org.apache.pekko.actor.CoordinatedShutdown$tasks$TaskDefinition$$anon$2.run(CoordinatedShutdown.scala:437)
at org.apache.pekko.actor.CoordinatedShutdown$tasks$StrictPhaseDefinition.$anonfun$run$3(CoordinatedShutdown.scala:496)
at scala.collection.StrictOptimizedIterableOps.map(StrictOptimizedIterableOps.scala:100)
at scala.collection.StrictOptimizedIterableOps.map$(StrictOptimizedIterableOps.scala:87)
at scala.collection.immutable.Set$Set1.map(Set.scala:165)
at org.apache.pekko.actor.CoordinatedShutdown$tasks$StrictPhaseDefinition.run(CoordinatedShutdown.scala:496)
at org.apache.pekko.actor.CoordinatedShutdown.loop$1(CoordinatedShutdown.scala:745)
at org.apache.pekko.actor.CoordinatedShutdown.$anonfun$run$7(CoordinatedShutdown.scala:773)
at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:470)
at org.apache.pekko.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:73)
at org.apache.pekko.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:110)
at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:94)
at org.apache.pekko.dispatch.BatchingExecutor$BlockableBatch.run(BatchingExecutor.scala:110)
at org.apache.pekko.dispatch.TaskInvocation.run(AbstractDispatcher.scala:59)
at org.apache.pekko.dispatch.ForkJoinExecutorConfigurator$PekkoForkJoinTask.exec(ForkJoinExecutorConfigurator.scala:57)
at java.base/java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(Unknown Source)
at java.base/java.util.concurrent.ForkJoinPool.scan(Unknown Source)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)
This happens because a stop hook is registered to clean the temporary directory but the directory is created only at first access. If the app doesn't need temporary files, the directory is never created and the stop hook thus tries to create it only to delete it afterwards.
Related sources:
playframework/core/play/src/main/scala/play/api/libs/Files.scala
Lines 318 to 320 in 28447a5
| applicationLifecycle.addStopHook { () => | |
| Future.successful { | |
| if (JFiles.isDirectory(playTempFolder)) { |
playframework/core/play/src/main/scala/play/api/libs/Files.scala
Lines 260 to 266 in 28447a5
| private lazy val playTempFolder: Path = { | |
| val dir = Paths.get(conf.get[String]("play.temporaryFile.dir")) | |
| JFiles.createDirectories(dir) // make sure dir exists, otherwise createTempDirectory fails | |
| val tmpFolder = JFiles.createTempDirectory(dir, TempDirectoryPrefix) | |
| temporaryFileReaper.updateTempFolder(tmpFolder) | |
| tmpFolder | |
| } |
While the error is definitely not an issue, it can raise false alerts for systems watching ERROR logs.
I would suggest to have a way to know if the directory was never created and thus do not try to clean anything. WDYT?