Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Error at shutdown if tmpdir is not writeable even though it is not used #13013

@gaeljw

Description

@gaeljw

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:

applicationLifecycle.addStopHook { () =>
Future.successful {
if (JFiles.isDirectory(playTempFolder)) {

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions