[ZEPPELIN-6394] Make the interpreter setting write safe under concurrent saves - #5426
Open
dev-donghwan wants to merge 1 commit into
Open
[ZEPPELIN-6394] Make the interpreter setting write safe under concurrent saves#5426dev-donghwan wants to merge 1 commit into
dev-donghwan wants to merge 1 commit into
Conversation
…ent saves Dependency downloads save the interpreter settings from a thread per interpreter setting, so several threads write conf/interpreter.json at the same moment. FileUtils.atomicWriteToFile writes a temp file and then moves it over the destination with REPLACE_EXISTING alone, which is not atomic: when two writers move their own temp file onto the same destination, one of them fails with NoSuchFileException and leaves its temp file in the config directory. Requesting ATOMIC_MOVE as well removes that. The temp file is created in the destination directory, so the move never crosses file systems and the atomic move is always supported. InterpreterSettingManager.saveToFile takes its snapshot and writes it as one step, so an older snapshot can no longer be written after a newer one and drop a setting that was added in between while it survives in memory until the next restart.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is this PR for?
When several interpreters finish downloading their dependencies at the same time, each of them saves the interpreter settings from its own thread, so
conf/interpreter.jsonis written concurrently. The reporter's log shows those writes failing:There are two problems behind it.
1. The write itself is not atomic.
FileUtils.atomicWriteToFilewrites a temp file and then moves it over the destination withREPLACE_EXISTINGalone. That replace is not a single operation, so when two writers move their own temp file onto the same destination, one of them can fail withNoSuchFileExceptionand leave its temp file behind in the config directory. The failure is swallowed as anERRORlog, so the settings silently do not reach disk.Measured on the current code with 16 threads writing the same file 300 times each:
REPLACE_EXISTINGREPLACE_EXISTING, ATOMIC_MOVEThis PR requests
ATOMIC_MOVEas well. The temp file is created in the destination directory (Files.createTempFile(destinationDirectory, ...)), so the move never crosses file systems and the atomic move is always supported. The option was present as a trailing comment from the first version of this method in ZEPPELIN-4907 and was never enabled; there is no recorded reason for leaving it off.2. The snapshot and the write are two steps.
InterpreterSettingManager.saveToFilecopies the settings and then writes them, without holding anything in between, so an older snapshot can be written after a newer one. When that happens, a setting that was added in between is dropped from the file while it survives in memory, and the loss only shows up after the next restart. This is the serialization the Jira asks for; it is done with a private lock rather thansynchronizedon the method so that the monitor stays internal.Note for the Jira description: on the default
LocalConfigStoragethe concurrent writes do not corrupt the file, because every write goes to its own temp file. What they do is fail, and lose an update.Relation to #5144. The
saveToFile()serialization in this PR overlaps #5144, which was opened by the reporter and already carries an LGTM that a later rebase dismissed. That PR does not cover the move itself, which is what produces theNoSuchFileExceptionin the report. If it is preferred to land #5144 for the serialization, I am happy to drop theInterpreterSettingManagerchange from here and leave only theFileUtilsfix.What type of PR is it?
Bug Fix
What is the Jira issue?
How should this be tested?
Two new tests, both verified to fail without the corresponding change:
FileUtilsTest.testConcurrentWritesToTheSameFileSucceed— 16 threads write the same file 300 times each and no write may fail or leave a temp file behind. Fails on master on every run with theNoSuchFileExceptionabove.InterpreterSettingManagerSaveRaceTest.testSaveDuringAnotherSaveIsNotOverwritten— a setting is added while another save is in flight, and the last write has to carry it. Fails on master withthe last write dropped added_during_save.Existing suites run:
InterpreterSettingManagerTest,InterpreterSettingTest,InterpreterFactoryTest,InterpreterInfoSavingTest,LocalConfigStorageTest,CredentialsTest,LocalRecoveryStorageTest.Questions: